diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index ed5f2fa..c609506 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -145,6 +145,13 @@ internal static class PluginHostFactory sp.GetRequiredService(), sp.GetRequiredService() )); + services.AddSingleton(sp => new Ui.Windows.MainWindow( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService() + )); services.AddSingleton(sp => new Integrations.FailedTellNotifier( sp.GetRequiredService>() )); diff --git a/HellionChat/Ui/Components/InputBar.cs b/HellionChat/Ui/Components/InputBar.cs index be37f9c..a10cca3 100644 --- a/HellionChat/Ui/Components/InputBar.cs +++ b/HellionChat/Ui/Components/InputBar.cs @@ -16,7 +16,7 @@ namespace HellionChat.Ui.Components; // picker overlay. internal sealed class InputBar { - private const float Height = 32f; + public const float Height = 32f; private const float PillHeight = 22f; private const float PillPaddingX = 8f; private const int BufferCapacity = 500; diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs new file mode 100644 index 0000000..33b6b19 --- /dev/null +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -0,0 +1,121 @@ +using System.Numerics; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; + +namespace HellionChat.Ui.Windows; + +// Top-level chat window assembled from the components layer. Layout from +// top to bottom: honorific header, horizontal body with sidebar + main +// area (messages + input bar), and the status strip pinned to the +// bottom. The window-level theme push stays on the global plugin draw +// path for now — this window only composes content. +// +// Components are fully qualified through the Ui.Components prefix so the +// old Ui.StatusBar type (still alive until the cleanup block removes it) +// cannot shadow the new layer through parent-namespace resolution. +internal sealed class MainWindow : Window +{ + private const float DefaultWidth = 620f; + private const float DefaultHeight = 340f; + private const float MinWidth = 480f; + private const float MinHeight = 260f; + + private readonly Components.HonorificHeader _honorific; + private readonly Components.Sidebar _sidebar; + private readonly Components.MessageList _messages; + private readonly Components.InputBar _input; + private readonly Components.StatusBar _status; + + private Tab? _activeTab; + + public MainWindow( + Components.HonorificHeader honorific, + Components.Sidebar sidebar, + Components.MessageList messages, + Components.InputBar input, + Components.StatusBar status + ) + : base($"{Plugin.PluginName}###hellion-main") + { + _honorific = honorific; + _sidebar = sidebar; + _messages = messages; + _input = input; + _status = status; + + Size = new Vector2(DefaultWidth, DefaultHeight); + SizeCondition = ImGuiCond.FirstUseEver; + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(MinWidth, MinHeight), + MaximumSize = new Vector2(float.MaxValue, float.MaxValue), + }; + IsOpen = Plugin.Config.MainWindowOpen; + RespectCloseHotkey = false; + } + + public Tab? ActiveTab => _activeTab; + + // new-shadow on Window.Toggle so the open path also writes Config — + // OnClose already covers the close path through the base behaviour. + public new void Toggle() + { + IsOpen = !IsOpen; + Plugin.Config.MainWindowOpen = IsOpen; + } + + public override void OnClose() + { + Plugin.Config.MainWindowOpen = false; + } + + public override void Draw() + { + // First-frame seed: the active tab defaults to the first persisted + // tab so the message list isn't empty on a clean session. + if (_activeTab is null && Plugin.Config.Tabs.Count > 0) + _activeTab = Plugin.Config.Tabs[0]; + + var statusHeight = Components.StatusBar.Height; + + using (var body = ImRaii.Child("##hellion-body", new Vector2(-1f, -statusHeight))) + { + if (body.Success) + DrawBody(); + } + + _status.Draw(_activeTab); + } + + private void DrawBody() + { + var bodyWidth = ImGui.GetContentRegionAvail().X; + _honorific.Draw(bodyWidth); + + using (ImRaii.Group()) + { + _sidebar.Draw(bodyWidth, Plugin.Config.Tabs, ref _activeTab); + } + + ImGui.SameLine(); + + using (ImRaii.Group()) + { + DrawMainArea(); + } + } + + private void DrawMainArea() + { + var inputHeight = Components.InputBar.Height; + + using (var messages = ImRaii.Child("##hellion-main-area", new Vector2(-1f, -inputHeight))) + { + if (messages.Success) + _messages.Draw(_activeTab!); + } + + _input.Draw(_activeTab); + } +}