chore: comments, second pass -- the task codes the first pass missed
The first sweep matched a character class that swallowed the digit, so a bare B1 slipped through while B1-2 was caught. Searching the whole A-Z space instead of guessing prefixes turned up 130-odd more: B0 through B6, C2, C3, D1, H2, M6, P7, P8, T2, W2, plus GP-04, KB-01, OD-1, PM-1, PM-3, SEC-01, TR-4, TR-7, UI-11, UI-12, XC-8 and API-3. Kept deliberately: 41 B4 01 is a byte signature, "N0" a format string, #L119-L128 a source anchor, LS4/LS6 are linkshells, and A=FF B=0C G=41 R=C2 explains a colour-channel order. Those look like codes and are not. Also translated the eight German comments left in the theme files and ImGuiUtil. Seven of them described what a palette does to which channel, which is worth reading -- just not in a second language in an otherwise English codebase.
This commit is contained in:
@@ -23,7 +23,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
private readonly ILogger<AutoTellTabsService> _logger;
|
||||
|
||||
// Tabs-list structure lock now lives on Plugin (neutral owner) so the
|
||||
// MessageManager refilter can share it. See Plugin.TabsListLock / B3.
|
||||
// MessageManager refilter can share it. See Plugin.TabsListLock.
|
||||
private object TabsListLock => _plugin.TabsListLock;
|
||||
|
||||
// Bumped whenever something wipes unpinned temp tabs wholesale (logout).
|
||||
@@ -179,7 +179,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
|
||||
// Three steps, because building the tab pulls history out of the store and
|
||||
// that must not happen under TabsListLock (B3 rule; the query sorts the whole
|
||||
// that must not happen under TabsListLock (the query sorts the whole
|
||||
// receiver history). Step 1 and 3 are locked, step 2 is not.
|
||||
int generation;
|
||||
lock (TabsListLock)
|
||||
@@ -303,7 +303,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
internal void DropOldestTempTab()
|
||||
{
|
||||
// B3: lock the list-structure ops so the (currently caller-less) Unpin path
|
||||
// Lock the list-structure ops so the (currently caller-less) Unpin path
|
||||
// can't race the worker; re-entrant when HandleTell already holds the lock.
|
||||
lock (TabsListLock)
|
||||
{
|
||||
@@ -577,7 +577,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
// Count and flag under one lock so the cap can't be raced. SaveConfig stays
|
||||
// OUTSIDE -- holding TabsListLock across a save would put an fsync on the
|
||||
// click path, which is what B6 just removed elsewhere.
|
||||
// click path, which is what a later cycle just removed elsewhere.
|
||||
lock (TabsListLock)
|
||||
{
|
||||
if (PinnedTempTabCount >= MaxPinnedTempTabs)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace HellionChat;
|
||||
|
||||
// Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge (B1).
|
||||
// Before B1 the fallback merged over the full `Ranges` array (Default + endonyms),
|
||||
// Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge.
|
||||
// Before that the fallback merged over the full `Ranges` array (Default + endonyms),
|
||||
// duplicating the Latin/Default work already done by the global/Japanese fonts.
|
||||
// This is the trimmed remainder the fallback is actually the sole source for:
|
||||
// - Hangul Syllables (AC00-D7A3): no other merged font ships Korean glyphs.
|
||||
@@ -9,7 +9,7 @@ namespace HellionChat;
|
||||
// font is Inter-Light (no CJK), so the fallback is the SOLE Han source. The JpRange
|
||||
// overlap is harmless (MergeMode: the Japanese font wins for shared kanji).
|
||||
// Deliberately excluded: ONLY the ASCII/Latin Default block (0x20-0xFF), which the
|
||||
// global font already owns -- that doubled Latin merge is the B1 waste being removed.
|
||||
// global font already owns -- that doubled Latin merge is the waste being removed.
|
||||
// Kept as plain start/end pairs so it is unit-testable without the unsafe ImGui
|
||||
// glyph-range builder (mirrors FontSizeResolver's split-for-test rationale).
|
||||
internal static class CjkFallbackRange
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
internal const int LatestVersion = 26;
|
||||
internal const int LatestVersion = 27;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -45,7 +45,7 @@ public class Configuration : IPluginConfiguration
|
||||
// Global window opacity, applied across all themes.
|
||||
public float WindowOpacity = 0.85f;
|
||||
|
||||
// UI-12: background opacity of the main chat window while unfocused.
|
||||
// Background opacity of the main chat window while unfocused.
|
||||
// WindowOpacity above stays the focused value.
|
||||
public float WindowOpacityInactive = 0.65f;
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Configuration : IPluginConfiguration
|
||||
public bool SeenPopOutHeaderHint;
|
||||
public bool AutoTellTabsOpenAsPopout;
|
||||
|
||||
// UI-7: how sender names are rendered in the chat log.
|
||||
// How sender names are rendered in the chat log.
|
||||
public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly;
|
||||
public NameFormMode NameFormMode = NameFormMode.Full;
|
||||
|
||||
@@ -232,7 +232,7 @@ public class Configuration : IPluginConfiguration
|
||||
// Toast when a tell the user sent could not be delivered.
|
||||
public bool NotifyFailedTell = true;
|
||||
|
||||
// UI-11: warn before sending a message that carries plugin-only glyphs.
|
||||
// Warn before sending a message that carries plugin-only glyphs.
|
||||
public bool NotifyPluginDisclosure = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public bool Use24HourClock = true;
|
||||
@@ -462,7 +462,7 @@ public class Tab
|
||||
[NonSerialized]
|
||||
internal string? _cachedTellIcon;
|
||||
|
||||
// PM-3 hover-lerp state. Default 0f means "not hovered". Sidebar
|
||||
// hover-lerp state. Default 0f means "not hovered". Sidebar
|
||||
// path animates per tab; card-mode-border path is tab-aggregate
|
||||
// (any card-row hover ramps the alpha for all cards in this tab).
|
||||
// Lerp speed lives in the render loop, not here, so the same field
|
||||
|
||||
@@ -94,13 +94,13 @@ public sealed class FontManager : IDisposable
|
||||
private ushort[] Ranges = [];
|
||||
private ushort[] JpRange = [];
|
||||
|
||||
// B1: trimmed remainder the NotoSansCjk fallback is the sole source for
|
||||
// Trimmed remainder the NotoSansCjk fallback is the sole source for
|
||||
// (Hangul + full Han); excludes the Default/Latin block already merged
|
||||
// by the global font, so the fallback no longer re-merges the full Ranges array.
|
||||
private ushort[] CjkFallbackGlyphRange = [];
|
||||
|
||||
// Report accessor for the ctor self-test: built glyph-range array lengths so
|
||||
// the step can show the B1 dedup effect (a small trimmed fallback vs the large
|
||||
// the step can show the dedup effect (a small trimmed fallback vs the large
|
||||
// primary range) in its on-disk report instead of a bare Pass.
|
||||
internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths =>
|
||||
(Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length);
|
||||
@@ -247,7 +247,7 @@ public sealed class FontManager : IDisposable
|
||||
|
||||
// Instance method so Ranges / JpRange are reachable without parameter
|
||||
// plumbing; PascalCase field names follow the existing class style.
|
||||
// B1: shared CJK + symbols tail for both the regular and italic delegate
|
||||
// Shared CJK + symbols tail for both the regular and italic delegate
|
||||
// fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode),
|
||||
// so this runs AFTER the primary font is set as config.MergeFont. The CJK
|
||||
// fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true
|
||||
@@ -265,7 +265,7 @@ public sealed class FontManager : IDisposable
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange (B1). Merged last so earlier fonts win.
|
||||
// NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange. Merged last so earlier fonts win.
|
||||
config.SizePt = basePt;
|
||||
config.GlyphRanges = CjkFallbackGlyphRange;
|
||||
AddFontWithFallback(
|
||||
@@ -427,7 +427,7 @@ public sealed class FontManager : IDisposable
|
||||
|
||||
// Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics)
|
||||
// belong to the primary/Japanese ranges only. The trimmed CJK fallback
|
||||
// (B1) skips them so it stays a pure Hangul/Simplified-Han remainder and
|
||||
// skips them so it stays a pure Hangul/Simplified-Han remainder and
|
||||
// does not re-merge the Default-block work the global font already did.
|
||||
if (includeCommonExtras)
|
||||
{
|
||||
@@ -498,7 +498,7 @@ public sealed class FontManager : IDisposable
|
||||
);
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true);
|
||||
|
||||
// B1: the fallback gets only the trimmed Hangul/Simplified-Han remainder.
|
||||
// The fallback gets only the trimmed Hangul/Simplified-Han remainder.
|
||||
// No Default block, no endonyms — those are already merged by the global and
|
||||
// Japanese fonts, so re-merging them on the fallback was wasted atlas work.
|
||||
CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false);
|
||||
|
||||
@@ -507,14 +507,14 @@ internal unsafe class KeybindManager : IDisposable
|
||||
|
||||
// Resolve the surface this keybind acts on FIRST: a focused pop-out otherwise
|
||||
// the main window. Channel-set/REPLY/prefill all write here so the action
|
||||
// follows the input the user is typing in (C3 full tail rebuild, OD-1).
|
||||
// follows the input the user is typing in.
|
||||
var (targetWindow, targetTab) = ResolveKeybindTarget();
|
||||
|
||||
// Surface + focus the resolved target ONCE, before routing. Main: ActivateChat
|
||||
// re-surfaces it from a hide/closed state (the chat-activation entry point
|
||||
// retired in v1.6.0). Pop-out: arm only its focus — NOT ActivateChat, which
|
||||
// would yank the main window to front and un-hide it on every pop-out-targeted
|
||||
// keybind (OD-1: stay where the user types). Exactly one window arms focus per
|
||||
// keybind: stay where the user types. Exactly one window arms focus per
|
||||
// keybind, so the next frame has no SetKeyboardFocusHere race.
|
||||
if (targetWindow is ChannelPopoutWindow)
|
||||
targetWindow.RequestInputFocus();
|
||||
@@ -547,7 +547,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
// Rotation binds (REPLY / linkshell-cycle). Ported from v1.5.6's
|
||||
// ChatLogWindow.Activated (1d3b429:240-334) without the ChatActivatedArgs
|
||||
// indirection (gone in the rewrite). Writes onto the resolved surface's
|
||||
// tab (C2/C3 shared target), not Plugin.CurrentTab.
|
||||
// tab, not Plugin.CurrentTab.
|
||||
if (targetTab is { } rotTab)
|
||||
{
|
||||
var targetChannel = (InputChannel?)rotateChannel;
|
||||
@@ -629,7 +629,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
|
||||
if (info.Permanent)
|
||||
{
|
||||
// KB-01 (1.5.6 parity, ChatLogWindow.SetChannel 1d3b429:1476-1479):
|
||||
// 1.5.6 parity (ChatLogWindow.SetChannel, 1d3b429:1476-1479):
|
||||
// committing the game channel also pre-targets the game's native input.
|
||||
// Forward the tab's reply target for Tell so the partner is armed
|
||||
// game-side (ChangeChatChannel code 17); null for a linkshell —
|
||||
@@ -654,7 +654,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
// Prefill text binds (CMD_COMMAND seeds "/"): the token always goes to the
|
||||
// main InputBar (the focus contract does not expose pop-out buffers); a
|
||||
// focused pop-out already received focus above, so only token routing matters
|
||||
// here (documented scope limit, OD-1).
|
||||
// here -- a documented scope limit.
|
||||
if (info.Text is { } text)
|
||||
Plugin.Instance.InputBar.SetPendingMessage(text);
|
||||
}
|
||||
@@ -665,7 +665,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
}
|
||||
|
||||
// Resolve which chat surface a keybind action targets: the open pop-out whose
|
||||
// input currently has focus, otherwise the main window. C2/C3 share this so a
|
||||
// input currently has focus, otherwise the main window. Both paths share it so a
|
||||
// channel-switch/REPLY/prefill follows the surface the user is typing in. The
|
||||
// returned tab is that surface's bound tab (pop-out: Bound; main: ActiveTab).
|
||||
// Null tab => skip the tab-write (early-load window where no tab exists yet).
|
||||
@@ -682,7 +682,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
}
|
||||
|
||||
// Tab-delta keybinds (ChatTabForward/Backward) stay main-window-only by design:
|
||||
// a channel-bound pop-out has no tab list to cycle (OD-1). The focus contract is
|
||||
// a channel-bound pop-out has no tab list to cycle. The focus contract is
|
||||
// consumed by the channel-set/REPLY/prefill tail, not here.
|
||||
private void DispatchTabDelta(int delta)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class ExtraChat : IDisposable
|
||||
|
||||
// volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these.
|
||||
// Reference assignment is atomic on x64, but the barrier ensures visibility
|
||||
// across threads (especially Mono/Wine). See AUDIT-2026-05-05 [SEC-01].
|
||||
// across threads (especially Mono/Wine). Raised in the 2026-05-05 audit.
|
||||
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
|
||||
internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
|
||||
ChannelCommandColoursInternal;
|
||||
|
||||
@@ -163,7 +163,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
|
||||
internal void ClearAllTabs()
|
||||
{
|
||||
// B3: snapshot the tab LIST under the shared lock so the worker-thread
|
||||
// Snapshot the tab LIST under the shared lock so the worker-thread
|
||||
// add/remove can't tear the enumeration; tab.Clear() then runs lock-free
|
||||
// (each tab's Messages has its own SemaphoreSlim — lock order: list outer).
|
||||
List<Tab> tabsSnapshot;
|
||||
@@ -184,8 +184,8 @@ internal class MessageManager : IAsyncDisposable
|
||||
using var messages = Store.GetMostRecentMessages(CurrentContentId, since);
|
||||
|
||||
// TempTabs excluded (live state from AutoTellTabsService). Bucket via the
|
||||
// pure MapMessagesToTabs so the assignment stays testable outside Dalamud (B3-1).
|
||||
// B3: snapshot under the shared lock (list copy only — short critical
|
||||
// pure MapMessagesToTabs so the assignment stays testable outside Dalamud.
|
||||
// Snapshot under the shared lock (list copy only — short critical
|
||||
// section). The Store query above and the AddSortPrune writes below stay
|
||||
// OUTSIDE the lock (lock order: list outer, MessageList inner).
|
||||
List<Tab> nonTempTabs;
|
||||
|
||||
@@ -2,7 +2,7 @@ using HellionChat.Resources;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// UI-7: how a sender's name is rendered in the chat log. Kept in its own file
|
||||
// How a sender's name is rendered in the chat log. Kept in its own file
|
||||
// (no Dalamud usings) so the SenderNameFormatter pure-helper test stays
|
||||
// AppDomain-isolated (feedback_dalamud_test_isolation).
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ internal sealed class PayloadHandler
|
||||
if (!Sheets.IsInForay())
|
||||
{
|
||||
// Single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
|
||||
// XC-8: shares the /tell builder with the native detours. IsPublic (not
|
||||
// Shares the /tell builder with the native detours. IsPublic (not
|
||||
// IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World.
|
||||
_inputBar.SetPendingMessage(
|
||||
GameFunctions.Chat.BuildTellCommand(
|
||||
|
||||
+80
-13
@@ -134,7 +134,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
||||
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
|
||||
|
||||
// Ctor-smoke anchors (B0-2). Exposed so the Payload/Chunk ctor-smoke steps
|
||||
// Ctor-smoke anchors. Exposed so the Payload/Chunk ctor-smoke steps
|
||||
// can drive the real per-frame Lender path (Borrow()) and the eager
|
||||
// singletons through the container, never via new(). Mirror of the
|
||||
// FontManager property pattern — every SelfTest reaches services this way.
|
||||
@@ -178,7 +178,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
internal bool ChatActivationRequested;
|
||||
|
||||
// Set in the first DisposeAsync statement so async callbacks scheduled
|
||||
// via Framework.RunOnTick (v1.4.8 B3 retention sweep) can early-bail
|
||||
// via Framework.RunOnTick (v1.4.8 retention sweep) can early-bail
|
||||
// before they touch state that has already been torn down. Volatile
|
||||
// because the tick reads it from a different thread than the writer.
|
||||
private volatile bool _isDisposing;
|
||||
@@ -188,7 +188,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// just unloaded belongs to nobody.
|
||||
internal bool IsDisposing => _isDisposing;
|
||||
|
||||
// v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at
|
||||
// v1.9.0: last full Draw() wall-time in ms, written once per frame at
|
||||
// the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push
|
||||
// and the font push (the first-frame hitch measurement must include atlas/style
|
||||
// prologue cost), not just WindowSystem.Draw — measuring the inner call
|
||||
@@ -214,7 +214,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// must never block doing so.
|
||||
internal readonly Util.DbOperationGate DbOperations = new();
|
||||
|
||||
// B3: neutral owner of the Config.Tabs LIST-structure lock so both the
|
||||
// Neutral owner of the Config.Tabs LIST-structure lock so both the
|
||||
// worker-thread mutator (AutoTellTabsService) and the framework-thread
|
||||
// refilter (MessageManager) share ONE monitor. Lock order: this outer,
|
||||
// MessageList's SemaphoreSlim inner — never the reverse.
|
||||
@@ -287,6 +287,37 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
|
||||
);
|
||||
}
|
||||
// 2.0.0 does not migrate, it starts over. Five cycles rebuilt the whole
|
||||
// window layer, and a config carried through them keeps values chosen
|
||||
// against surfaces that no longer exist -- an opacity picked for a
|
||||
// window that has been redrawn twice since, tabs laid out for a sidebar
|
||||
// that works differently now. Every user of this build is a tester who
|
||||
// was told this happens, and it is the only way to be sure everyone
|
||||
// sees the same defaults.
|
||||
//
|
||||
// The file is copied aside first. Rolling back to 1.5.6 is a supported
|
||||
// move here and it stays cheap: the old settings are a file copy away,
|
||||
// rather than an evening of clicking them back in.
|
||||
if (Config.Version < 27)
|
||||
{
|
||||
BackUpConfigBeforeReset();
|
||||
|
||||
Config = Configuration.CreateFresh();
|
||||
Config.Version = 27;
|
||||
|
||||
// Saved immediately: a crash between here and the first user-driven
|
||||
// save would otherwise run the whole reset again on the next start,
|
||||
// and the second run would back up the already-reset file over the
|
||||
// real backup.
|
||||
SaveConfig();
|
||||
|
||||
Log.Information(
|
||||
"Config reset to defaults for 2.0.0. Previous settings kept as "
|
||||
+ "HellionChat.json.pre-2.0.0.bak next to the config file."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
|
||||
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
|
||||
// set it false (only effective in 1.5.6) wanted top tabs — carry that
|
||||
@@ -341,7 +372,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
var carried = 0;
|
||||
foreach (var tab in Config.Tabs)
|
||||
{
|
||||
if (tab.NameCameFromPartner || (!tab.IsTempTab && tab.TellTarget?.IsSet() != true))
|
||||
if (
|
||||
tab.NameCameFromPartner
|
||||
|| (!tab.IsTempTab && tab.TellTarget?.IsSet() != true)
|
||||
)
|
||||
continue;
|
||||
|
||||
tab.NameCameFromPartner = true;
|
||||
@@ -356,14 +390,15 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Config.Version = 26;
|
||||
Config.Version = 27;
|
||||
|
||||
// Unpinned TempTabs are session-only and dropped on every load. Pinned
|
||||
// TempTabs survive reload -- tester feedback in v1.4.7.
|
||||
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
|
||||
|
||||
// GP-04: clear stale Tab.PopOut flags now — the pool binds further down
|
||||
// Clear stale Tab.PopOut flags now — the pool binds further down
|
||||
// (ChannelPopoutPool resolve below), so at this point no tab can own a
|
||||
// slot. A persisted PopOut=true (notably on surviving pinned TempTabs)
|
||||
// would otherwise be a flag with no window. Runs after the strip, before
|
||||
@@ -462,7 +497,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
|
||||
ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>();
|
||||
|
||||
// Ctor-smoke anchors (B0-2). Resolved last, against the fully built
|
||||
// Ctor-smoke anchors. Resolved last, against the fully built
|
||||
// container: every MakePayloadHandler dep (MainWindow, InputBar,
|
||||
// ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve
|
||||
// below just reuses the same cached singleton. These are plain
|
||||
@@ -732,7 +767,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
return;
|
||||
|
||||
// Set before any cleanup so deferred Framework.RunOnTick callbacks
|
||||
// (B3 retention sweep) see the flag and bail out before they touch
|
||||
// (the retention sweep) see the flag and bail out before they touch
|
||||
// MessageManager / Log / static fields that the rest of this method
|
||||
// is about to tear down.
|
||||
_isDisposing = true;
|
||||
@@ -840,6 +875,38 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
return failure;
|
||||
}
|
||||
|
||||
// Copies the config aside before the 2.0.0 reset overwrites it. Best effort
|
||||
// by design: a backup that cannot be written must not stop the plugin from
|
||||
// starting, and the reset itself is what the user was told would happen.
|
||||
//
|
||||
// Overwrite=false, so a second run cannot bury the real backup under a copy
|
||||
// of the already-reset file. The reset saves immediately for the same
|
||||
// reason, but a crash in between is exactly when this matters.
|
||||
private static void BackUpConfigBeforeReset()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (dir is null)
|
||||
return;
|
||||
|
||||
var configFile = Path.Combine(dir, "HellionChat.json");
|
||||
if (!File.Exists(configFile))
|
||||
return;
|
||||
|
||||
var backup = Path.Combine(dir, "HellionChat.json.pre-2.0.0.bak");
|
||||
if (File.Exists(backup))
|
||||
return;
|
||||
|
||||
File.Copy(configFile, backup);
|
||||
Log.Information($"HellionChat: config backed up to {backup} before the 2.0.0 reset");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning(e, "HellionChat: could not back up the config before the 2.0.0 reset");
|
||||
}
|
||||
}
|
||||
|
||||
private static void MigrateFromChatTwoLayout()
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
@@ -1181,7 +1248,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// Schedule on the next framework tick to avoid the ~194ms
|
||||
// hitch from blocking with .Wait() while the frame finishes.
|
||||
// The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is
|
||||
// now guarded by the shared Plugin.TabsListLock (B3), so this
|
||||
// now guarded by the shared Plugin.TabsListLock, so this
|
||||
// tick scheduling is purely hitch-avoidance, not safety.
|
||||
// Pattern reference: SimpleTweaks
|
||||
// Tweaks/Chat/CaseInsensitiveCommands.cs:45.
|
||||
@@ -1268,7 +1335,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
// v1.9.0 B5: time the whole handler (style + font prologue included).
|
||||
// v1.9.0: time the whole handler (style + font prologue included).
|
||||
// Bail before measuring once teardown has begun — a late Draw tick
|
||||
// must not touch ThemeRegistry / FontManager after DisposeAsync.
|
||||
if (_isDisposing)
|
||||
@@ -1277,7 +1344,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
var drawWatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
// v1.4.8 B2: pick up external edits of the active custom theme JSON
|
||||
// v1.4.8: pick up external edits of the active custom theme JSON
|
||||
// without forcing the user to re-click the picker. The disk-stat is
|
||||
// 1Hz-throttled inside RefreshActiveIfStale, so this is essentially
|
||||
// free on built-in themes and ~1 stat/second on custom themes.
|
||||
@@ -1367,7 +1434,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// Config.Tabs across the save so JSON includes them. Cloning only the
|
||||
// unpinned subset keeps the allocation proportional to
|
||||
// AutoTellTabsLimit (<=15) instead of the full tab list.
|
||||
// B3: the strip/restore mutates the tab LIST, so it shares TabsListLock
|
||||
// The strip/restore mutates the tab LIST, so it shares TabsListLock
|
||||
// with the worker add/remove and the refilter snapshot. Re-entrant: the
|
||||
// one worker caller (HandleTell) already holds it; framework callers take
|
||||
// it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback
|
||||
|
||||
@@ -5,7 +5,7 @@ using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B2: behavioural check that the card path feeds CardClipPlanner AND that a
|
||||
// Behavioural check that the card path feeds CardClipPlanner AND that a
|
||||
// layout change clears the height cache — not a non-null check. Pure plan math
|
||||
// is pinned headless by CardClipPlanTests; this drives the live accessors.
|
||||
internal sealed class CardClipPlanStep : ISelfTestStep
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace HellionChat.SelfTests;
|
||||
// - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is
|
||||
// Tab.TellTarget and is deliberately untouched by the strip.
|
||||
// The channel label is intentionally NOT asserted: a tell tab re-derives back to
|
||||
// Tell after the strip (spec TR-7); only the target matters for privacy.
|
||||
// Tell after the strip ; only the target matters for privacy.
|
||||
internal sealed class CurrentTabGuidedStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
@@ -5,7 +5,7 @@ using HellionChat._Helpers;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B2-3: proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist)
|
||||
// Proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist)
|
||||
// scanner into the REAL send entry InputBar.TrySend. Drives TrySend via the
|
||||
// arm-test-hook with a PUA glyph in the buffer and NotifyPluginDisclosure on:
|
||||
// the first send must ARM and HOLD (no send), so PendingMessage stays the probe
|
||||
|
||||
@@ -112,7 +112,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// B1: assert the atlas actually finished building all required handles,
|
||||
// Assert the atlas actually finished building all required handles,
|
||||
// not just that the references are non-null. FontsReady is the observable
|
||||
// state the trimmed-fallback rebuild must still reach; a half-built atlas
|
||||
// would pass the null/exception checks above but fail here.
|
||||
@@ -128,7 +128,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
||||
}
|
||||
|
||||
// Report what was actually verified rather than a bare Pass.
|
||||
// The glyph-range entry counts make the B1 dedup visible — the cjk-fallback
|
||||
// The glyph-range entry counts make the dedup visible — the cjk-fallback
|
||||
// range is now a small trimmed remainder next to the large primary range.
|
||||
var counts = fm.GlyphRangeLengths;
|
||||
var italicState =
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Windows;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// P8 wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and
|
||||
// wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and
|
||||
// Toggle() (/hellion) restore it. Pure window-state — the focus side is left to smoke.
|
||||
internal sealed class HideRestoreSelfTestStep : ISelfTestStep
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Windows;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B1-2 window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
|
||||
// window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
|
||||
// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure
|
||||
// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them
|
||||
// -- flags must rebuild from a fresh base, or NoMove sticks after toggling
|
||||
@@ -72,7 +72,7 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// P7 title-bar contract: ShowTitleBar=false adds NoTitleBar from the
|
||||
// title-bar contract: ShowTitleBar=false adds NoTitleBar from the
|
||||
// fresh base, true clears it (same no-accumulation guarantee).
|
||||
var barHidden = MainWindow.ResolveFlags(true, true, false);
|
||||
var barShown = MainWindow.ResolveFlags(true, true, true);
|
||||
|
||||
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// UI-12 focus opacity. Pins the pure ResolveBgAlpha contract (focused →
|
||||
// focus opacity. Pins the pure ResolveBgAlpha contract (focused →
|
||||
// WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring
|
||||
// (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard)
|
||||
// is NOT headless-deterministic — the guard may leave BgAlpha null when
|
||||
|
||||
@@ -7,7 +7,7 @@ using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B3-3: notification-sound selection. Drives the pure SelectNotificationSound
|
||||
// Notification-sound selection. Drives the pure SelectNotificationSound
|
||||
// (the exact pick logic ProcessMessage runs per message) through its SelfTest
|
||||
// wrapper with local synthetic tabs — Plugin.Config.Tabs is never touched, so
|
||||
// no real tab gains messages or unread state. The audible preview button is
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.IO;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Disk sink for the B5 performance baseline. Kept separate from the SelfTest
|
||||
// Disk sink for the performance baseline. Kept separate from the SelfTest
|
||||
// step so the per-frame hot path never references file IO. Writes one
|
||||
// perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move)
|
||||
// like ThemeRegistry's theme writer, so a mid-write crash leaves either the
|
||||
|
||||
@@ -18,7 +18,7 @@ internal sealed class PerformanceBaselineStep : ISelfTestStep
|
||||
private const int TargetFrames = 1000;
|
||||
|
||||
// Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an
|
||||
// intentional under-count of draw work, not the exact quad count (API-3).
|
||||
// intentional under-count of draw work, not the exact quad count.
|
||||
private const int VerticesPerQuadProxy = 6;
|
||||
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B3-5: only the snap decision is headless-testable. Scroll detection + bar +
|
||||
// Only the snap decision is headless-testable. Scroll detection + bar +
|
||||
// hit-test are smoke-only (the scroll child exists only in-game; GetScrollY is
|
||||
// garbage headless). Drives ResolveSnapToBottom via the SelfTest accessor and
|
||||
// asserts the OR + the request reset invariant.
|
||||
|
||||
@@ -5,7 +5,7 @@ using Dalamud.Plugin.SelfTest;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B2-1/B2-2: proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
|
||||
// Proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
|
||||
// render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned
|
||||
// real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54)
|
||||
// with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Code;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// v1.10.0/C3: the active row gets a surface and an accent bar, so exactly the
|
||||
// v1.10.0: the active row gets a surface and an accent bar, so exactly the
|
||||
// row the user is on must be marked -- and only that one. Drives the real
|
||||
// Sidebar.Draw and reads the render-observability counter, so a regression in
|
||||
// the draw path fails rather than a parallel calculation passing.
|
||||
|
||||
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B3-2: greeted glyph renders only for temp tabs when the toggle is on. Drives
|
||||
// Greeted glyph renders only for temp tabs when the toggle is on. Drives
|
||||
// the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only
|
||||
// real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls
|
||||
// IsExpanded/GetWidth) inside the /xlperf window frame and reads the render
|
||||
|
||||
@@ -58,7 +58,7 @@ internal sealed class SidebarModeAutoSwitchStep : ISelfTestStep
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// B1-3a: the expanded width must come from Config.SidebarWidth, not the
|
||||
// The expanded width must come from Config.SidebarWidth, not the
|
||||
// old fixed 150 constant. Drive the REAL GetWidth (the single source
|
||||
// Sidebar.Draw consumes) with concrete values and assert the OBSERVED
|
||||
// effect — in-range passthrough plus clamping — instead of mirroring the
|
||||
|
||||
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B3-4: section headers render once per non-empty temp-tab pool, and compact
|
||||
// Section headers render once per non-empty temp-tab pool, and compact
|
||||
// mode suppresses the header text (separators stay). Drives the REAL
|
||||
// Sidebar.Draw inside the /xlperf window frame (same render precedent as
|
||||
// SidebarGreetedGlyphStep) and reads the render observability counter.
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Ui.Components;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// B3-1: rename must persist. Drives the real ApplyTabRename (the InputText
|
||||
// Rename must persist. Drives the real ApplyTabRename (the InputText
|
||||
// callback path), then SaveConfig + reload from disk and asserts the new name
|
||||
// survived — a fresh-from-config tab, not the same reference (a reference check
|
||||
// would pass on a dead roundtrip). Uses a persistent (non-temp) tab: unpinned
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace HellionChat.SelfTests;
|
||||
// The activation strip. Drives the REAL OnTabActivated -- the entry the
|
||||
// Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call
|
||||
// — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the
|
||||
// five contracts: strip-on-switch, no-strip-on-reclick (TR-4), leg1 preserve,
|
||||
// five contracts: strip-on-switch, no-strip-on-reclick, leg1 preserve,
|
||||
// derive, and non-tell untouched.
|
||||
internal sealed class TellResetOnActivateStep : ISelfTestStep
|
||||
{
|
||||
@@ -41,7 +41,7 @@ internal sealed class TellResetOnActivateStep : ISelfTestStep
|
||||
}
|
||||
|
||||
// (b) re-clicking the already-active tab (previous == tab) must NOT strip
|
||||
// a live game-tell conversation (TR-4 regression guard).
|
||||
// a live game-tell conversation (regression guard).
|
||||
var reclick = MakeStaleTellTab(boundTellTarget: false, withLabel: false);
|
||||
TabLifecycleHelpers.OnTabActivated(reclick, reclick);
|
||||
if (reclick.CurrentChannel.TellTarget is null)
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Themes;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Verifies the v1.5.4 PM-1 crossfade contract: switching the active
|
||||
// Verifies the v1.5.4 crossfade contract: switching the active
|
||||
// theme arms TryGetActiveCrossfade for ~300ms, then the registry
|
||||
// returns to direct AbgrCache reads. A second switch within 100ms
|
||||
// keeps the lerped path active (no identity-snap). CleanUp restores
|
||||
@@ -67,7 +67,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
|
||||
// it as "saw the start" if more than 300ms have elapsed.
|
||||
// Skip the mid-crossfade-switch phase in that case -- the
|
||||
// lerped path is no longer active, so a second switch would
|
||||
// re-arm a fresh crossfade and not exercise PM-1b's
|
||||
// re-arm a fresh crossfade and not exercise its
|
||||
// mid-flight-origin override.
|
||||
if (Environment.TickCount64 - this.armedAtTickMs > 300)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
|
||||
|
||||
if (!this.sawMidCrossfadeSwitch)
|
||||
{
|
||||
// PM-Test-3 mid-crossfade-switch phase: within ~100ms of the
|
||||
// mid-crossfade-switch phase: within ~100ms of the
|
||||
// first observed crossfade, fire a second Switch to a THIRD
|
||||
// theme. ArmCrossfade must compose the current lerped state
|
||||
// as the new origin -- TryGetActiveCrossfade still returns
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat.Code;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// v1.10.0/D1: the top-tab strip marks the active tab with a fill plus an accent
|
||||
// v1.10.0: the top-tab strip marks the active tab with a fill plus an accent
|
||||
// underline. Drives the real TopTabBar.Draw and reads the render counter.
|
||||
//
|
||||
// "At most one", not "exactly one": the strip skips popped-out tabs, so zero
|
||||
|
||||
@@ -51,9 +51,9 @@ internal static class EventHorizon
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Event Horizon — Cosmic-Purple-Drift: helle Pastelle bekommen
|
||||
// Lavender-Tinte, Akzent-Channels (Tell) ziehen Richtung Magenta-
|
||||
// Lila. Channel-Identität bleibt klar erkennbar.
|
||||
// Cosmic purple drift: the pale pastels take a lavender tint and
|
||||
// the accent channels (tell) pull towards magenta-violet. Channel
|
||||
// identity stays readable throughout.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
|
||||
|
||||
@@ -51,8 +51,8 @@ internal static class ForgeMerchantman
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Forge Merchantman — Patina-Tinte in Party/FC, Bernstein-Tinte in
|
||||
// Yell/Alliance/CustomEmote. Channel-identity bleibt voll erhalten.
|
||||
// Patina tint on party and free company, amber on yell, alliance
|
||||
// and custom emotes. Channel identity is left fully intact.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
|
||||
|
||||
@@ -51,8 +51,8 @@ internal static class HellionArctic
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Hellion Arctic — FFXIV-Standard mit dezenter Cyan-Tinte in den
|
||||
// blauen Channels (Party/FC). Channel-Identität bleibt klar.
|
||||
// The FFXIV defaults with a restrained cyan tint on the blue
|
||||
// channels (party, free company). Channel identity stays clear.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
||||
|
||||
@@ -51,9 +51,10 @@ internal static class IndigoViolet
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Indigo Violet — Lavender-Pink-Drift in Tell und LS6/7. Türkis-
|
||||
// Mint-Aurora-Counter in Party/FC und LS4. Glitter-Gold in Yell.
|
||||
// Differenzierung zu Event Horizon: dunkler, dichter, Türkis statt Gold.
|
||||
// Lavender-pink drift on tell and linkshells 6 and 7, countered by
|
||||
// a turquoise-mint aurora on party, free company and linkshell 4.
|
||||
// Glitter gold on yell. What sets it apart from Event Horizon:
|
||||
// darker, denser, and turquoise where that one goes gold.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
|
||||
|
||||
@@ -51,9 +51,9 @@ internal static class MintGrove
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Mint Grove — Naturthemen-Tönung: Honey-Amber in Yell-Familie,
|
||||
// Mint-Drift in NoviceNetwork und Linkshell. Tell-Pink-Identität
|
||||
// bleibt erhalten für Erkennbarkeit.
|
||||
// Nature-themed tint: honey amber across the yell family, a mint
|
||||
// drift in novice network and linkshell. Tell keeps its pink so
|
||||
// the channel stays recognisable.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
|
||||
|
||||
@@ -51,8 +51,9 @@ internal static class NightBlue
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Night Blue — Royal-Blue-Tinte in Party/FC, Bronze-Gold in Yell/
|
||||
// Alliance. Channel-identity (Tell-Pink, NN-Lime) bleibt erhalten.
|
||||
// Royal blue on party and free company, bronze gold on yell and
|
||||
// alliance. Channel identity is preserved -- tell stays pink,
|
||||
// novice network stays lime.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
|
||||
|
||||
@@ -51,8 +51,9 @@ internal static class SynthwaveSunset
|
||||
ChatColors: new ThemeChatColors(
|
||||
new Dictionary<HellionChat.Code.ChatType, uint>
|
||||
{
|
||||
// Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC),
|
||||
// Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels.
|
||||
// Magenta carries the warm channels (yell, shout, free company),
|
||||
// cyan the cool ones (tell, party). Neon accents on the status-
|
||||
// adjacent channels.
|
||||
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
|
||||
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
|
||||
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class ThemeRegistry
|
||||
|
||||
public const string DefaultSlug = HellionArctic.Slug;
|
||||
|
||||
// 1Hz throttle for the v1.4.8 B2 auto-refresh-on-active path. The
|
||||
// 1Hz throttle for the v1.4.8 auto-refresh-on-active path. The
|
||||
// Plugin.Draw hook calls RefreshActiveIfStale every frame, but the
|
||||
// actual File.GetLastWriteTimeUtc disk-stat only runs once per second
|
||||
// -- 60fps would otherwise mean 3600 stats/min on the same path (more
|
||||
@@ -24,7 +24,7 @@ public sealed class ThemeRegistry
|
||||
private readonly string? _customThemesDir;
|
||||
private Theme _active;
|
||||
|
||||
// v1.4.8 B2: source path of the currently active custom theme. Captured
|
||||
// v1.4.8: source path of the currently active custom theme. Captured
|
||||
// at Switch() time so RefreshActiveIfStale does not have to reconstruct
|
||||
// a filename from the slug -- custom theme filenames are not required
|
||||
// to match the slug they declare in the JSON body. Null when the active
|
||||
@@ -33,7 +33,7 @@ public sealed class ThemeRegistry
|
||||
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
|
||||
private DateTime _lastActiveStamp = DateTime.MinValue;
|
||||
|
||||
// PM-1 crossfade state. Switch() captures the previous AbgrCache as a
|
||||
// crossfade state. Switch() captures the previous AbgrCache as a
|
||||
// VALUE-COPY (not a Theme reference) -- the built-in singletons share
|
||||
// their RecomputeAbgrCache identity, so a reference would mutate
|
||||
// alongside the new active. _crossfadeStartTickMs == long.MinValue
|
||||
@@ -115,7 +115,7 @@ public sealed class ThemeRegistry
|
||||
// ThemeImportExportRow opens this path via Process.Start.
|
||||
public string? CustomThemesDir => _customThemesDir;
|
||||
|
||||
// Read-only enumeration of all built-in theme slugs. T2 ThemePickerCategoryStep
|
||||
// Read-only enumeration of all built-in theme slugs. ThemePickerCategoryStep
|
||||
// diffs this set against ThemePicker.CategoryMapSlugs to enforce coverage.
|
||||
public IEnumerable<string> BuiltinSlugs => _builtIns.Keys;
|
||||
|
||||
@@ -293,7 +293,7 @@ public sealed class ThemeRegistry
|
||||
// (see `Switch` built-in-first lookup), so saving a custom file under
|
||||
// a built-in slug persists the file but leaves the built-in active —
|
||||
// looks green, behaves broken. ColorPicker DrawIdleState forks
|
||||
// built-in themes into a custom slug before BeginEditing, M6
|
||||
// built-in themes into a custom slug before BeginEditing,
|
||||
// ImportFromPath renames built-in-colliding imports to <slug>_imported.
|
||||
// New call-sites must either fork first or rename to a non-built-in slug.
|
||||
public bool SaveEditingBuffer(out string targetPath)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace HellionChat.Themes;
|
||||
|
||||
// Pure stale-check for the v1.4.8 B2 theme-auto-refresh-on-active path.
|
||||
// Pure stale-check for the v1.4.8 theme-auto-refresh-on-active path.
|
||||
// Lives in a free helper class so the Build-Suite can exercise the diff
|
||||
// rules without instantiating ThemeRegistry (which touches the Dalamud
|
||||
// log proxy and the filesystem). The rules:
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace HellionChat.Ui.Components;
|
||||
|
||||
// B2 (PERF-B2): variable-height clip plan. ImGuiListClipper needs a constant
|
||||
// Variable-height clip plan. ImGuiListClipper needs a constant
|
||||
// row height, and since v1.10.0 neither density has one (compact rows wrap
|
||||
// too), so both compute a plan from the cached per-row heights: a lead dummy
|
||||
// for the rows above
|
||||
|
||||
@@ -34,11 +34,11 @@ internal sealed class ChunkRenderer
|
||||
// names change every plugin reload to avoid stable cross-session linkage.
|
||||
_salt = new Random().Next().ToString();
|
||||
|
||||
// Not yet consumed in C2/C3; E-task wiring will likely add log call-sites later.
|
||||
// No call sites yet; logging here will likely come later.
|
||||
_ = _logger;
|
||||
}
|
||||
|
||||
// B2-1/B2-2 render-observability: the formatted sender text the real draw
|
||||
// render-observability: the formatted sender text the real draw
|
||||
// path actually produced (post-ForDisplay). A SelfTest reads this after
|
||||
// driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat
|
||||
// reached the real render entry — never the helper in isolation. null until
|
||||
@@ -52,7 +52,7 @@ internal sealed class ChunkRenderer
|
||||
float lineWidth = 0f
|
||||
)
|
||||
{
|
||||
// UI-7: render a copy with the sender name reformatted per the user's
|
||||
// Render a copy with the sender name reformatted per the user's
|
||||
// display options. Skipped in screenshot mode so the name-anonymising
|
||||
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns
|
||||
// the list unchanged when nothing applies, so non-sender lists and the
|
||||
|
||||
@@ -64,7 +64,7 @@ internal sealed class InputBar
|
||||
private bool _wasInputTextHovered;
|
||||
private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value.
|
||||
|
||||
// UI-11 plugin-disclosure arm-and-hold: holds the buffer that armed the
|
||||
// plugin-disclosure arm-and-hold: holds the buffer that armed the
|
||||
// disclosure warning. null = not armed. Compared by value so an edit
|
||||
// re-arms and a resend on the identical buffer goes through. 1.5.6 parity
|
||||
// (ChatInputBar 1d3b429:27).
|
||||
@@ -196,7 +196,7 @@ internal sealed class InputBar
|
||||
ImGui.SameLine();
|
||||
DrawQuickButtons();
|
||||
|
||||
// UI-11: yellow inline warning while a plugin-only-glyph message is
|
||||
// Yellow inline warning while a plugin-only-glyph message is
|
||||
// armed-and-held (buffer unchanged since it armed). Renders on its own
|
||||
// line below the input row. 1.5.6 parity (ChatInputBar 1d3b429:93-103).
|
||||
if (
|
||||
@@ -589,7 +589,7 @@ internal sealed class InputBar
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
// UI-11: plugin-disclosure arm-and-hold. Arm + scan on the RAW
|
||||
// Plugin-disclosure arm-and-hold. Arm + scan on the RAW
|
||||
// _pendingMessage (NOT the trimmed `text`) so the Draw warning gate
|
||||
// (_pendingMessage == _disclosureArmedBuffer) matches byte-for-byte even
|
||||
// when the buffer has leading/trailing whitespace. 1.5.6 armed/held/
|
||||
|
||||
@@ -18,13 +18,13 @@ internal sealed class MessageList
|
||||
|
||||
private PayloadHandler? _handler;
|
||||
|
||||
// B3-5: scroll-to-bottom state. Per-instance, so pop-out windows (own
|
||||
// Scroll-to-bottom state. Per-instance, so pop-out windows (own
|
||||
// MessageList instance, PluginHostFactory.cs:263-266) isolate automatically —
|
||||
// the old 1.5.6 updateScrollState flag is NOT needed here.
|
||||
private bool _scrolledUp;
|
||||
private bool _scrollToBottomRequested;
|
||||
|
||||
// B2: the height cache is only valid while these inputs are unchanged.
|
||||
// The height cache is only valid while these inputs are unchanged.
|
||||
// FontManager's own fingerprint covers font sizes only, not density / the two
|
||||
// name-display modes / width — a stale height would misplace the clipper dummies.
|
||||
// Per tab, not per list: the old single field let a width change in tab A mark
|
||||
@@ -73,12 +73,12 @@ internal sealed class MessageList
|
||||
return snap;
|
||||
}
|
||||
|
||||
// SelfTest hook (B3-5 reset-invariant, REQUIRED — not optional). Lets
|
||||
// SelfTest hook (reset-invariant, REQUIRED — not optional). Lets
|
||||
// ScrollSnapDecisionStep flip the request flag without a real click, so the
|
||||
// post-snap reset can be asserted; without it only the OR branch is testable.
|
||||
internal void RequestScrollToBottomForSelfTest() => _scrollToBottomRequested = true;
|
||||
|
||||
// SelfTest hook (B2): runs the real planner against a caller fixture so the
|
||||
// SelfTest hook: runs the real planner against a caller fixture so the
|
||||
// step asserts the plan without a live scroll child (GetScrollY is garbage headless).
|
||||
internal CardClipPlan PlanCardClipForSelfTest(
|
||||
IReadOnlyList<float> heights,
|
||||
@@ -86,7 +86,7 @@ internal sealed class MessageList
|
||||
float viewportHeight
|
||||
) => CardClipPlanner.Plan(heights, scrollY, viewportHeight);
|
||||
|
||||
// SelfTest hook (B2): drives the live invalidation, returns the tab's remaining
|
||||
// SelfTest hook: drives the live invalidation, returns the tab's remaining
|
||||
// cached-height count so the step can assert the drop. nowMs is a parameter so
|
||||
// the step can step past the settle window without sleeping (v1.10.0).
|
||||
internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs)
|
||||
@@ -165,7 +165,7 @@ internal sealed class MessageList
|
||||
|
||||
MeasureTimestampColumn(tab);
|
||||
|
||||
// B2: drop stale cached heights before the snapshot draw. Both densities
|
||||
// Drop stale cached heights before the snapshot draw. Both densities
|
||||
// need this now -- compact rows are not constant height either, they wrap.
|
||||
// Width read here while it is valid.
|
||||
InvalidateHeightCacheIfLayoutChanged(
|
||||
@@ -187,7 +187,7 @@ internal sealed class MessageList
|
||||
var frozen = _fingerprintGates[tab.Identifier].IsPending;
|
||||
DrawRows(tab, messages, compact ? _drawCompactRow : _drawCardRow, frozen);
|
||||
|
||||
// B3-5: scroll values are frame-constant inside the child, so this
|
||||
// Scroll values are frame-constant inside the child, so this
|
||||
// reflects the current frame's state wherever it runs; kept after the
|
||||
// render to mirror the 1.5.6 end-of-DrawMessageLog placement.
|
||||
_scrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f;
|
||||
@@ -282,7 +282,7 @@ internal sealed class MessageList
|
||||
ImGui.SetCursorPos(origin with { X = origin.X + _stampColumnWidth });
|
||||
}
|
||||
|
||||
// B3-5: Discord-style full-width bar pinned to the bottom edge of the
|
||||
// Discord-style full-width bar pinned to the bottom edge of the
|
||||
// visible region while the user is scrolled up. Geometry comes from window
|
||||
// pos + size (visible region), never from the content flow: when scrolled
|
||||
// up the visible bottom sits above the content bottom, so the
|
||||
@@ -335,7 +335,7 @@ internal sealed class MessageList
|
||||
|
||||
private void DrawCompactRow(Message message, string? previousStamp)
|
||||
{
|
||||
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path
|
||||
// Render the sender through DrawChunks (the name-aware path
|
||||
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
||||
// flat SenderSource.TextValue string. message.Sender already carries the
|
||||
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
||||
@@ -566,7 +566,7 @@ internal sealed class MessageList
|
||||
|
||||
private void DrawCardRow(Message message, string? previousStamp)
|
||||
{
|
||||
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line
|
||||
// Sender via DrawChunks (name-aware path), on its own line
|
||||
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
|
||||
// SameLine after the sender). The 1.5.6 channel-colour push on the
|
||||
// sender is deferred styling polish (deferred to v1.9.0); plain
|
||||
|
||||
@@ -133,7 +133,7 @@ internal sealed class AboutTab
|
||||
LastHonorificStatusKey = kind.ToString();
|
||||
var colors = _themes.Active.Colors;
|
||||
|
||||
// Null-safety via the `is { } v` pattern, never `.Value` raw (spec SEC-2):
|
||||
// Null-safety via the `is { } v` pattern, never `.Value` raw :
|
||||
// the version is bound only on the arms that have it; the impossible
|
||||
// Detected/Incompatible-without-version state falls through to default.
|
||||
switch (kind)
|
||||
|
||||
@@ -29,7 +29,7 @@ internal sealed class ThemePicker
|
||||
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false),
|
||||
];
|
||||
|
||||
// T2 ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
|
||||
// ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
|
||||
// to enforce coverage. Kept on the static map so the test does not pierce instance state.
|
||||
internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ internal sealed class Sidebar
|
||||
{
|
||||
public const float IconOnlyWidth = 38f;
|
||||
|
||||
// B1-3a: expanded sidebar width is user-configurable (Config.SidebarWidth),
|
||||
// Expanded sidebar width is user-configurable (Config.SidebarWidth),
|
||||
// clamped to these bounds (matches the ChannelsTab slider range). Replaces
|
||||
// the old fixed 150px ExpandedWidth constant.
|
||||
public const float MinSidebarWidth = 40f;
|
||||
@@ -38,7 +38,7 @@ internal sealed class Sidebar
|
||||
// previously active one -- that row was still active when it was painted.
|
||||
internal int LastRenderedActiveSurfaceCount { get; private set; }
|
||||
|
||||
// B3-2 render observability: counts greeted glyphs actually drawn this frame.
|
||||
// render observability: counts greeted glyphs actually drawn this frame.
|
||||
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
|
||||
// The SelfTest reads it after driving the real Draw — no dead service roundtrip.
|
||||
internal int LastRenderedGreetedGlyphCount;
|
||||
@@ -48,7 +48,7 @@ internal sealed class Sidebar
|
||||
// beside it.
|
||||
private const float PinGlyphScale = 0.6f;
|
||||
|
||||
// B3-4 render observability: section headers actually drawn this frame.
|
||||
// render observability: section headers actually drawn this frame.
|
||||
// Incremented only in the real header branch; reset at Draw start.
|
||||
internal int LastDrawnSectionHeaderCount;
|
||||
|
||||
@@ -149,7 +149,7 @@ internal sealed class Sidebar
|
||||
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
// B3-4 sectioned render order (1.5.6 parity): persistent → pinned
|
||||
// sectioned render order (1.5.6 parity): persistent → pinned
|
||||
// TempTabs → unpinned TempTabs. Only the display sequence regroups;
|
||||
// the tab list itself stays untouched and every row keeps its
|
||||
// ORIGINAL list index for PushID, so an open context-menu popup
|
||||
@@ -250,7 +250,7 @@ internal sealed class Sidebar
|
||||
// Only split off a separate pop-out hit area when there's room for
|
||||
// both buttons. Below that, the whole row stays as a single
|
||||
// selectable strip without the pop-out affordance.
|
||||
// A3: gate the pop-out affordance on the expanded sidebar too. In
|
||||
// Gate the pop-out affordance on the expanded sidebar too. In
|
||||
// icon-only mode avail still clears the width threshold, which used to
|
||||
// paint the pop-out glyph over the tab icon. The row stays a single
|
||||
// selectable strip when collapsed; right-click pop-out is unaffected.
|
||||
@@ -476,7 +476,7 @@ internal sealed class Sidebar
|
||||
// The hit area sits at the LEFT edge of the row, but the item must
|
||||
// be submitted AFTER TabContextMenu.Draw — any interactive item
|
||||
// between the row button and the popup call would steal the
|
||||
// right-click trigger (B3-1 ordering constraint).
|
||||
// right-click trigger (ordering constraint).
|
||||
ImGui.SetCursorScreenPos(origin);
|
||||
|
||||
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
|
||||
|
||||
@@ -82,7 +82,7 @@ internal static class TabContextMenu
|
||||
ClearPendingRename();
|
||||
}
|
||||
|
||||
// Per-tab notification sound (B3-3). The checkbox gates the picker so
|
||||
// Per-tab notification sound. The checkbox gates the picker so
|
||||
// tabs that never want a sound keep the popup short.
|
||||
if (
|
||||
ImGui.Checkbox(
|
||||
|
||||
@@ -36,7 +36,7 @@ public class DbViewer : Window
|
||||
private int CurrentPage = 1;
|
||||
private string SimpleSearchTerm = "";
|
||||
|
||||
// v1.4.8 H2: opt-in full-text search across the whole DB via FTS5.
|
||||
// v1.4.8: opt-in full-text search across the whole DB via FTS5.
|
||||
// Transient UI state (per-session), not persisted -- users opt in fresh
|
||||
// every time so they always see the page-filter as the default mode.
|
||||
private bool UseFullTextSearch;
|
||||
@@ -232,7 +232,7 @@ public class DbViewer : Window
|
||||
tooltipRight: Language.Page_ArrowRight_Tooltip
|
||||
);
|
||||
|
||||
// Full-text search toggle (v1.4.8 H2). IsFtsIndexBuilt is a cached
|
||||
// Full-text search toggle (v1.4.8). IsFtsIndexBuilt is a cached
|
||||
// volatile bool in MessageStore -- single field read per frame, no
|
||||
// SELECT count(*). ImRaii.Disabled blocks any click while the index
|
||||
// is still being built, so no defensive force-off branch needed
|
||||
|
||||
@@ -168,7 +168,7 @@ internal sealed class InputPreview : Window
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Preview_Header);
|
||||
|
||||
// Primary path (A2) resets the Lender counter in MainWindow.Draw();
|
||||
// Primary path resets the Lender counter in MainWindow.Draw();
|
||||
// this fallback covers the edge-case where MainWindow is closed but
|
||||
// InputPreview is still open, preventing handler pool growth.
|
||||
if (!_mainWindow.IsOpen)
|
||||
|
||||
@@ -44,7 +44,7 @@ internal sealed class ChannelPopoutPool
|
||||
// A popped tab gets its own input bar, so strip stale tell state first —
|
||||
// otherwise a popped-out stale-tell tab would be a send surface that
|
||||
// bypasses the click-path activation strip. Previous = the main window's
|
||||
// active tab; popping the active tab itself must not strip (TR-4 guard).
|
||||
// active tab; popping the active tab itself must not strip (guard).
|
||||
TabLifecycleHelpers.OnTabActivated(tab, Plugin.Instance.MainWindow?.ActiveTab);
|
||||
|
||||
var slot = _slots.TryReserve(tab.Identifier);
|
||||
|
||||
@@ -103,7 +103,7 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
|
||||
}
|
||||
|
||||
// IFocusableChatWindow — this pop-out's own InputBar carries the focus state
|
||||
// the keybind tail checks when deciding whether to route at this surface (C3).
|
||||
// the keybind tail checks when deciding whether to route at this surface.
|
||||
public bool HasFocusedInput => _input.IsFocused;
|
||||
|
||||
// Arm-and-hold the one-frame Activate flag; the pop-out's Draw applies the
|
||||
|
||||
@@ -82,14 +82,14 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
||||
RespectCloseHotkey = false;
|
||||
}
|
||||
|
||||
// UI-12: per-window focus-dependent opacity. ResolveBgAlpha stays guard-free
|
||||
// Per-window focus-dependent opacity. ResolveBgAlpha stays guard-free
|
||||
// and pure so the self-test can drive it directly; PreDraw owns the guard +
|
||||
// wiring. 1.5.6 parity (focused → WindowOpacity, unfocused →
|
||||
// WindowOpacityInactive, ChatLogWindow.PreOpenCheck 1d3b429:724).
|
||||
internal float ResolveBgAlpha(bool isFocused) =>
|
||||
isFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive;
|
||||
|
||||
// B1-2 / P7: rebuild flags from a fresh base every frame so toggling
|
||||
// Rebuild flags from a fresh base every frame so toggling
|
||||
// CanMove/CanResize/ShowTitleBar back on actually CLEARS NoMove/NoResize/
|
||||
// NoTitleBar (not accumulating). Move/resize/title-bar logic as 1.5.6
|
||||
// (ChatLogWindow.PreOpenCheck 1d3b429:703-710); base flags = today's
|
||||
@@ -240,7 +240,7 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
||||
}
|
||||
|
||||
// IFocusableChatWindow — the keybind tail resolves which surface owns the
|
||||
// input focus before routing a channel-set/REPLY/prefill at it (C3).
|
||||
// input focus before routing a channel-set/REPLY/prefill at it.
|
||||
public bool HasFocusedInput => _input.IsFocused;
|
||||
|
||||
// Arm-and-hold: field writes only, safe from the framework thread; the draw
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.Windows;
|
||||
|
||||
// `internal` to match the Plugin.SettingsWindow property in W2; `public` here
|
||||
// `internal` to match the Plugin.SettingsWindow property; `public` here
|
||||
// would raise CS0053 against the internal members. Matches MainWindow shape.
|
||||
internal sealed class SettingsWindow : Window
|
||||
{
|
||||
|
||||
@@ -91,7 +91,7 @@ internal static class ColourUtil
|
||||
}
|
||||
|
||||
// Modulates the alpha byte of an ABGR color by a factor in [0, 1].
|
||||
// RGB stays intact. Used by the PM-3 hover-lerp path where each
|
||||
// RGB stays intact. Used by the hover-lerp path where each
|
||||
// frame produces a fractional alpha value but the colour itself
|
||||
// must not shift.
|
||||
internal static uint ApplyAlpha(uint abgr, float alphaFactor)
|
||||
@@ -103,7 +103,7 @@ internal static class ColourUtil
|
||||
}
|
||||
|
||||
// Mixes an ABGR colour's RGB channels toward white (0xFF) by factor t in
|
||||
// [0, 1]; the alpha byte is left untouched. A1 hover-sheen accent-tint:
|
||||
// [0, 1]; the alpha byte is left untouched. Hover-sheen accent tint:
|
||||
// a low factor nudges the sweep toward the element's accent hue without
|
||||
// going fully saturated (effect level stays "subtle"). RGB-only on
|
||||
// purpose -- DrawHoverSheen owns the alpha falloff.
|
||||
|
||||
@@ -90,10 +90,9 @@ internal static class ImGuiUtil
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled]))
|
||||
ImGui.TextUnformatted("(?)");
|
||||
|
||||
// AllowWhenDisabled — ohne das Flag liefert IsItemHovered bei
|
||||
// ausgegrauten Settings false, der User könnte nicht mehr lesen
|
||||
// warum eine Option nicht aktiv ist. Genau dann braucht er den
|
||||
// Hover-Tooltip aber am dringendsten.
|
||||
// AllowWhenDisabled, because without it IsItemHovered returns false on a
|
||||
// greyed-out setting -- and a reader who cannot find out why an option is
|
||||
// inactive is exactly the reader who needs the tooltip most.
|
||||
if (!ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
return;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using HellionChat._Helpers;
|
||||
|
||||
namespace HellionChat.Util;
|
||||
|
||||
// UI-7: produces a render-only view of a chunk list with the sender's name
|
||||
// Produces a render-only view of a chunk list with the sender's name
|
||||
// reformatted per the user's WorldSuffixMode / NameFormMode. Called from
|
||||
// ChatLogWindow.DrawChunks on every draw — it never mutates the input, so the
|
||||
// stored message (the Sender BLOB in the DB) stays byte-for-byte unchanged and
|
||||
@@ -76,7 +76,7 @@ internal static class SenderNameDisplay
|
||||
// text, and any cross-world icon) with one formatted chunk that keeps
|
||||
// the PlayerPayload link so the name stays clickable. Dropping the
|
||||
// original sender icon is an accepted trade-off and only happens once
|
||||
// the user moves UI-7 off its defaults.
|
||||
// the user moves off its defaults.
|
||||
// Channel brackets and colons are ChunkSource.None wrappers outside the
|
||||
// Sender span — they are preserved untouched by the copy loops below.
|
||||
var copy = new List<Chunk>(chunks.Count);
|
||||
|
||||
@@ -72,7 +72,7 @@ internal static class TabLifecycleHelpers
|
||||
|
||||
public static bool ShouldStripOnSave(Tab t) => IsInUnpinnedPool(t);
|
||||
|
||||
// GP-04: clear every Tab.PopOut at load time. The pool binds later, so at
|
||||
// Clear every Tab.PopOut at load time. The pool binds later, so at
|
||||
// load NO tab can own a slot — a persisted PopOut=true is always a stale flag
|
||||
// with no window. Unconditional (pinned included) because pinned TempTabs
|
||||
// survive the load and are the main stale-flag source; a !IsPinned filter
|
||||
@@ -91,7 +91,7 @@ internal static class TabLifecycleHelpers
|
||||
// partner-name label) so a normal typed line cannot route as a silent /tell
|
||||
// to the old partner — the same privacy guard StripTellBindingOnPromote
|
||||
// applies on promote. Re-activating the already-active tab must NOT strip
|
||||
// (a live game-tell would lose its context, TR-4); a tab carrying its own
|
||||
// (a live game-tell would lose its context); a tab carrying its own
|
||||
// Tab.TellTarget is a real tell binding (leg1) and is left intact.
|
||||
internal static void OnTabActivated(Tab tab, Tab? previous)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace HellionChat._Helpers;
|
||||
|
||||
// UI-11 pure decision helper: does a message about to be sent carry a glyph
|
||||
// pure decision helper: does a message about to be sent carry a glyph
|
||||
// that only renders correctly for players running HellionChat or a similar
|
||||
// plugin? Those are FFXIV Private-Use-Area icon codepoints (the same range
|
||||
// SeIconChar covers); a recipient without a plugin sees an empty box.
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace HellionChat._Helpers;
|
||||
|
||||
// UI-7 pure decision helper: builds the display string for a sender's name +
|
||||
// pure decision helper: builds the display string for a sender's name +
|
||||
// world per the user's NameFormMode / WorldSuffixMode. Dalamud-free so the
|
||||
// Build Suite can cover every combination; SenderNameDisplay feeds it the name
|
||||
// and world it pulled from the PlayerPayload.
|
||||
|
||||
Reference in New Issue
Block a user