From 100290ea4d0db954eebae723b276350d7176af9e Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Wed, 19 Aug 2026 09:58:29 +0200 Subject: [PATCH] feat(chat): put the channel header above both windows Drawn before the scroll child in each, so it stays put while the log moves. The child's height is deliberately left alone: it is given as a negative value, and ImGui resolves those against the space still available from the current cursor -- which the header has already reduced. Subtracting it a second time would have opened a gap of exactly the header's height above the input row, in both windows. Where the name shows follows one rule: never twice on the same screen. The sidebar layout is the only place nothing else carries it, so that is the only place the header says it. The top-tab strip carries it, and a pop-out with its title bar on carries it in the title. The pop-out's plain title row is gone, replaced by the header. The comment it left behind is worth keeping in mind -- an earlier header row was removed exactly because it repeated the tab name one line below the title bar. That warning is now the rule rather than a reason to have no header at all. The trailing detail is the home world and the clock, and the clock follows the same Use24HourClock setting the message timestamps do. Two clock formats in one window, with the header sitting directly above a column of timestamps, would be a defect rather than a preference. Culture is pinned for the same reason it is pinned in the message list. --- .../Resources/HellionStrings.Designer.cs | 1 + HellionChat/Resources/HellionStrings.ca.resx | 3 ++ HellionChat/Resources/HellionStrings.cs.resx | 3 ++ HellionChat/Resources/HellionStrings.da.resx | 3 ++ HellionChat/Resources/HellionStrings.de.resx | 3 ++ HellionChat/Resources/HellionStrings.el.resx | 3 ++ HellionChat/Resources/HellionStrings.es.resx | 3 ++ HellionChat/Resources/HellionStrings.fi.resx | 3 ++ HellionChat/Resources/HellionStrings.fr.resx | 3 ++ HellionChat/Resources/HellionStrings.hu.resx | 3 ++ HellionChat/Resources/HellionStrings.it.resx | 3 ++ HellionChat/Resources/HellionStrings.ja.resx | 3 ++ HellionChat/Resources/HellionStrings.ko.resx | 3 ++ HellionChat/Resources/HellionStrings.nb.resx | 3 ++ HellionChat/Resources/HellionStrings.nl.resx | 3 ++ HellionChat/Resources/HellionStrings.pl.resx | 3 ++ .../Resources/HellionStrings.pt-BR.resx | 3 ++ .../Resources/HellionStrings.pt-PT.resx | 3 ++ HellionChat/Resources/HellionStrings.resx | 3 ++ HellionChat/Resources/HellionStrings.ro.resx | 3 ++ HellionChat/Resources/HellionStrings.ru.resx | 3 ++ HellionChat/Resources/HellionStrings.sv.resx | 3 ++ HellionChat/Resources/HellionStrings.tr.resx | 3 ++ HellionChat/Resources/HellionStrings.uk.resx | 3 ++ .../Resources/HellionStrings.zh-Hans.resx | 3 ++ .../Resources/HellionStrings.zh-Hant.resx | 3 ++ .../Ui/StyleEngine/Widgets/ChannelHeader.cs | 18 ++++++++++ .../Widgets/ChannelHeaderDetail.cs | 34 +++++++++++++++++++ HellionChat/Ui/Windows/ChannelPopoutWindow.cs | 30 +++++++++------- HellionChat/Ui/Windows/MainWindow.cs | 20 +++++++++++ 30 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 HellionChat/Ui/StyleEngine/Widgets/ChannelHeaderDetail.cs diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index d1682c0..f72737d 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -388,6 +388,7 @@ internal class HellionStrings internal static string StatusBar_MessagesThousands => Get(nameof(StatusBar_MessagesThousands)); internal static string Settings_Preview_TitleMock => Get(nameof(Settings_Preview_TitleMock)); internal static string Settings_Preview_StatusOpen => Get(nameof(Settings_Preview_StatusOpen)); + internal static string ChannelHeader_NotLoggedIn => Get(nameof(ChannelHeader_NotLoggedIn)); internal static string Settings_Section_Links => Get(nameof(Settings_Section_Links)); internal static string Settings_Section_Behaviour => Get(nameof(Settings_Section_Behaviour)); internal static string Settings_Section_Keybinds => Get(nameof(Settings_Section_Keybinds)); diff --git a/HellionChat/Resources/HellionStrings.ca.resx b/HellionChat/Resources/HellionStrings.ca.resx index d2d0148..1cedf9c 100644 --- a/HellionChat/Resources/HellionStrings.ca.resx +++ b/HellionChat/Resources/HellionStrings.ca.resx @@ -1358,4 +1358,7 @@ obert + + Sense sessió + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.cs.resx b/HellionChat/Resources/HellionStrings.cs.resx index ea554e1..ca16c67 100644 --- a/HellionChat/Resources/HellionStrings.cs.resx +++ b/HellionChat/Resources/HellionStrings.cs.resx @@ -1357,4 +1357,7 @@ otevřeno + + Nepřihlášen + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.da.resx b/HellionChat/Resources/HellionStrings.da.resx index 29802a1..b0c3a86 100644 --- a/HellionChat/Resources/HellionStrings.da.resx +++ b/HellionChat/Resources/HellionStrings.da.resx @@ -1357,4 +1357,7 @@ åben + + Ikke logget ind + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx index de48bc0..d4a2e8c 100644 --- a/HellionChat/Resources/HellionStrings.de.resx +++ b/HellionChat/Resources/HellionStrings.de.resx @@ -1352,4 +1352,7 @@ offen + + Nicht eingeloggt + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.el.resx b/HellionChat/Resources/HellionStrings.el.resx index abfe8ef..7aad3ea 100644 --- a/HellionChat/Resources/HellionStrings.el.resx +++ b/HellionChat/Resources/HellionStrings.el.resx @@ -1357,4 +1357,7 @@ ανοιχτό + + Εκτός σύνδεσης + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.es.resx b/HellionChat/Resources/HellionStrings.es.resx index dfb57c8..1a867ff 100644 --- a/HellionChat/Resources/HellionStrings.es.resx +++ b/HellionChat/Resources/HellionStrings.es.resx @@ -1358,4 +1358,7 @@ abierto + + Sin sesión + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.fi.resx b/HellionChat/Resources/HellionStrings.fi.resx index 2bdefc6..fc6cbc9 100644 --- a/HellionChat/Resources/HellionStrings.fi.resx +++ b/HellionChat/Resources/HellionStrings.fi.resx @@ -1357,4 +1357,7 @@ auki + + Ei kirjautunut sisään + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.fr.resx b/HellionChat/Resources/HellionStrings.fr.resx index 956f1e5..c1679c2 100644 --- a/HellionChat/Resources/HellionStrings.fr.resx +++ b/HellionChat/Resources/HellionStrings.fr.resx @@ -1358,4 +1358,7 @@ ouvert + + Non connecté + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.hu.resx b/HellionChat/Resources/HellionStrings.hu.resx index f47efee..edcab59 100644 --- a/HellionChat/Resources/HellionStrings.hu.resx +++ b/HellionChat/Resources/HellionStrings.hu.resx @@ -1357,4 +1357,7 @@ nyitva + + Nincs bejelentkezve + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.it.resx b/HellionChat/Resources/HellionStrings.it.resx index 7a372ef..f821293 100644 --- a/HellionChat/Resources/HellionStrings.it.resx +++ b/HellionChat/Resources/HellionStrings.it.resx @@ -1358,4 +1358,7 @@ aperto + + Non connesso + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.ja.resx b/HellionChat/Resources/HellionStrings.ja.resx index a84ed43..67029bb 100644 --- a/HellionChat/Resources/HellionStrings.ja.resx +++ b/HellionChat/Resources/HellionStrings.ja.resx @@ -1358,4 +1358,7 @@ 開いています + + 未ログイン + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.ko.resx b/HellionChat/Resources/HellionStrings.ko.resx index d6e4303..8be4681 100644 --- a/HellionChat/Resources/HellionStrings.ko.resx +++ b/HellionChat/Resources/HellionStrings.ko.resx @@ -1358,4 +1358,7 @@ 열림 + + 로그인하지 않음 + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.nb.resx b/HellionChat/Resources/HellionStrings.nb.resx index d5f6a01..bd4eba9 100644 --- a/HellionChat/Resources/HellionStrings.nb.resx +++ b/HellionChat/Resources/HellionStrings.nb.resx @@ -1357,4 +1357,7 @@ åpen + + Ikke innlogget + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.nl.resx b/HellionChat/Resources/HellionStrings.nl.resx index 08ac71a..a98ec25 100644 --- a/HellionChat/Resources/HellionStrings.nl.resx +++ b/HellionChat/Resources/HellionStrings.nl.resx @@ -1358,4 +1358,7 @@ open + + Niet ingelogd + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.pl.resx b/HellionChat/Resources/HellionStrings.pl.resx index d88313d..1002fb9 100644 --- a/HellionChat/Resources/HellionStrings.pl.resx +++ b/HellionChat/Resources/HellionStrings.pl.resx @@ -1357,4 +1357,7 @@ otwarte + + Niezalogowany + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.pt-BR.resx b/HellionChat/Resources/HellionStrings.pt-BR.resx index b37e345..3c17c35 100644 --- a/HellionChat/Resources/HellionStrings.pt-BR.resx +++ b/HellionChat/Resources/HellionStrings.pt-BR.resx @@ -1358,4 +1358,7 @@ aberto + + Não conectado + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.pt-PT.resx b/HellionChat/Resources/HellionStrings.pt-PT.resx index 1eb606a..708b673 100644 --- a/HellionChat/Resources/HellionStrings.pt-PT.resx +++ b/HellionChat/Resources/HellionStrings.pt-PT.resx @@ -1357,4 +1357,7 @@ aberto + + Sem sessão iniciada + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index d6ebc8a..c0dfed0 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -1369,4 +1369,7 @@ open + + Not logged in + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.ro.resx b/HellionChat/Resources/HellionStrings.ro.resx index d7b6900..adae878 100644 --- a/HellionChat/Resources/HellionStrings.ro.resx +++ b/HellionChat/Resources/HellionStrings.ro.resx @@ -1358,4 +1358,7 @@ deschis + + Neconectat + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.ru.resx b/HellionChat/Resources/HellionStrings.ru.resx index 365e56d..8cb69a7 100644 --- a/HellionChat/Resources/HellionStrings.ru.resx +++ b/HellionChat/Resources/HellionStrings.ru.resx @@ -1358,4 +1358,7 @@ открыт + + Не выполнен вход + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.sv.resx b/HellionChat/Resources/HellionStrings.sv.resx index 276879b..b175ce6 100644 --- a/HellionChat/Resources/HellionStrings.sv.resx +++ b/HellionChat/Resources/HellionStrings.sv.resx @@ -1358,4 +1358,7 @@ öppen + + Inte inloggad + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.tr.resx b/HellionChat/Resources/HellionStrings.tr.resx index 3b310af..b99d3e7 100644 --- a/HellionChat/Resources/HellionStrings.tr.resx +++ b/HellionChat/Resources/HellionStrings.tr.resx @@ -1357,4 +1357,7 @@ açık + + Oturum açılmadı + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.uk.resx b/HellionChat/Resources/HellionStrings.uk.resx index 23e35f6..6942e83 100644 --- a/HellionChat/Resources/HellionStrings.uk.resx +++ b/HellionChat/Resources/HellionStrings.uk.resx @@ -1357,4 +1357,7 @@ відкрито + + Вхід не виконано + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.zh-Hans.resx b/HellionChat/Resources/HellionStrings.zh-Hans.resx index 66299f2..ea43134 100644 --- a/HellionChat/Resources/HellionStrings.zh-Hans.resx +++ b/HellionChat/Resources/HellionStrings.zh-Hans.resx @@ -1358,4 +1358,7 @@ 打开 + + 未登录 + \ No newline at end of file diff --git a/HellionChat/Resources/HellionStrings.zh-Hant.resx b/HellionChat/Resources/HellionStrings.zh-Hant.resx index e000264..d06e35e 100644 --- a/HellionChat/Resources/HellionStrings.zh-Hant.resx +++ b/HellionChat/Resources/HellionStrings.zh-Hant.resx @@ -1358,4 +1358,7 @@ 開啟 + + 未登入 + \ No newline at end of file diff --git a/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs index 53c75df..935be04 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs @@ -131,6 +131,24 @@ internal static class ChannelHeader ImGuiP.ItemSize(new Vector2(width, height - ImGui.GetStyle().ItemSpacing.Y)); } + // Both windows need the same string, and only one of them should know how to + // get at the world. IsValid is the guard the payload handler already uses -- + // HomeWorld is a row reference that stays unresolved until a character is + // actually logged in. + internal static string CurrentDetail() + { + var world = Plugin.PlayerState.HomeWorld.IsValid + ? Plugin.PlayerState.HomeWorld.Value.Name.ExtractText() + : null; + + return ChannelHeaderDetail.Format( + world, + DateTimeOffset.Now, + Plugin.Config.Use24HourClock, + Resources.HellionStrings.ChannelHeader_NotLoggedIn + ); + } + private static float MeasureIcon(FontManager fonts, FontAwesomeIcon icon) { using (fonts.FontAwesome.Push()) diff --git a/HellionChat/Ui/StyleEngine/Widgets/ChannelHeaderDetail.cs b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeaderDetail.cs new file mode 100644 index 0000000..765f1b5 --- /dev/null +++ b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeaderDetail.cs @@ -0,0 +1,34 @@ +using System.Globalization; + +namespace HellionChat.Ui.StyleEngine.Widgets; + +// TEST-MIRROR: Ui/ChannelHeaderDetailTests.cs +// +// The trailing half of the channel header: where you are and what time it is. +// +// The clock follows the same Use24HourClock setting the message timestamps do. +// Two clock formats in one window would be a defect rather than a preference, and +// the header sits directly above a column of timestamps. +// +// Culture is pinned for the same reason it is pinned in the message list: a +// German machine renders "PM" as "nachm." under its own culture, and the two +// would then disagree on the same screen. +internal static class ChannelHeaderDetail +{ + internal const string Separator = " · "; + + internal static string Format( + string? world, + DateTimeOffset now, + bool use24Hour, + string fallback + ) + { + var where = string.IsNullOrWhiteSpace(world) ? fallback : world; + var clock = use24Hour + ? now.ToString("HH:mm", CultureInfo.InvariantCulture) + : now.ToString("h:mm tt", CultureInfo.InvariantCulture); + + return string.Concat(where, Separator, clock); + } +} diff --git a/HellionChat/Ui/Windows/ChannelPopoutWindow.cs b/HellionChat/Ui/Windows/ChannelPopoutWindow.cs index 8ad82cb..d0ffa97 100644 --- a/HellionChat/Ui/Windows/ChannelPopoutWindow.cs +++ b/HellionChat/Ui/Windows/ChannelPopoutWindow.cs @@ -123,14 +123,23 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow if (Bound is null) return; - // No header row at all any more. With the title bar on it repeated the - // tab name one line below itself; with the bar off, hiding it took the - // only way out of the window with it, because the title bar carries no - // close button either -- closing has to go through the pool so the slot - // is released. Pop-in lives in the input row now, where the other window - // actions already are. - if (!Plugin.Config.ShowPopOutTitleBar) - DrawTitle(Bound); + // v1.13.0: the plain title row became the channel header. The warning it + // left behind still holds and is now the rule the header follows -- with + // the title bar on, the window title already carries the tab name, so the + // header drops the name and shows only the world and the clock. + // + // Drawn before the body child on purpose, and the child's height is left + // alone: it is a negative value, which ImGui resolves against the space + // still available from the current cursor, and the header has already + // taken its share. + StyleEngine.Widgets.ChannelHeader.Draw( + Bound, + Plugin.Config.ShowPopOutTitleBar + ? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly + : StyleEngine.Widgets.ChannelHeaderMode.Full, + Plugin.Instance.FontManager, + StyleEngine.Widgets.ChannelHeader.CurrentDetail() + ); // The header close button can unbind us mid-frame (CloseRequested -> // pool.TryClose -> Unbind nulls Bound). Re-check before the body so we @@ -165,9 +174,4 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow } // Name only. Shown when the window has no title bar to carry it. - private void DrawTitle(Tab tab) - { - ImGui.TextUnformatted(tab.Name); - ImGui.Separator(); - } } diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index 9684f62..909579f 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -388,6 +388,26 @@ internal sealed class MainWindow : Window, IFocusableChatWindow ? Plugin.InputPreview.PreviewHeight : 0f; + // Before the child, so it does not scroll away with the log. The child's + // height is left alone on purpose: it is given as a negative value, and + // ImGui resolves those against the space still available from the current + // cursor -- which the header has already reduced. Subtracting it a second + // time would open a gap of exactly the header's height above the input row. + if (_activeTab is { } headerTab) + { + var mode = + Plugin.Config.MainWindowLayoutMode == MainWindowLayoutMode.TopTabs + ? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly + : StyleEngine.Widgets.ChannelHeaderMode.Full; + + StyleEngine.Widgets.ChannelHeader.Draw( + headerTab, + mode, + Plugin.Instance.FontManager, + StyleEngine.Widgets.ChannelHeader.CurrentDetail() + ); + } + using ( var messages = ImRaii.Child( "##hellion-main-area",