feat(sidebar): pinned section, dimmed pin glyph, configurable width
Smoke-test round 3 feedback from Jin:
- Sidebar now groups tabs into three sections rendered in this order:
persistent → pinned TempTabs → unpinned TempTabs. Each TempTab
section carries its own divider header ("Angepinnt (n)" / "Aktive
Tells (n)"). Plugin.Config.Tabs order is untouched — only the
display order changes, so tabI still mirrors the real index and
LastTab/WantedTab stay consistent.
- The thumbtack glyph overlay on a pinned tab dropped from accent
colour at full alpha to TextMuted at ~47% alpha. The section header
is now the primary discoverability cue; the glyph is just a per-tab
confirmation hint.
- Sidebar width is now a Config field (default 44, range 44-160).
Slider lives in Theme & Layout under the existing Sidebar-Tab-View
toggle. The icon button inside each row stretches with the width so
a widened sidebar doesn't leave the icon floating in dead space.
This commit is contained in:
@@ -113,6 +113,11 @@ public class Configuration : IPluginConfiguration
|
|||||||
public int AutoTellTabsLimit = 15;
|
public int AutoTellTabsLimit = 15;
|
||||||
public bool AutoTellTabsCompactDisplay;
|
public bool AutoTellTabsCompactDisplay;
|
||||||
public int AutoTellTabsHistoryPreload = 20;
|
public int AutoTellTabsHistoryPreload = 20;
|
||||||
|
|
||||||
|
// Sidebar width in pixels. Default 44 mirrors the icon-only layout from
|
||||||
|
// v1.2.0; users can widen up to 160 to fit a section-header line like
|
||||||
|
// "Active Tells (3)" without truncation.
|
||||||
|
public int SidebarWidth = 44;
|
||||||
public bool AutoTellTabsShowGreetedToggle;
|
public bool AutoTellTabsShowGreetedToggle;
|
||||||
public bool SeenPopOutInputHint;
|
public bool SeenPopOutInputHint;
|
||||||
public bool PopOutInputEnabled = true;
|
public bool PopOutInputEnabled = true;
|
||||||
@@ -339,6 +344,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
AutoTellTabsLimit = other.AutoTellTabsLimit;
|
AutoTellTabsLimit = other.AutoTellTabsLimit;
|
||||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||||
|
SidebarWidth = other.SidebarWidth;
|
||||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||||
|
|
||||||
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||||
|
|||||||
@@ -177,6 +177,9 @@ internal class HellionStrings
|
|||||||
internal static string PinTab_LimitReached => Get(nameof(PinTab_LimitReached));
|
internal static string PinTab_LimitReached => Get(nameof(PinTab_LimitReached));
|
||||||
internal static string PinTab_PinnedTooltip => Get(nameof(PinTab_PinnedTooltip));
|
internal static string PinTab_PinnedTooltip => Get(nameof(PinTab_PinnedTooltip));
|
||||||
internal static string PinTab_PinTooltip => Get(nameof(PinTab_PinTooltip));
|
internal static string PinTab_PinTooltip => Get(nameof(PinTab_PinTooltip));
|
||||||
|
internal static string PinTab_SectionHeader => Get(nameof(PinTab_SectionHeader));
|
||||||
|
internal static string Settings_ThemeAndLayout_SidebarWidth_Name => Get(nameof(Settings_ThemeAndLayout_SidebarWidth_Name));
|
||||||
|
internal static string Settings_ThemeAndLayout_SidebarWidth_Description => Get(nameof(Settings_ThemeAndLayout_SidebarWidth_Description));
|
||||||
|
|
||||||
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
||||||
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
||||||
|
|||||||
@@ -404,6 +404,15 @@
|
|||||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||||
<value>Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person.</value>
|
<value>Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PinTab_SectionHeader" xml:space="preserve">
|
||||||
|
<value>Angepinnt</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ThemeAndLayout_SidebarWidth_Name" xml:space="preserve">
|
||||||
|
<value>Sidebar-Breite</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ThemeAndLayout_SidebarWidth_Description" xml:space="preserve">
|
||||||
|
<value>Breite der Tab-Sidebar in Pixeln. Default (44 px) ist Icon-only; breiter machen damit Sektion-Header wie „Aktive Tells (3)" nicht abgeschnitten werden.</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat-Einstellungstab) -->
|
<!-- Hellion Chat — Auto-Tell-Tabs (Chat-Einstellungstab) -->
|
||||||
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||||
|
|||||||
@@ -398,6 +398,15 @@
|
|||||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||||
<value>Pinned tabs survive relog and stay bound to this conversation partner.</value>
|
<value>Pinned tabs survive relog and stay bound to this conversation partner.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PinTab_SectionHeader" xml:space="preserve">
|
||||||
|
<value>Pinned</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ThemeAndLayout_SidebarWidth_Name" xml:space="preserve">
|
||||||
|
<value>Sidebar width</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ThemeAndLayout_SidebarWidth_Description" xml:space="preserve">
|
||||||
|
<value>Width of the tab sidebar in pixels. The default (44 px) is icon-only; widen it to fit the section headers like "Active Tells (3)" without truncation.</value>
|
||||||
|
</data>
|
||||||
<data name="PinTab_LimitReached" xml:space="preserve">
|
<data name="PinTab_LimitReached" xml:space="preserve">
|
||||||
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -1673,6 +1673,30 @@ public sealed class ChatLogWindow : Window
|
|||||||
Plugin.WantedTab = null;
|
Plugin.WantedTab = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sidebar render order: persistent tabs in their original Plugin.Config.Tabs
|
||||||
|
// position, then pinned TempTabs, then unpinned TempTabs. Returns indices
|
||||||
|
// into Plugin.Config.Tabs so tabI in the loop body still mirrors the real
|
||||||
|
// list position (LastTab / WantedTab stay consistent).
|
||||||
|
private static List<int> BuildSidebarRenderOrder()
|
||||||
|
{
|
||||||
|
var tabs = Plugin.Config.Tabs;
|
||||||
|
var persistent = new List<int>(tabs.Count);
|
||||||
|
var pinned = new List<int>();
|
||||||
|
var unpinned = new List<int>();
|
||||||
|
for (var i = 0; i < tabs.Count; i++)
|
||||||
|
{
|
||||||
|
if (TabLifecycleHelpers.IsInPinnedPool(tabs[i]))
|
||||||
|
pinned.Add(i);
|
||||||
|
else if (TabLifecycleHelpers.IsInUnpinnedPool(tabs[i]))
|
||||||
|
unpinned.Add(i);
|
||||||
|
else
|
||||||
|
persistent.Add(i);
|
||||||
|
}
|
||||||
|
persistent.AddRange(pinned);
|
||||||
|
persistent.AddRange(unpinned);
|
||||||
|
return persistent;
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawTabSidebar()
|
private void DrawTabSidebar()
|
||||||
{
|
{
|
||||||
var currentTab = -1;
|
var currentTab = -1;
|
||||||
@@ -1685,7 +1709,8 @@ public sealed class ChatLogWindow : Window
|
|||||||
if (!tabTable.Success)
|
if (!tabTable.Success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.TableSetupColumn("tabs", ImGuiTableColumnFlags.WidthFixed, 44f);
|
var sidebarWidth = Math.Clamp(Plugin.Config.SidebarWidth, 44, 160);
|
||||||
|
ImGui.TableSetupColumn("tabs", ImGuiTableColumnFlags.WidthFixed, sidebarWidth);
|
||||||
ImGui.TableSetupColumn("chat", ImGuiTableColumnFlags.WidthStretch, 1);
|
ImGui.TableSetupColumn("chat", ImGuiTableColumnFlags.WidthStretch, 1);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
@@ -1704,23 +1729,42 @@ public sealed class ChatLogWindow : Window
|
|||||||
ImGui.Dummy(new Vector2(0, ImGui.GetFrameHeightWithSpacing()));
|
ImGui.Dummy(new Vector2(0, ImGui.GetFrameHeightWithSpacing()));
|
||||||
|
|
||||||
var previousTab = Plugin.CurrentTab;
|
var previousTab = Plugin.CurrentTab;
|
||||||
// Divider rendered once before the first temp tab with a live unit counter.
|
// Render order: persistent → pinned TempTabs → unpinned TempTabs.
|
||||||
|
// Underlying Plugin.Config.Tabs order is untouched (tabI mirrors
|
||||||
|
// the real list index), only the display sequence groups by
|
||||||
|
// section so each section can carry its own divider header.
|
||||||
|
var renderOrder = BuildSidebarRenderOrder();
|
||||||
|
var pinnedHeaderRendered = false;
|
||||||
var tempTabHeaderRendered = false;
|
var tempTabHeaderRendered = false;
|
||||||
var tempTabCount = Plugin.Config.Tabs.Count(t => t.IsTempTab);
|
var pinnedCount = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool);
|
||||||
|
var unpinnedTempCount = Plugin.Config.Tabs.Count(
|
||||||
|
TabLifecycleHelpers.IsInUnpinnedPool
|
||||||
|
);
|
||||||
|
|
||||||
for (var tabI = 0; tabI < Plugin.Config.Tabs.Count; tabI++)
|
foreach (var tabI in renderOrder)
|
||||||
{
|
{
|
||||||
var tab = Plugin.Config.Tabs[tabI];
|
var tab = Plugin.Config.Tabs[tabI];
|
||||||
if (tab.PopOut)
|
if (tab.PopOut)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (tab.IsTempTab && !tempTabHeaderRendered)
|
if (TabLifecycleHelpers.IsInPinnedPool(tab) && !pinnedHeaderRendered)
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (!Plugin.Config.AutoTellTabsCompactDisplay)
|
if (!Plugin.Config.AutoTellTabsCompactDisplay)
|
||||||
{
|
{
|
||||||
ImGui.TextDisabled(
|
ImGui.TextDisabled(
|
||||||
$"{HellionStrings.AutoTellTabs_SectionHeader} ({tempTabCount})"
|
$"{HellionStrings.PinTab_SectionHeader} ({pinnedCount})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pinnedHeaderRendered = true;
|
||||||
|
}
|
||||||
|
else if (TabLifecycleHelpers.IsInUnpinnedPool(tab) && !tempTabHeaderRendered)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
if (!Plugin.Config.AutoTellTabsCompactDisplay)
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled(
|
||||||
|
$"{HellionStrings.AutoTellTabs_SectionHeader} ({unpinnedTempCount})"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
tempTabHeaderRendered = true;
|
tempTabHeaderRendered = true;
|
||||||
@@ -1809,9 +1853,12 @@ public sealed class ChatLogWindow : Window
|
|||||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(iconColor)))
|
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(iconColor)))
|
||||||
using (Plugin.FontManager.FontAwesome.Push())
|
using (Plugin.FontManager.FontAwesome.Push())
|
||||||
{
|
{
|
||||||
|
// Button stretches with the configured sidebar width so a
|
||||||
|
// user-widened sidebar feels intentional, not a 36px icon
|
||||||
|
// floating in empty space.
|
||||||
clicked = ImGui.Button(
|
clicked = ImGui.Button(
|
||||||
$"{icon.ToIconString()}##sidebar-tab-{tabI}",
|
$"{icon.ToIconString()}##sidebar-tab-{tabI}",
|
||||||
new Vector2(36f, ImGui.GetFrameHeight())
|
new Vector2(sidebarWidth - 8f, ImGui.GetFrameHeight())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1871,22 +1918,23 @@ public sealed class ChatLogWindow : Window
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin indicator: small thumbtack glyph top-left of the icon.
|
// Pin indicator: subtle thumbtack glyph top-left of the icon.
|
||||||
// Sits opposite the unread dot so they never collide.
|
// Muted colour because the "Pinned" section header already
|
||||||
|
// groups these tabs visually — this is just a per-tab
|
||||||
|
// confirmation glyph, not the primary discoverability cue.
|
||||||
if (tab.IsPinned)
|
if (tab.IsPinned)
|
||||||
{
|
{
|
||||||
var min = ImGui.GetItemRectMin();
|
var min = ImGui.GetItemRectMin();
|
||||||
const float pinPadding = 2f;
|
const float pinPadding = 1f;
|
||||||
var pinPos = new Vector2(min.X + pinPadding, min.Y + pinPadding);
|
var pinPos = new Vector2(min.X + pinPadding, min.Y + pinPadding);
|
||||||
|
var pinColor = theme.Colors.TextMuted;
|
||||||
|
// Dim further so the glyph reads as a hint, not a badge.
|
||||||
|
var pinAbgr = ColourUtil.RgbaToAbgr(pinColor) & 0x77FFFFFFu;
|
||||||
using (Plugin.FontManager.FontAwesome.Push())
|
using (Plugin.FontManager.FontAwesome.Push())
|
||||||
{
|
{
|
||||||
ImGui
|
ImGui
|
||||||
.GetWindowDrawList()
|
.GetWindowDrawList()
|
||||||
.AddText(
|
.AddText(pinPos, pinAbgr, FontAwesomeIcon.Thumbtack.ToIconString());
|
||||||
pinPos,
|
|
||||||
ColourUtil.RgbaToAbgr(theme.Colors.Accent),
|
|
||||||
FontAwesomeIcon.Thumbtack.ToIconString()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,26 @@ internal sealed class ThemeAndLayout : ISettingsTab
|
|||||||
string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName)
|
string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Mutable.SidebarTabView)
|
||||||
|
{
|
||||||
|
var sidebarWidth = Mutable.SidebarWidth;
|
||||||
|
if (
|
||||||
|
ImGui.SliderInt(
|
||||||
|
HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Name,
|
||||||
|
ref sidebarWidth,
|
||||||
|
44,
|
||||||
|
160,
|
||||||
|
$"{sidebarWidth} px"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Mutable.SidebarWidth = sidebarWidth;
|
||||||
|
}
|
||||||
|
ImGuiUtil.HelpMarker(
|
||||||
|
HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|||||||
Reference in New Issue
Block a user