Three smoke bugs from the second in-game test: 1. The channel pill was draw-list only, so it didn't react to clicks and there was no way to switch channels inside a tab. The pill now has a hit area on top and opens a popup that lists every ChatType in tab.SelectedChannels with a ToInputChannel mapping; selecting one writes through CurrentChannel.SetChannel. 2. Switching to Allgemein / Gruppe / Linkshell still showed "—" because tab.CurrentChannel.Channel stayed at Invalid until somebody set it. The sidebar now seeds CurrentChannel on tab activation by walking SelectedChannels for the first key with a valid mapping, so every tab opens with its own real channel instead of inheriting the FC default. 3. MainWindow still surfaced an outer scrollbar next to the message list's own scroll. Adding NoScrollbar + NoScrollWithMouse to the window flags strips the second bar — the body child owns scroll on its own. Plus the system-icon path: System / BattleSystem / GatheringSystem / Error / Notice / LootNotice all map to fa-cog now, so the System tab renders the gear instead of falling back to the generic comment.
131 lines
4.1 KiB
C#
131 lines
4.1 KiB
C#
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),
|
|
};
|
|
// The message list owns its own scroll inside the body child;
|
|
// the outer window must not show a second scrollbar.
|
|
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
|
|
IsOpen = Plugin.Config.MainWindowOpen;
|
|
RespectCloseHotkey = false;
|
|
}
|
|
|
|
public Tab? ActiveTab => _activeTab;
|
|
|
|
// Internal accessors for self-tests so the probes can reach the live
|
|
// component without exposing them as public surface.
|
|
internal Components.Sidebar GetSidebarForSelfTest() => _sidebar;
|
|
|
|
internal Components.HonorificHeader GetHonorificHeaderForSelfTest() => _honorific;
|
|
|
|
// 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);
|
|
}
|
|
}
|