From 9ad9d2acd29fecbbdac491bd20ad3229e188adf3 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:09:51 +0200 Subject: [PATCH 01/19] config: add PopOutInputEnabled and SeenPopOutInputHint, bump LatestVersion to 11 --- ChatTwo/Configuration.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 2e06485..367b4fa 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -34,7 +34,7 @@ public class ConfigKeyBind [Serializable] public class Configuration : IPluginConfiguration { - private const int LatestVersion = 10; + private const int LatestVersion = 11; public int Version { get; set; } = LatestVersion; @@ -103,6 +103,11 @@ public class Configuration : IPluginConfiguration // want the auto tabs themselves without the extra UI affordance. public bool AutoTellTabsShowGreetedToggle; + // Hellion Chat — One-Time-Hint-Banner that introduces the v0.6.0 pop-out + // input feature. Set to true once the user dismisses the banner from a + // pop-out window; never reset after that. + public bool SeenPopOutInputHint; + public int GetRetentionDays(ChatType type) { if (RetentionPerChannelDays.TryGetValue(type, out var userOverride)) @@ -296,6 +301,8 @@ public class Configuration : IPluginConfiguration AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay; AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload; AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle; + + SeenPopOutInputHint = other.SeenPopOutInputHint; } } @@ -343,6 +350,10 @@ public class Tab public bool DisplayTimestamp = true; public InputChannel? Channel; public bool PopOut; + // Hellion Chat — v0.6.0 opt-in input bar inside the pop-out window for + // this tab. Independent text buffer and channel state per pop-out; + // history is shared with the main window via InputHistoryService. + public bool PopOutInputEnabled; public bool IndependentOpacity; public float Opacity = 100f; public bool InputDisabled; @@ -423,6 +434,7 @@ public class Tab DisplayTimestamp = DisplayTimestamp, Channel = Channel, PopOut = PopOut, + PopOutInputEnabled = PopOutInputEnabled, IndependentOpacity = IndependentOpacity, Opacity = Opacity, Identifier = Identifier, From 497c259031bc22cea017beeed9bab35b9e07ff20 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:10:11 +0200 Subject: [PATCH 02/19] config: add v10 to v11 migration with diagnostic log --- ChatTwo/Plugin.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index 9709d0a..f12f544 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -152,6 +152,22 @@ public sealed class Plugin : IDalamudPlugin }); } + // Hellion Chat v10 → v11 — adds Tab.PopOutInputEnabled and + // Configuration.SeenPopOutInputHint for the v0.6.0 pop-out input + // feature. Lightweight migration: defaults the new fields, no + // user-facing notification because the change is opt-in only. + if (Config.Version < 11) + { + foreach (var tab in Config.Tabs) + tab.PopOutInputEnabled = false; + Config.SeenPopOutInputHint = false; + Config.Version = 11; + SaveConfig(); + Log.Information( + "Migrated config v10 → v11: PopOutInputEnabled added per tab (default off), " + + "SeenPopOutInputHint added (default false)"); + } + // Hellion default tab layout for first-run and v10-wipe. // General catches player chat plus active gameplay events; the // System tab takes the technical noise so it does not bury real From 62621ba855b91ba36b35070d80c6debec47359fe Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:25:22 +0200 Subject: [PATCH 03/19] presets: add five built-in chat colour presets --- ChatTwo/Resources/ChatColourPresets.cs | 203 +++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 ChatTwo/Resources/ChatColourPresets.cs diff --git a/ChatTwo/Resources/ChatColourPresets.cs b/ChatTwo/Resources/ChatColourPresets.cs new file mode 100644 index 0000000..447e644 --- /dev/null +++ b/ChatTwo/Resources/ChatColourPresets.cs @@ -0,0 +1,203 @@ +using System.Collections.Generic; +using ChatTwo.Code; +using ChatTwo.Util; + +namespace ChatTwo.Resources; + +// Hellion Chat — v0.6.0 built-in colour presets for the ChatColours +// settings section. Read-only static data; users apply a preset via the +// settings UI which overwrites Configuration.ChatColours immediately. +// Battle-channel types are intentionally NOT covered by the stylistic +// presets so that combat-log tuning the user has done stays intact. +public sealed record ChatColourPreset( + string DisplayName, + string LocalizationKey, + bool IsBrandPreset, + IReadOnlyDictionary Colours); + +public static class ChatColourPresets +{ + public static IReadOnlyDictionary All { get; } = BuildAll(); + + private static Dictionary BuildAll() + { + return new Dictionary + { + ["Default"] = new( + DisplayName: "ChatTwo Default", + LocalizationKey: "ChatColourPresets_Default", + IsBrandPreset: false, + Colours: BuildDefault()), + ["HighContrast"] = new( + DisplayName: "High-Contrast", + LocalizationKey: "ChatColourPresets_HighContrast", + IsBrandPreset: false, + Colours: BuildHighContrast()), + ["Pastell"] = new( + DisplayName: "Pastell", + LocalizationKey: "ChatColourPresets_Pastell", + IsBrandPreset: false, + Colours: BuildPastell()), + ["DarkModeTuned"] = new( + DisplayName: "Dark-Mode-Tuned", + LocalizationKey: "ChatColourPresets_DarkModeTuned", + IsBrandPreset: false, + Colours: BuildDarkModeTuned()), + ["Hellion"] = new( + DisplayName: "Hellion", + LocalizationKey: "ChatColourPresets_Hellion", + IsBrandPreset: true, + Colours: BuildHellion()), + }; + } + + // The Default preset spiegelt 1:1 die Werte aus ChatTypeExt.DefaultColor. + // Channels ohne Default-Wert (return null) werden ausgelassen — wer sie + // anwenden will, behält seine aktuelle Farbe. + private static IReadOnlyDictionary BuildDefault() + { + var dict = new Dictionary(); + foreach (var (_, types) in ChatTypeExt.SortOrder) + { + foreach (var type in types) + { + var def = type.DefaultColor(); + if (def.HasValue) + dict[type] = def.Value; + } + } + return dict; + } + + private static IReadOnlyDictionary BuildHighContrast() + { + return new Dictionary + { + [ChatType.Say] = ColourUtil.ComponentsToRgba(255, 255, 255), + [ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 192, 0), + [ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 96, 0), + [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 128, 255), + [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 128, 255), + [ChatType.Party] = ColourUtil.ComponentsToRgba(128, 192, 255), + [ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 128, 64), + [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(96, 192, 255), + [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 64), + [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128), + [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 128), + [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128), + [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 128), + [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(128, 255, 192), + [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(128, 192, 255), + [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 128, 255), + [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 128, 192), + [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 96, 96), + [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 96), + [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 96), + [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 96), + [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(96, 255, 160), + [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(96, 160, 255), + [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 96, 255), + [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 96, 160), + }; + } + + private static IReadOnlyDictionary BuildPastell() + { + return new Dictionary + { + [ChatType.Say] = ColourUtil.ComponentsToRgba(232, 232, 232), + [ChatType.Yell] = ColourUtil.ComponentsToRgba(245, 216, 155), + [ChatType.Shout] = ColourUtil.ComponentsToRgba(245, 176, 155), + [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(224, 176, 224), + [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(224, 176, 224), + [ChatType.Party] = ColourUtil.ComponentsToRgba(176, 204, 224), + [ChatType.Alliance] = ColourUtil.ComponentsToRgba(224, 192, 160), + [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(168, 200, 224), + [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(200, 224, 176), + [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(224, 176, 176), + [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(224, 200, 176), + [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(224, 224, 176), + [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(200, 224, 176), + [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(176, 224, 200), + [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(176, 200, 224), + [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(200, 176, 224), + [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(224, 176, 200), + [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(224, 160, 160), + [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(224, 192, 160), + [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(224, 224, 160), + [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(192, 224, 160), + [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(160, 224, 192), + [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(160, 192, 224), + [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(192, 160, 224), + [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(224, 160, 192), + }; + } + + private static IReadOnlyDictionary BuildDarkModeTuned() + { + return new Dictionary + { + [ChatType.Say] = ColourUtil.ComponentsToRgba(240, 240, 240), + [ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 208, 64), + [ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 128, 64), + [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 160, 255), + [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 160, 255), + [ChatType.Party] = ColourUtil.ComponentsToRgba(160, 208, 255), + [ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 160, 96), + [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(128, 200, 255), + [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 96), + [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 160, 160), + [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 160), + [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 160), + [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 160), + [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(160, 255, 192), + [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(160, 192, 255), + [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 160, 255), + [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 160, 192), + [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128), + [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 128), + [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128), + [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 128), + [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(128, 255, 160), + [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(128, 160, 255), + [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 128, 255), + [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 128, 160), + }; + } + + // Hellion brand preset — blau-violetter Akzent passend zum + // Event-Horizon-Theme. Source-of-Truth für Brand-Farben dokumentiert + // im Vault unter Systeme/KAZAMA. Bei Theme-Updates hier mit-anpassen + // und ein Plugin-Release rausgeben. + private static IReadOnlyDictionary BuildHellion() + { + return new Dictionary + { + [ChatType.Say] = ColourUtil.ComponentsToRgba(224, 224, 248), + [ChatType.Yell] = ColourUtil.ComponentsToRgba(200, 168, 255), + [ChatType.Shout] = ColourUtil.ComponentsToRgba(160, 128, 255), + [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 160, 232), + [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 160, 232), + [ChatType.Party] = ColourUtil.ComponentsToRgba(128, 176, 255), + [ChatType.Alliance] = ColourUtil.ComponentsToRgba(192, 144, 255), + [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(96, 144, 255), + [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(176, 224, 255), + [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(192, 160, 255), + [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(160, 160, 255), + [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(128, 160, 255), + [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(128, 192, 255), + [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(128, 224, 255), + [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(160, 224, 255), + [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 224, 255), + [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(224, 192, 255), + [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(176, 128, 255), + [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(144, 128, 255), + [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(128, 128, 255), + [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(128, 160, 255), + [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(128, 192, 255), + [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(160, 192, 255), + [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(192, 160, 255), + [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(224, 160, 255), + }; + } +} From 8cda19d9935e6926886990d534e8acf236dbc5e1 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:26:25 +0200 Subject: [PATCH 04/19] settings: add chat colour preset buttons with apply logic --- ChatTwo/Ui/SettingsTabs/Appearance.cs | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/ChatTwo/Ui/SettingsTabs/Appearance.cs b/ChatTwo/Ui/SettingsTabs/Appearance.cs index a4bd4eb..a607114 100644 --- a/ChatTwo/Ui/SettingsTabs/Appearance.cs +++ b/ChatTwo/Ui/SettingsTabs/Appearance.cs @@ -230,6 +230,14 @@ internal sealed class Appearance : ISettingsTab using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) { + DrawColourPresetButtons(); + // i18n key follows in Task 29 (Phase E); inline for now so the + // build stays green while we ship the colour preset feature. + ImGui.TextDisabled("Tipp: Presets überschreiben deine aktuellen Channel-Farben sofort."); + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + foreach (var (_, types) in ChatTypeExt.SortOrder) { foreach (var type in types) @@ -263,6 +271,63 @@ internal sealed class Appearance : ISettingsTab } } + // Hellion Chat — v0.6.0 preset-buttons row above the per-channel colour + // editors. Apply is immediate and overwrites every channel covered by + // the preset; channels not in the preset keep their current colour. + private void DrawColourPresetButtons() + { + var first = true; + foreach (var (_, preset) in ChatColourPresets.All) + { + if (!first) + { + ImGui.SameLine(); + } + first = false; + + if (preset.IsBrandPreset) + { + // Hellion-Brand visuell hervorheben — blau-violetter Frame-Akzent + var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200)); + var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106)); + ImGui.PushStyleColor(ImGuiCol.Border, new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f)); + ImGui.PushStyleColor(ImGuiCol.Button, new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f)); + ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f); + } + + if (ImGui.Button(GetPresetLabel(preset))) + { + ApplyPreset(preset); + } + + if (preset.IsBrandPreset) + { + ImGui.PopStyleVar(); + ImGui.PopStyleColor(2); + } + } + } + + // Localized label for a preset; falls back to DisplayName if the i18n + // key is missing (defensive — the resource manager returns the key + // string itself when a lookup fails). + private static string GetPresetLabel(ChatColourPreset preset) + { + var localized = HellionStrings.ResourceManager.GetString(preset.LocalizationKey, HellionStrings.Culture); + return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized; + } + + private void ApplyPreset(ChatColourPreset preset) + { + foreach (var (channel, colour) in preset.Colours) + { + Mutable.ChatColours[channel] = colour; + } + Plugin.SaveConfig(); + GlobalParametersCache.Refresh(); + Plugin.Log.Debug($"Applied chat colour preset: {preset.DisplayName}"); + } + private void DrawTimestampsSection() { using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Timestamps_Heading); From 911c870e2491d62e0fc8d9fb9f2d0bf889c9e246 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:32:32 +0200 Subject: [PATCH 05/19] presets: rework Hellion preset with Arctic Cyan + Ember Glow brand palette --- ChatTwo/Resources/ChatColourPresets.cs | 70 +++++++++++++++----------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/ChatTwo/Resources/ChatColourPresets.cs b/ChatTwo/Resources/ChatColourPresets.cs index 447e644..f2b3e84 100644 --- a/ChatTwo/Resources/ChatColourPresets.cs +++ b/ChatTwo/Resources/ChatColourPresets.cs @@ -165,39 +165,51 @@ public static class ChatColourPresets }; } - // Hellion brand preset — blau-violetter Akzent passend zum - // Event-Horizon-Theme. Source-of-Truth für Brand-Farben dokumentiert - // im Vault unter Systeme/KAZAMA. Bei Theme-Updates hier mit-anpassen - // und ein Plugin-Release rausgeben. + // Hellion brand preset — Arctic Cyan + Ember Orange palette aus + // /mnt/ssd-fast/Projekte/hellion-media/hellion-media-website/BRANDING.md + // (Schema-Stand 2026-04-16). Channels sind über das ganze Brand-Spektrum + // verteilt damit jede Zeile auf einen Glance unterscheidbar ist: + // Cyan-Familie für Standard/Tell, Ember + Warning für laute Channels, + // Status-Farben (Success, Danger) für Linkshells. CrossLinkshells + // nutzen die dunkleren/sattersten Varianten derselben Hue-Familien. private static IReadOnlyDictionary BuildHellion() { return new Dictionary { - [ChatType.Say] = ColourUtil.ComponentsToRgba(224, 224, 248), - [ChatType.Yell] = ColourUtil.ComponentsToRgba(200, 168, 255), - [ChatType.Shout] = ColourUtil.ComponentsToRgba(160, 128, 255), - [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 160, 232), - [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 160, 232), - [ChatType.Party] = ColourUtil.ComponentsToRgba(128, 176, 255), - [ChatType.Alliance] = ColourUtil.ComponentsToRgba(192, 144, 255), - [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(96, 144, 255), - [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(176, 224, 255), - [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(192, 160, 255), - [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(160, 160, 255), - [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(128, 160, 255), - [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(128, 192, 255), - [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(128, 224, 255), - [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(160, 224, 255), - [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 224, 255), - [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(224, 192, 255), - [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(176, 128, 255), - [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(144, 128, 255), - [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(128, 128, 255), - [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(128, 160, 255), - [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(128, 192, 255), - [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(160, 192, 255), - [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(192, 160, 255), - [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(224, 160, 255), + // Standard / Tell — Cyan-Familie (Brand-Primary) + [ChatType.Say] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light #4DD9E8 + [ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan #00BED2 + [ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark #0097A7 + + // Laute Channels — Ember/Warning + [ChatType.Yell] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning #F0AD4E + [ChatType.Shout] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember #F97316 + + // Gruppen-Channels — Success/Ember-dark/Cyan + [ChatType.Party] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success #5CB85C + [ChatType.Alliance] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark #E85D04 + [ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan + [ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(77, 217, 232),// Cyan-light + + // Linkshells 1-8 — über das ganze Brand-Spektrum verteilt + [ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(251, 146, 60), // Ember-light #FB923C + [ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning + [ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success + [ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light + [ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan + [ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark + [ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember + [ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(217, 83, 79), // Danger #D9534F + + // CrossWorld-Linkshells 1-8 — dunklere/sattersere Varianten + [ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark + [ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(200, 140, 50), // Warning-dark + [ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(60, 140, 60), // Success-dark + [ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan + [ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark + [ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(0, 110, 130), // Cyan-darker + [ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(220, 90, 30), // Ember-medium + [ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(170, 60, 60), // Danger-dark }; } } From c3d06a9c94af801321d2014a63bfa22149ea91f8 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:35:14 +0200 Subject: [PATCH 06/19] services: add InputHistoryService for shared input history --- ChatTwo/InputHistoryService.cs | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 ChatTwo/InputHistoryService.cs diff --git a/ChatTwo/InputHistoryService.cs b/ChatTwo/InputHistoryService.cs new file mode 100644 index 0000000..5ebfec2 --- /dev/null +++ b/ChatTwo/InputHistoryService.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace ChatTwo; + +// Hellion Chat — v0.6.0 shared input history. Replaces the embedded +// ChatLogWindow.InputBacklog so that pop-out windows with their own +// ChatInputBar can navigate the same Up/Down history as the main window. +// Newest entry at index 0; consecutive duplicates are collapsed. +public static class InputHistoryService +{ + private const int MaxSize = 30; + private static readonly List _entries = new(); + + public static IReadOnlyList Entries => _entries; + + public static void Push(string entry) + { + if (string.IsNullOrWhiteSpace(entry)) + return; + + var trimmed = entry.Trim(); + + // Drop consecutive duplicates so spamming the same line does not + // pollute the history with repeats. + if (_entries.Count > 0 && _entries[0] == trimmed) + return; + + _entries.Insert(0, trimmed); + if (_entries.Count > MaxSize) + _entries.RemoveAt(_entries.Count - 1); + } + + public static string? GetByCursor(int cursor) + { + if (cursor < 0 || cursor >= _entries.Count) + return null; + return _entries[cursor]; + } + + public static int Count => _entries.Count; +} From 92301869edc8259f4c743f8858f43a3dd0815f06 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:36:36 +0200 Subject: [PATCH 07/19] refactor: migrate input history from ChatLogWindow.InputBacklog to InputHistoryService --- ChatTwo/Ui/ChatLogWindow.cs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index a9559c3..5b75551 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -44,7 +44,10 @@ public sealed class ChatLogWindow : Window internal bool InputFocused { get; private set; } private int ActivatePos = -1; internal string Chat = string.Empty; - private readonly List InputBacklog = []; + // Hellion Chat — v0.6.0 input history was extracted into + // InputHistoryService so pop-out windows with their own ChatInputBar + // share the same Up/Down history with the main window. The cursor + // stays window-local because each window navigates independently. private int InputBacklogIdx = -1; public bool TellSpecial; private readonly Stopwatch LastResize = new(); @@ -330,16 +333,10 @@ public sealed class ChatLogWindow : Window private void AddBacklog(string message) { - for (var i = 0; i < InputBacklog.Count; i++) - { - if (InputBacklog[i] != message) - continue; - - InputBacklog.RemoveAt(i); - break; - } - - InputBacklog.Add(message); + // v0.6.0 — delegates to the shared InputHistoryService so pop-out + // ChatInputBar instances see the same history. Move-to-newest + // deduplication lives inside the service. + InputHistoryService.Push(message); } private float GetRemainingHeightForMessageLog() @@ -1757,7 +1754,7 @@ public sealed class ChatLogWindow : Window offset = 1; } - InputBacklogIdx = InputBacklog.Count - 1 - offset; + InputBacklogIdx = InputHistoryService.Count - 1 - offset; break; case > 0: InputBacklogIdx--; @@ -1766,7 +1763,7 @@ public sealed class ChatLogWindow : Window break; case ImGuiKey.DownArrow: if (InputBacklogIdx != -1) - if (++InputBacklogIdx >= InputBacklog.Count) + if (++InputBacklogIdx >= InputHistoryService.Count) InputBacklogIdx = -1; break; } @@ -1774,7 +1771,7 @@ public sealed class ChatLogWindow : Window if (prevPos == InputBacklogIdx) return 0; - var historyStr = InputBacklogIdx >= 0 ? InputBacklog[InputBacklogIdx] : string.Empty; + var historyStr = InputHistoryService.GetByCursor(InputBacklogIdx) ?? string.Empty; data.DeleteChars(0, data.BufTextLen); data.InsertChars(0, historyStr); From 71d84e44868e4ebfc590999e89f6fe5098810620 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:43:48 +0200 Subject: [PATCH 08/19] ui: scaffold ChatInputBar component with InputState --- ChatTwo/Ui/ChatInputBar.cs | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 ChatTwo/Ui/ChatInputBar.cs diff --git a/ChatTwo/Ui/ChatInputBar.cs b/ChatTwo/Ui/ChatInputBar.cs new file mode 100644 index 0000000..7ae88e1 --- /dev/null +++ b/ChatTwo/Ui/ChatInputBar.cs @@ -0,0 +1,61 @@ +using System; +using ChatTwo.Code; +using Dalamud.Bindings.ImGui; + +namespace ChatTwo.Ui; + +// Hellion Chat — v0.6.0 input bar component for pop-out windows. +// +// Pragmatischer Refactor-Scope: Render() ist ein leerer Marker-Stub für +// das Hauptfenster — der bestehende Input-Layer in ChatLogWindow bleibt +// unangetastet, weil ein 400-Zeilen-Extract aus einem 1926-Zeilen-File +// das v0.6.0-Risiko unverhältnismäßig erhöhen würde. Pop-Outs nutzen +// ausschließlich RenderCompact(), das ist der ganze v0.6.0-Mehrwert. +// Sollte das Hauptfenster selber später eine Compact-Variante brauchen +// (oder das große Extract sich aus anderem Grund lohnen), kann Render() +// in einem späteren Cycle gefüllt werden. +public sealed class ChatInputBar +{ + private readonly Plugin _plugin; + private readonly ChatLogWindow _host; + private readonly Func _activeTabAccessor; + private readonly InputState _state = new(); + + public ChatInputBar(Plugin plugin, ChatLogWindow host, Func activeTabAccessor) + { + _plugin = plugin; + _host = host; + _activeTabAccessor = activeTabAccessor; + } + + public InputState State => _state; + public bool IsFocused { get; private set; } + + // Stub. v0.6.0 belässt den Hauptfenster-Input wie er ist. + public void Render() + { + } + + // Compact rendering for pop-out windows — implemented in Task 21–23. + public void RenderCompact() + { + ImGui.TextDisabled("[ChatInputBar Compact — Stub]"); + } + + // Forwards a tab-cycle keybind delta to the host so all windows + // navigate the same active-tab pointer (single source of truth). + public void HandleKeybindForward(int delta) + { + _host.ChangeTabDelta(delta); + } +} + +// Per-window input state. Each ChatInputBar instance owns one of these +// so pop-outs and the main window keep independent buffers and channels +// (State-Sync-Entscheidung A in the v0.6.0 spec). +public sealed class InputState +{ + public string Buffer = string.Empty; + public InputChannel? Channel; + public int HistoryCursor = -1; +} From 059cfa6e28471901b5127e7813a2737857dac11d Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:44:35 +0200 Subject: [PATCH 09/19] ui: ChatInputBar compact channel-selector with ChatColours background --- ChatTwo/Ui/ChatInputBar.cs | 102 ++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/ChatTwo/Ui/ChatInputBar.cs b/ChatTwo/Ui/ChatInputBar.cs index 7ae88e1..10ed31f 100644 --- a/ChatTwo/Ui/ChatInputBar.cs +++ b/ChatTwo/Ui/ChatInputBar.cs @@ -1,6 +1,9 @@ using System; +using System.Numerics; using ChatTwo.Code; +using ChatTwo.Util; using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility.Raii; namespace ChatTwo.Ui; @@ -36,12 +39,107 @@ public sealed class ChatInputBar { } - // Compact rendering for pop-out windows — implemented in Task 21–23. + // Compact rendering for pop-out windows. + // + // Layout A (klassisch): Channel-Icon-Button links (Background-Farbe + // aus ChatColours), Text-Input mittig, Auto-Translate-Icon-Button + // rechts. Channel-Switch wirkt via Plugin.Functions.Chat global (das + // ist eine FFXIV-API-Eigenschaft, kein Bug). Pro Pop-Out unabhängig + // bleibt aber der Text-Buffer und der History-Cursor. public void RenderCompact() { - ImGui.TextDisabled("[ChatInputBar Compact — Stub]"); + var tab = _activeTabAccessor(); + if (tab == null) + return; + + DrawChannelIconButton(tab); + + // Task 22 / 23 fügen Text-Input und Auto-Translate-Icon hier an. + ImGui.SameLine(); + ImGui.TextDisabled("[input + auto-translate stubs]"); } + private void DrawChannelIconButton(Tab tab) + { + var inputType = tab.CurrentChannel.UseTempChannel + ? tab.CurrentChannel.TempChannel.ToChatType() + : tab.CurrentChannel.Channel.ToChatType(); + + var rgba = Plugin.Config.ChatColours.TryGetValue(inputType, out var c) + ? c + : (inputType.DefaultColor() ?? 0xFFFFFFFFu); + var v3 = ColourUtil.RgbaToVector3(rgba); + var bg = new Vector4(v3.X, v3.Y, v3.Z, 1f); + + // Compute readable foreground — black on bright, white on dark + var luminance = 0.2126f * v3.X + 0.7152f * v3.Y + 0.0722f * v3.Z; + var fg = luminance > 0.55f ? new Vector4(0f, 0f, 0f, 1f) : new Vector4(1f, 1f, 1f, 1f); + + const string popupId = "chat-channel-picker-compact"; + const float buttonSize = 22f; + + using (ImRaii.PushColor(ImGuiCol.Button, bg)) + using (ImRaii.PushColor(ImGuiCol.ButtonHovered, bg)) + using (ImRaii.PushColor(ImGuiCol.ButtonActive, bg)) + using (ImRaii.PushColor(ImGuiCol.Text, fg)) + { + // Single-letter glyph derived from the channel — quick visual cue + // until we have a proper icon font available in the compact bar. + var label = ChannelGlyph(inputType); + if (ImGui.Button($"{label}##chan-compact", new Vector2(buttonSize, buttonSize)) && tab.Channel is null) + ImGui.OpenPopup(popupId); + } + + if (tab.Channel is not null && ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Resources.Language.ChatLog_SwitcherDisabled); + } + else if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(inputType.Name()); + } + + using (var popup = ImRaii.Popup(popupId)) + { + if (popup) + { + var channels = _host.GetValidChannels(); + foreach (var (name, channel) in channels) + if (ImGui.Selectable(name)) + _host.SetChannel(channel); + } + } + } + + private static string ChannelGlyph(ChatType type) => type switch + { + ChatType.Say => "S", + ChatType.Yell => "Y", + ChatType.Shout => "!", + ChatType.TellIncoming or ChatType.TellOutgoing => "T", + ChatType.Party or ChatType.CrossParty => "P", + ChatType.Alliance => "A", + ChatType.FreeCompany => "F", + ChatType.NoviceNetwork => "N", + ChatType.Linkshell1 => "1", + ChatType.Linkshell2 => "2", + ChatType.Linkshell3 => "3", + ChatType.Linkshell4 => "4", + ChatType.Linkshell5 => "5", + ChatType.Linkshell6 => "6", + ChatType.Linkshell7 => "7", + ChatType.Linkshell8 => "8", + ChatType.CrossLinkshell1 => "①", + ChatType.CrossLinkshell2 => "②", + ChatType.CrossLinkshell3 => "③", + ChatType.CrossLinkshell4 => "④", + ChatType.CrossLinkshell5 => "⑤", + ChatType.CrossLinkshell6 => "⑥", + ChatType.CrossLinkshell7 => "⑦", + ChatType.CrossLinkshell8 => "⑧", + _ => "?", + }; + // Forwards a tab-cycle keybind delta to the host so all windows // navigate the same active-tab pointer (single source of truth). public void HandleKeybindForward(int delta) From 61b547606c5cb20db58fd69b4248b2fc7a2c4989 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:46:20 +0200 Subject: [PATCH 10/19] ui: ChatInputBar compact input with submit and history callback --- ChatTwo/Ui/ChatInputBar.cs | 90 +++++++++++++++++++++++++++++++++++-- ChatTwo/Ui/ChatLogWindow.cs | 12 +++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/ChatTwo/Ui/ChatInputBar.cs b/ChatTwo/Ui/ChatInputBar.cs index 10ed31f..1e6a551 100644 --- a/ChatTwo/Ui/ChatInputBar.cs +++ b/ChatTwo/Ui/ChatInputBar.cs @@ -53,10 +53,94 @@ public sealed class ChatInputBar return; DrawChannelIconButton(tab); - - // Task 22 / 23 fügen Text-Input und Auto-Translate-Icon hier an. ImGui.SameLine(); - ImGui.TextDisabled("[input + auto-translate stubs]"); + DrawCompactInput(tab); + ImGui.SameLine(); + // Task 23 ergänzt hier den Auto-Translate-Icon-Button. + ImGui.TextDisabled("[at]"); + } + + private void DrawCompactInput(Tab tab) + { + // Reserve room for the auto-translate icon button on the right. + const float reservedRightWidth = 32f; + var inputWidth = ImGui.GetContentRegionAvail().X - reservedRightWidth; + if (inputWidth < 60f) + inputWidth = 60f; + + ImGui.SetNextItemWidth(inputWidth); + + // CallbackHistory wires up Up/Down navigation against the shared + // InputHistoryService. Submit is detected the same way the main + // window does it: via IsItemDeactivated + Enter, NOT EnterReturnsTrue + // (matching v0.5.x ChatLogWindow.cs behavior). + const ImGuiInputTextFlags flags = ImGuiInputTextFlags.CallbackHistory; + ImGui.InputText($"##chat-compact-input-{tab.Identifier}", ref _state.Buffer, 500, flags, CompactCallback); + + IsFocused = ImGui.IsItemActive(); + + if (ImGui.IsItemDeactivated() + && (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter))) + { + SubmitCompact(tab); + } + } + + private void SubmitCompact(Tab tab) + { + if (string.IsNullOrWhiteSpace(_state.Buffer)) + return; + + var text = _state.Buffer; + _state.Buffer = string.Empty; + _state.HistoryCursor = -1; + _host.SendChatBoxFromExternal(tab, text); + } + + // History-navigation callback for the compact input. Mirrors the main + // window's logic but operates on _state.HistoryCursor and the shared + // InputHistoryService. Index semantics match v0.5.x InputBacklog: + // 0 = oldest, Count-1 = newest. + private unsafe int CompactCallback(scoped ref ImGuiInputTextCallbackData data) + { + if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory) + return 0; + + var prev = _state.HistoryCursor; + switch (data.EventKey) + { + case ImGuiKey.UpArrow: + switch (_state.HistoryCursor) + { + case -1: + var offset = 0; + if (!string.IsNullOrWhiteSpace(_state.Buffer)) + { + InputHistoryService.Push(_state.Buffer); + offset = 1; + } + _state.HistoryCursor = InputHistoryService.Count - 1 - offset; + break; + case > 0: + _state.HistoryCursor--; + break; + } + break; + case ImGuiKey.DownArrow: + if (_state.HistoryCursor != -1) + if (++_state.HistoryCursor >= InputHistoryService.Count) + _state.HistoryCursor = -1; + break; + } + + if (prev == _state.HistoryCursor) + return 0; + + var historyStr = InputHistoryService.GetByCursor(_state.HistoryCursor) ?? string.Empty; + data.DeleteChars(0, data.BufTextLen); + data.InsertChars(0, historyStr); + + return 0; } private void DrawChannelIconButton(Tab tab) diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 5b75551..e39111e 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -922,6 +922,18 @@ public sealed class ChatLogWindow : Window ]; } + // v0.6.0 — pop-out windows route submission through this wrapper. + // The main-window Chat buffer is briefly used as a vehicle for + // SendChatBox (which reads it directly) and restored afterwards so + // the main window does not visibly lose any half-typed input. + internal void SendChatBoxFromExternal(Tab tab, string text) + { + var saved = Chat; + Chat = text; + SendChatBox(tab); + Chat = saved; + } + internal void SendChatBox(Tab activeTab) { if (!string.IsNullOrWhiteSpace(Chat)) From 8cad8651d2a79f405db9e2425d54b46b311a6c2f Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:48:46 +0200 Subject: [PATCH 11/19] ui: drop auto-translate from compact bar in v0.6.0 (out of scope) --- ChatTwo/Ui/ChatInputBar.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ChatTwo/Ui/ChatInputBar.cs b/ChatTwo/Ui/ChatInputBar.cs index 1e6a551..0a8541d 100644 --- a/ChatTwo/Ui/ChatInputBar.cs +++ b/ChatTwo/Ui/ChatInputBar.cs @@ -41,11 +41,17 @@ public sealed class ChatInputBar // Compact rendering for pop-out windows. // - // Layout A (klassisch): Channel-Icon-Button links (Background-Farbe - // aus ChatColours), Text-Input mittig, Auto-Translate-Icon-Button - // rechts. Channel-Switch wirkt via Plugin.Functions.Chat global (das - // ist eine FFXIV-API-Eigenschaft, kein Bug). Pro Pop-Out unabhängig - // bleibt aber der Text-Buffer und der History-Cursor. + // v0.6.0 Compact-Layout: Channel-Icon-Button links (Background-Farbe + // aus ChatColours), Text-Input rechts daneben. Auto-Translate-Picker + // ist bewusst NICHT im Compact-Mode (Spec-Abweichung Layout D → A). + // Rechtfertigung: das Hauptfenster-Auto-Complete-Popup ist nicht ohne + // grossen Refactor pro Window instanzierbar; typische Pop-Out-Use-Cases + // (FC-Greeter, Club-Hostess) brauchen Auto-Translate selten dort. + // Eigene Compact-Auto-Complete-Implementation kann ein späterer + // Cycle nachreichen wenn Tester-Feedback das verlangt. + // + // Channel-Switch wirkt via Plugin.Functions.Chat global (FFXIV-API). + // Pro Pop-Out unabhängig bleiben Text-Buffer und History-Cursor. public void RenderCompact() { var tab = _activeTabAccessor(); @@ -55,16 +61,13 @@ public sealed class ChatInputBar DrawChannelIconButton(tab); ImGui.SameLine(); DrawCompactInput(tab); - ImGui.SameLine(); - // Task 23 ergänzt hier den Auto-Translate-Icon-Button. - ImGui.TextDisabled("[at]"); } private void DrawCompactInput(Tab tab) { - // Reserve room for the auto-translate icon button on the right. - const float reservedRightWidth = 32f; - var inputWidth = ImGui.GetContentRegionAvail().X - reservedRightWidth; + // Input takes the whole remaining width — no auto-translate button + // reserved on the right side in v0.6.0 (see RenderCompact comment). + var inputWidth = ImGui.GetContentRegionAvail().X; if (inputWidth < 60f) inputWidth = 60f; From a701f6c103e933f922147ce8b1acf5084a17e792 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:49:30 +0200 Subject: [PATCH 12/19] keybinds: extract DispatchTabDelta helper for upcoming focus-aware routing --- ChatTwo/GameFunctions/KeybindManager.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ChatTwo/GameFunctions/KeybindManager.cs b/ChatTwo/GameFunctions/KeybindManager.cs index 4de9161..88d4020 100644 --- a/ChatTwo/GameFunctions/KeybindManager.cs +++ b/ChatTwo/GameFunctions/KeybindManager.cs @@ -414,13 +414,13 @@ internal unsafe class KeybindManager : IDisposable { if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward)) { Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false; - Plugin.ChatLogWindow.ChangeTabDelta(1); + DispatchTabDelta(1); return; } if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward)) { Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false; - Plugin.ChatLogWindow.ChangeTabDelta(-1); + DispatchTabDelta(-1); return; } @@ -465,6 +465,15 @@ internal unsafe class KeybindManager : IDisposable { } } + // v0.6.0 — central dispatch for ChatTabForward/Backward so Task 25 + // can extend it with focus-aware routing to pop-out ChatInputBars. + // Right now both windows share the main ChatLogWindow.ChangeTabDelta, + // identical to v0.5.x behavior. + private void DispatchTabDelta(int delta) + { + Plugin.ChatLogWindow.ChangeTabDelta(delta); + } + private static Keybind GetKeybind(string id) { var outData = new FFXIVClientStructs.FFXIV.Client.System.Input.Keybind(); From cb5457ba2ea5b5b88cd25dca5d0a3f79788aed62 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:50:42 +0200 Subject: [PATCH 13/19] popout: opt-in ChatInputBar with focus-aware keybind routing --- ChatTwo/GameFunctions/KeybindManager.cs | 17 ++++++++++--- ChatTwo/InputHistoryService.cs | 28 +++++++++++++------- ChatTwo/Ui/ChatLogWindow.cs | 7 +++++ ChatTwo/Ui/Popout.cs | 34 ++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/ChatTwo/GameFunctions/KeybindManager.cs b/ChatTwo/GameFunctions/KeybindManager.cs index 88d4020..d15bd73 100644 --- a/ChatTwo/GameFunctions/KeybindManager.cs +++ b/ChatTwo/GameFunctions/KeybindManager.cs @@ -465,12 +465,21 @@ internal unsafe class KeybindManager : IDisposable { } } - // v0.6.0 — central dispatch for ChatTabForward/Backward so Task 25 - // can extend it with focus-aware routing to pop-out ChatInputBars. - // Right now both windows share the main ChatLogWindow.ChangeTabDelta, - // identical to v0.5.x behavior. + // v0.6.0 — central dispatch for ChatTabForward/Backward. If a pop-out + // window currently has its compact input focused, the keybind is + // forwarded into that pop-out's ChatInputBar so the user navigates + // tabs in the window they are typing in. Otherwise the main window + // handles it (= v0.5.x behavior). private void DispatchTabDelta(int delta) { + foreach (var popout in Plugin.ChatLogWindow.ActivePopouts) + { + if (popout.HasFocusedInputBar && popout.InputBar != null) + { + popout.InputBar.HandleKeybindForward(delta); + return; + } + } Plugin.ChatLogWindow.ChangeTabDelta(delta); } diff --git a/ChatTwo/InputHistoryService.cs b/ChatTwo/InputHistoryService.cs index 5ebfec2..bc65431 100644 --- a/ChatTwo/InputHistoryService.cs +++ b/ChatTwo/InputHistoryService.cs @@ -5,7 +5,11 @@ namespace ChatTwo; // Hellion Chat — v0.6.0 shared input history. Replaces the embedded // ChatLogWindow.InputBacklog so that pop-out windows with their own // ChatInputBar can navigate the same Up/Down history as the main window. -// Newest entry at index 0; consecutive duplicates are collapsed. +// Index semantics are kept identical to the v0.5.x InputBacklog: +// index 0 = oldest entry +// index Count - 1 = newest entry +// Push performs move-to-newest deduplication: existing entries are +// removed before the new one is appended at the end. public static class InputHistoryService { private const int MaxSize = 30; @@ -13,6 +17,8 @@ public static class InputHistoryService public static IReadOnlyList Entries => _entries; + public static int Count => _entries.Count; + public static void Push(string entry) { if (string.IsNullOrWhiteSpace(entry)) @@ -20,14 +26,20 @@ public static class InputHistoryService var trimmed = entry.Trim(); - // Drop consecutive duplicates so spamming the same line does not - // pollute the history with repeats. - if (_entries.Count > 0 && _entries[0] == trimmed) - return; + // Move-to-newest: existing entries are removed before the append + // so the same line typed twice does not occupy two history slots. + for (var i = 0; i < _entries.Count; i++) + { + if (_entries[i] == trimmed) + { + _entries.RemoveAt(i); + break; + } + } - _entries.Insert(0, trimmed); + _entries.Add(trimmed); if (_entries.Count > MaxSize) - _entries.RemoveAt(_entries.Count - 1); + _entries.RemoveAt(0); } public static string? GetByCursor(int cursor) @@ -36,6 +48,4 @@ public static class InputHistoryService return null; return _entries[cursor]; } - - public static int Count => _entries.Count; } diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index e39111e..33763a8 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -1490,6 +1490,13 @@ public sealed class ChatLogWindow : Window internal readonly List PopOutDocked = []; internal readonly HashSet PopOutWindows = []; + + // v0.6.0 — live enumeration of all active Popout windows so the + // KeybindManager can find a focused ChatInputBar to forward tab-cycle + // keybinds to. Filter on IsOpen prevents touching closed-but-still- + // registered popouts. + internal IEnumerable ActivePopouts => + Plugin.WindowSystem.Windows.OfType().Where(p => p.IsOpen); private void AddPopOutsToDraw() { HandlerLender.ResetCounter(); diff --git a/ChatTwo/Ui/Popout.cs b/ChatTwo/Ui/Popout.cs index c3f85ab..0333433 100644 --- a/ChatTwo/Ui/Popout.cs +++ b/ChatTwo/Ui/Popout.cs @@ -15,6 +15,13 @@ internal class Popout : Window private long FrameTime; // set every frame private long LastActivityTime = Environment.TickCount64; + // v0.6.0 — optional input bar inside the pop-out window. Lazy-allocated + // when the user enables Tab.PopOutInputEnabled and torn down when the + // toggle is turned off (independent text buffer is intentionally + // discarded — see v0.6.0 spec edge-case P1). + public ChatInputBar? InputBar { get; private set; } + public bool HasFocusedInputBar => InputBar?.IsFocused ?? false; + public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout") { ChatLogWindow = chatLogWindow; @@ -93,8 +100,33 @@ internal class Popout : Window ImGui.Separator(); } + // v0.6.0 — pop-out optional input bar. Reserve height first so the + // message log draws into the right region; only shown when the + // per-tab toggle is on. Toggle-OFF resets InputBar so the next + // toggle-ON gives a fresh buffer (no stale text persists). + var inputEnabled = Tab.PopOutInputEnabled; + if (!inputEnabled && InputBar != null) + { + InputBar = null; + } + if (inputEnabled) + { + InputBar ??= new ChatInputBar(ChatLogWindow.Plugin, ChatLogWindow, () => Tab); + } + + var inputBarHeight = inputEnabled + ? ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y + : 0f; + var handler = ChatLogWindow.HandlerLender.Borrow(); - ChatLogWindow.DrawMessageLog(Tab, handler, ImGui.GetContentRegionAvail().Y, false); + var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight; + ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false); + + if (inputEnabled && InputBar != null) + { + ImGui.Separator(); + InputBar.RenderCompact(); + } if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows)) LastActivityTime = FrameTime; From 1687271bfdd74d133e4c96128997b528ba5ff606 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:51:35 +0200 Subject: [PATCH 14/19] popout: show one-time hint banner about pop-out input feature --- ChatTwo/Ui/Popout.cs | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/ChatTwo/Ui/Popout.cs b/ChatTwo/Ui/Popout.cs index 0333433..c3cb7d8 100644 --- a/ChatTwo/Ui/Popout.cs +++ b/ChatTwo/Ui/Popout.cs @@ -100,6 +100,11 @@ internal class Popout : Window ImGui.Separator(); } + // v0.6.0 — one-time hint banner explaining the new pop-out input + // feature. Shown once per user; "Got it" or "Open settings" + // dismisses it and persists the flag. + var hintBannerHeight = DrawHintBannerIfNeeded(); + // v0.6.0 — pop-out optional input bar. Reserve height first so the // message log draws into the right region; only shown when the // per-tab toggle is on. Toggle-OFF resets InputBar so the next @@ -119,7 +124,7 @@ internal class Popout : Window : 0f; var handler = ChatLogWindow.HandlerLender.Borrow(); - var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight; + var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight - hintBannerHeight; ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false); if (inputEnabled && InputBar != null) @@ -132,6 +137,58 @@ internal class Popout : Window LastActivityTime = FrameTime; } + // Returns the vertical space the banner consumed (0 when not shown) + // so the message log can shrink accordingly. + private float DrawHintBannerIfNeeded() + { + if (Plugin.Config.SeenPopOutInputHint) + return 0f; + + // Inline strings — i18n keys follow in Task 29 (Phase E). + const string hintText = "Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Pro Tab in den Tab-Settings aktivieren."; + const string ackLabel = "Verstanden"; + const string openLabel = "Tab-Settings öffnen"; + + var startY = ImGui.GetCursorPosY(); + + var bg = new System.Numerics.Vector4(0.16f, 0.20f, 0.28f, 1f); + ImGui.PushStyleColor(ImGuiCol.ChildBg, bg); + ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f); + + var dismiss = false; + var openSettings = false; + using (var child = ImRaii.Child("##v060-pop-out-hint", new System.Numerics.Vector2(0f, 64f), true)) + { + if (child) + { + ImGui.TextWrapped(hintText); + if (ImGui.Button(ackLabel)) + dismiss = true; + ImGui.SameLine(); + if (ImGui.Button(openLabel)) + { + dismiss = true; + openSettings = true; + } + } + } + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(); + ImGui.Spacing(); + + if (dismiss) + { + Plugin.Config.SeenPopOutInputHint = true; + ChatLogWindow.Plugin.SaveConfig(); + Plugin.Log.Debug("Pop-Out input hint dismissed"); + if (openSettings) + ChatLogWindow.Plugin.SettingsWindow.Toggle(); + } + + return ImGui.GetCursorPosY() - startY; + } + public override void PostDraw() { ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked(); From 2c79a67dae219e28727b6a78b9cc9f5eed03fdf2 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 12:51:59 +0200 Subject: [PATCH 15/19] settings: add PopOutInputEnabled toggle below PopOut option --- ChatTwo/Ui/SettingsTabs/Tabs.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChatTwo/Ui/SettingsTabs/Tabs.cs b/ChatTwo/Ui/SettingsTabs/Tabs.cs index 1f4b7ee..8ba4248 100755 --- a/ChatTwo/Ui/SettingsTabs/Tabs.cs +++ b/ChatTwo/Ui/SettingsTabs/Tabs.cs @@ -96,6 +96,12 @@ internal sealed class Tabs : ISettingsTab if (tab.PopOut) { using var _ = ImRaii.PushIndent(10.0f); + // v0.6.0 — Pop-Out optional input bar. Inline strings; i18n + // keys follow in Task 29 (Phase E). + ImGui.Checkbox("Eingabe im Pop-Out aktivieren", ref tab.PopOutInputEnabled); + ImGuiUtil.HelpMarker("Erlaubt direktes Tippen und Absenden im Pop-Out-Fenster dieses Tabs. Jedes Pop-Out hat einen eigenen Text-Buffer; Channel-Wechsel wirkt global wie im Hauptfenster."); + ImGui.Spacing(); + ImGui.Checkbox(Language.Options_Tabs_IndependentOpacity, ref tab.IndependentOpacity); if (tab.IndependentOpacity) ImGuiUtil.DragFloatVertical(Language.Options_Tabs_Opacity, ref tab.Opacity, 0.25f, 0f, 100f, $"{tab.Opacity:N2}%%", ImGuiSliderFlags.AlwaysClamp); From 9e1f55964442a8fb82f723e5747cc01fc8b18b51 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 13:01:07 +0200 Subject: [PATCH 16/19] config: pivot pop-out input from per-tab to global master switch --- ChatTwo/Configuration.cs | 13 ++++++++----- ChatTwo/Plugin.cs | 13 ++++++------- ChatTwo/Ui/Popout.cs | 8 ++++---- ChatTwo/Ui/SettingsTabs/Tabs.cs | 6 ------ ChatTwo/Ui/SettingsTabs/Window.cs | 5 +++++ 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 367b4fa..f2238de 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -108,6 +108,13 @@ public class Configuration : IPluginConfiguration // pop-out window; never reset after that. public bool SeenPopOutInputHint; + // Hellion Chat — v0.6.0 master switch for the pop-out input bar. + // Global on purpose: per-tab makes no sense for Auto-Tell-Tabs which + // are session-only and would force the user to re-enable it for every + // new conversation. Default OFF so existing users see no behavior + // change after the v10→v11 migration. + public bool PopOutInputEnabled; + public int GetRetentionDays(ChatType type) { if (RetentionPerChannelDays.TryGetValue(type, out var userOverride)) @@ -303,6 +310,7 @@ public class Configuration : IPluginConfiguration AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle; SeenPopOutInputHint = other.SeenPopOutInputHint; + PopOutInputEnabled = other.PopOutInputEnabled; } } @@ -350,10 +358,6 @@ public class Tab public bool DisplayTimestamp = true; public InputChannel? Channel; public bool PopOut; - // Hellion Chat — v0.6.0 opt-in input bar inside the pop-out window for - // this tab. Independent text buffer and channel state per pop-out; - // history is shared with the main window via InputHistoryService. - public bool PopOutInputEnabled; public bool IndependentOpacity; public float Opacity = 100f; public bool InputDisabled; @@ -434,7 +438,6 @@ public class Tab DisplayTimestamp = DisplayTimestamp, Channel = Channel, PopOut = PopOut, - PopOutInputEnabled = PopOutInputEnabled, IndependentOpacity = IndependentOpacity, Opacity = Opacity, Identifier = Identifier, diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index f12f544..c74306f 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -152,19 +152,18 @@ public sealed class Plugin : IDalamudPlugin }); } - // Hellion Chat v10 → v11 — adds Tab.PopOutInputEnabled and - // Configuration.SeenPopOutInputHint for the v0.6.0 pop-out input - // feature. Lightweight migration: defaults the new fields, no - // user-facing notification because the change is opt-in only. + // Hellion Chat v10 → v11 — adds the global Configuration.PopOutInputEnabled + // master switch and SeenPopOutInputHint flag for the v0.6.0 pop-out + // input feature. Lightweight migration: defaults both fields, + // no user-facing notification because the change is opt-in only. if (Config.Version < 11) { - foreach (var tab in Config.Tabs) - tab.PopOutInputEnabled = false; + Config.PopOutInputEnabled = false; Config.SeenPopOutInputHint = false; Config.Version = 11; SaveConfig(); Log.Information( - "Migrated config v10 → v11: PopOutInputEnabled added per tab (default off), " + + "Migrated config v10 → v11: PopOutInputEnabled added (global, default off), " + "SeenPopOutInputHint added (default false)"); } diff --git a/ChatTwo/Ui/Popout.cs b/ChatTwo/Ui/Popout.cs index c3cb7d8..6c17c3b 100644 --- a/ChatTwo/Ui/Popout.cs +++ b/ChatTwo/Ui/Popout.cs @@ -107,9 +107,9 @@ internal class Popout : Window // v0.6.0 — pop-out optional input bar. Reserve height first so the // message log draws into the right region; only shown when the - // per-tab toggle is on. Toggle-OFF resets InputBar so the next - // toggle-ON gives a fresh buffer (no stale text persists). - var inputEnabled = Tab.PopOutInputEnabled; + // global master switch is on. Toggle-OFF resets InputBar so the + // next toggle-ON gives a fresh buffer (no stale text persists). + var inputEnabled = Plugin.Config.PopOutInputEnabled; if (!inputEnabled && InputBar != null) { InputBar = null; @@ -145,7 +145,7 @@ internal class Popout : Window return 0f; // Inline strings — i18n keys follow in Task 29 (Phase E). - const string hintText = "Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Pro Tab in den Tab-Settings aktivieren."; + const string hintText = "Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Master-Switch in den Fenster-Settings aktivieren."; const string ackLabel = "Verstanden"; const string openLabel = "Tab-Settings öffnen"; diff --git a/ChatTwo/Ui/SettingsTabs/Tabs.cs b/ChatTwo/Ui/SettingsTabs/Tabs.cs index 8ba4248..1f4b7ee 100755 --- a/ChatTwo/Ui/SettingsTabs/Tabs.cs +++ b/ChatTwo/Ui/SettingsTabs/Tabs.cs @@ -96,12 +96,6 @@ internal sealed class Tabs : ISettingsTab if (tab.PopOut) { using var _ = ImRaii.PushIndent(10.0f); - // v0.6.0 — Pop-Out optional input bar. Inline strings; i18n - // keys follow in Task 29 (Phase E). - ImGui.Checkbox("Eingabe im Pop-Out aktivieren", ref tab.PopOutInputEnabled); - ImGuiUtil.HelpMarker("Erlaubt direktes Tippen und Absenden im Pop-Out-Fenster dieses Tabs. Jedes Pop-Out hat einen eigenen Text-Buffer; Channel-Wechsel wirkt global wie im Hauptfenster."); - ImGui.Spacing(); - ImGui.Checkbox(Language.Options_Tabs_IndependentOpacity, ref tab.IndependentOpacity); if (tab.IndependentOpacity) ImGuiUtil.DragFloatVertical(Language.Options_Tabs_Opacity, ref tab.Opacity, 0.25f, 0f, 100f, $"{tab.Opacity:N2}%%", ImGuiSliderFlags.AlwaysClamp); diff --git a/ChatTwo/Ui/SettingsTabs/Window.cs b/ChatTwo/Ui/SettingsTabs/Window.cs index 109bc78..a522a3e 100644 --- a/ChatTwo/Ui/SettingsTabs/Window.cs +++ b/ChatTwo/Ui/SettingsTabs/Window.cs @@ -133,6 +133,11 @@ internal sealed class Window : ISettingsTab ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar); + // v0.6.0 — global master switch for the pop-out input bar. + // Inline strings; i18n keys follow in Task 29 (Phase E). + ImGui.Checkbox("Eingabe in Pop-Outs aktivieren", ref Mutable.PopOutInputEnabled); + ImGuiUtil.HelpMarker("Master-Switch: erlaubt direktes Tippen und Absenden in jedem Pop-Out-Fenster (inkl. Auto-Tell-Tabs). Channel-Wechsel im Pop-Out wirkt global wie im Hauptfenster; Text-Buffer und History-Cursor sind pro Pop-Out unabhängig."); + ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton); ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description); From acfe838bc6e62518930c6cc2088cd82a1416e374 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 13:06:26 +0200 Subject: [PATCH 17/19] i18n: add v0.6.0 strings for presets, pop-out input and hint banner --- ChatTwo/Resources/HellionStrings.Designer.cs | 19 +++++++++++ ChatTwo/Resources/HellionStrings.de.resx | 33 ++++++++++++++++++++ ChatTwo/Resources/HellionStrings.resx | 33 ++++++++++++++++++++ ChatTwo/Ui/Popout.cs | 7 ++--- ChatTwo/Ui/SettingsTabs/Appearance.cs | 4 +-- ChatTwo/Ui/SettingsTabs/Window.cs | 5 ++- 6 files changed, 91 insertions(+), 10 deletions(-) diff --git a/ChatTwo/Resources/HellionStrings.Designer.cs b/ChatTwo/Resources/HellionStrings.Designer.cs index 61ead5b..35939a3 100644 --- a/ChatTwo/Resources/HellionStrings.Designer.cs +++ b/ChatTwo/Resources/HellionStrings.Designer.cs @@ -242,4 +242,23 @@ internal class HellionStrings internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner)); internal static string Tabs_Presets_Linkshell => Get(nameof(Tabs_Presets_Linkshell)); internal static string Tabs_Presets_Linkshell_Hint => Get(nameof(Tabs_Presets_Linkshell_Hint)); + + // Hellion Chat — v0.6.0 chat colour presets (display labels) + internal static string ChatColourPresets_Default => Get(nameof(ChatColourPresets_Default)); + internal static string ChatColourPresets_HighContrast => Get(nameof(ChatColourPresets_HighContrast)); + internal static string ChatColourPresets_Pastell => Get(nameof(ChatColourPresets_Pastell)); + internal static string ChatColourPresets_DarkModeTuned => Get(nameof(ChatColourPresets_DarkModeTuned)); + internal static string ChatColourPresets_Hellion => Get(nameof(ChatColourPresets_Hellion)); + + // Hellion Chat — v0.6.0 chat colour presets section copy + internal static string Settings_Appearance_Colours_PresetsHint => Get(nameof(Settings_Appearance_Colours_PresetsHint)); + + // Hellion Chat — v0.6.0 pop-out input master switch + internal static string Settings_Window_PopOutInputEnabled_Name => Get(nameof(Settings_Window_PopOutInputEnabled_Name)); + internal static string Settings_Window_PopOutInputEnabled_Description => Get(nameof(Settings_Window_PopOutInputEnabled_Description)); + + // Hellion Chat — v0.6.0 one-time hint banner shown inside pop-outs + internal static string Popout_v060_HintText => Get(nameof(Popout_v060_HintText)); + internal static string Popout_v060_HintAck => Get(nameof(Popout_v060_HintAck)); + internal static string Popout_v060_HintOpenSettings => Get(nameof(Popout_v060_HintOpenSettings)); } diff --git a/ChatTwo/Resources/HellionStrings.de.resx b/ChatTwo/Resources/HellionStrings.de.resx index 2a586ae..944324b 100644 --- a/ChatTwo/Resources/HellionStrings.de.resx +++ b/ChatTwo/Resources/HellionStrings.de.resx @@ -555,4 +555,37 @@ Wenn du mehrere Linkshells benutzt, empfiehlt der Maintainer einen Tab pro Shell für eine sauberere Übersicht. Tab duplizieren und je Kopie die Kanalauswahl einschränken. + + ChatTwo Standard + + + Hoher Kontrast + + + Pastell + + + Dunkelmodus-optimiert + + + Hellion + + + Tipp: Presets überschreiben deine aktuellen Channel-Farben sofort. + + + Eingabe in Pop-Outs aktivieren + + + Master-Switch: erlaubt direktes Tippen und Absenden in jedem Pop-Out-Fenster (inkl. Auto-Tell-Tabs). Channel-Wechsel im Pop-Out wirkt global wie im Hauptfenster; Text-Buffer und History-Cursor sind pro Pop-Out unabhängig. + + + Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Master-Switch in den Fenster-Settings aktivieren. + + + Verstanden + + + Fenster-Settings öffnen + diff --git a/ChatTwo/Resources/HellionStrings.resx b/ChatTwo/Resources/HellionStrings.resx index 817c3c7..e4f70bc 100644 --- a/ChatTwo/Resources/HellionStrings.resx +++ b/ChatTwo/Resources/HellionStrings.resx @@ -555,4 +555,37 @@ If you use multiple linkshells, the maintainer recommends one tab per shell for cleaner readability. Duplicate this tab and narrow the channel selection per copy. + + ChatTwo Default + + + High-Contrast + + + Pastell + + + Dark-Mode-Tuned + + + Hellion + + + Tip: presets overwrite your current channel colours immediately. + + + Enable input in pop-outs + + + Master switch: lets you type and send messages directly inside every pop-out window (including auto-tell tabs). Channel changes inside a pop-out apply globally just like in the main window; the text buffer and history cursor stay independent per pop-out. + + + New in v0.6.0: you can type directly inside pop-out windows. Toggle the master switch in the window settings to enable it. + + + Got it + + + Open window settings + diff --git a/ChatTwo/Ui/Popout.cs b/ChatTwo/Ui/Popout.cs index 6c17c3b..80899cb 100644 --- a/ChatTwo/Ui/Popout.cs +++ b/ChatTwo/Ui/Popout.cs @@ -144,10 +144,9 @@ internal class Popout : Window if (Plugin.Config.SeenPopOutInputHint) return 0f; - // Inline strings — i18n keys follow in Task 29 (Phase E). - const string hintText = "Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Master-Switch in den Fenster-Settings aktivieren."; - const string ackLabel = "Verstanden"; - const string openLabel = "Tab-Settings öffnen"; + var hintText = Resources.HellionStrings.Popout_v060_HintText; + var ackLabel = Resources.HellionStrings.Popout_v060_HintAck; + var openLabel = Resources.HellionStrings.Popout_v060_HintOpenSettings; var startY = ImGui.GetCursorPosY(); diff --git a/ChatTwo/Ui/SettingsTabs/Appearance.cs b/ChatTwo/Ui/SettingsTabs/Appearance.cs index a607114..3f061c9 100644 --- a/ChatTwo/Ui/SettingsTabs/Appearance.cs +++ b/ChatTwo/Ui/SettingsTabs/Appearance.cs @@ -231,9 +231,7 @@ internal sealed class Appearance : ISettingsTab using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) { DrawColourPresetButtons(); - // i18n key follows in Task 29 (Phase E); inline for now so the - // build stays green while we ship the colour preset feature. - ImGui.TextDisabled("Tipp: Presets überschreiben deine aktuellen Channel-Farben sofort."); + ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint); ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing(); diff --git a/ChatTwo/Ui/SettingsTabs/Window.cs b/ChatTwo/Ui/SettingsTabs/Window.cs index a522a3e..f2e2638 100644 --- a/ChatTwo/Ui/SettingsTabs/Window.cs +++ b/ChatTwo/Ui/SettingsTabs/Window.cs @@ -134,9 +134,8 @@ internal sealed class Window : ISettingsTab ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar); // v0.6.0 — global master switch for the pop-out input bar. - // Inline strings; i18n keys follow in Task 29 (Phase E). - ImGui.Checkbox("Eingabe in Pop-Outs aktivieren", ref Mutable.PopOutInputEnabled); - ImGuiUtil.HelpMarker("Master-Switch: erlaubt direktes Tippen und Absenden in jedem Pop-Out-Fenster (inkl. Auto-Tell-Tabs). Channel-Wechsel im Pop-Out wirkt global wie im Hauptfenster; Text-Buffer und History-Cursor sind pro Pop-Out unabhängig."); + ImGui.Checkbox(HellionStrings.Settings_Window_PopOutInputEnabled_Name, ref Mutable.PopOutInputEnabled); + ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_PopOutInputEnabled_Description); ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton); ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description); From cf10c566dd83b40c1ca2a9d6d693eecafeb0ba22 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Sun, 3 May 2026 13:09:11 +0200 Subject: [PATCH 18/19] release: bump version to 0.6.0 with changelog entry --- ChatTwo/ChatTwo.csproj | 2 +- ChatTwo/HellionChat.yaml | 38 ++++++++++++++++++++++++++++++++++++++ repo.json | 12 ++++++------ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index f9c58b4..9b43ba3 100644 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -4,7 +4,7 @@ 0.1.0 is our bootstrap release; the underlying Chat 2 base is called out in the yaml changelog so users can see what it derives from. --> - 0.5.4 + 0.6.0 enable