feat(sidebar): per-tell hashed icon variety + unread-dot indicator

This commit is contained in:
2026-05-05 23:36:52 +02:00
parent e6c6c02780
commit b8ed2a1ce5
4 changed files with 79 additions and 4 deletions
+43
View File
@@ -61,4 +61,47 @@ internal static class AutoTellTabTint
var hash = (uint)(key.GetHashCode() & 0x7FFFFFFF); var hash = (uint)(key.GetHashCode() & 0x7FFFFFFF);
return Palette[(int)(hash % Palette.Count)]; return Palette[(int)(hash % Palette.Count)];
} }
/// <summary>
/// Tell-spezifischer Icon-Pool. 7 visuell distinkte FontAwesome-Glyphen
/// die im Tell-Kontext sinnvoll wirken (envelope = Tell-Default, star/
/// heart/bell = personalisiert, bookmark/flag/fire = markiert/wichtig).
/// Bewusst kein cog/comment/users — die wären für System-/Group-Tabs
/// reserviert und würden im Tell-Bereich verwirrend wirken.
/// </summary>
public static readonly IReadOnlyList<string> IconPool = new[]
{
"envelope",
"star",
"heart",
"bell",
"bookmark",
"flag",
"fire",
};
/// <summary>
/// Fallback-Icon bei ungültigem Input. "envelope" passt semantisch zum
/// Tell-Kontext besser als das alte hardcoded "clock".
/// </summary>
public const string IconFallback = "envelope";
/// <summary>
/// Liefert ein konsistentes Icon-Glyph für einen Tell-Partner.
/// Nutzt einen anderen Hash-Bias als For() (Color), damit Icon und
/// Color unabhängig variieren — gibt 7 × 12 = 84 distinct Combinations.
/// </summary>
public static string IconFor(string name, uint world)
{
if (string.IsNullOrEmpty(name) || world == 0)
return IconFallback;
// Anderer Hash-Bias als For() (verschiedene Modulo-Basis): wir
// nutzen "world@name" statt "name@world" damit Icon und Color
// nicht synchron variieren. Ohne Bias-Trennung würden alle Tells
// mit derselben Color auch dasselbe Icon haben.
var key = $"{world}@{name}";
var hash = (uint)(key.GetHashCode() & 0x7FFFFFFF);
return IconPool[(int)(hash % IconPool.Count)];
}
} }
+20
View File
@@ -1591,6 +1591,26 @@ public sealed class ChatLogWindow : Window
1.5f); // leichter Rounding 1.5f); // leichter Rounding
} }
// v1.2.0 — Unread-Dot oben rechts am Icon. Sichtbar ohne Hover, damit
// User Tabs mit ungelesenen Messages sofort erkennt. Aktive Tabs haben
// per Konvention Unread = 0 (LastTab-Branch in ChatLogWindow), daher
// kollidiert der Dot nicht mit der Active-Pill.
if (!isCurrentTab && tab.UnreadMode != UnreadMode.None && tab.Unread > 0)
{
var min = ImGui.GetItemRectMin();
var max = ImGui.GetItemRectMax();
const float dotRadius = 4f;
const float dotPadding = 3f;
var dotCenter = new Vector2(
max.X - dotRadius - dotPadding,
min.Y + dotRadius + dotPadding);
ImGui.GetWindowDrawList().AddCircleFilled(
dotCenter,
dotRadius,
ColourUtil.RgbaToAbgr(theme.Colors.StatusDanger),
12);
}
// Tooltip mit Tab-Name + Unread-Counter beim Hover. // Tooltip mit Tab-Name + Unread-Counter beim Hover.
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
+4 -3
View File
@@ -58,17 +58,18 @@ internal static class TabIconGlyphResolver
/// a) bekannter Glyph → diesen Glyph /// a) bekannter Glyph → diesen Glyph
/// b) unbekannter Glyph → harter Fallback "hashtag" (User hat /// b) unbekannter Glyph → harter Fallback "hashtag" (User hat
/// bewusst etwas gesetzt, also überstimmt das die Defaults) /// bewusst etwas gesetzt, also überstimmt das die Defaults)
/// 2. Auto-Tell-Tab → "clock" /// 2. Auto-Tell-Tab → <paramref name="autoTellGlyph"/> falls
/// übergeben, sonst "clock".
/// 3. Tab-Name-Default (<see cref="NameDefaults"/>-Lookup) /// 3. Tab-Name-Default (<see cref="NameDefaults"/>-Lookup)
/// 4. Fallback "hashtag" /// 4. Fallback "hashtag"
/// </summary> /// </summary>
public static string ResolveGlyphName(Tab tab) public static string ResolveGlyphName(Tab tab, string? autoTellGlyph = null)
{ {
if (!string.IsNullOrWhiteSpace(tab.Icon)) if (!string.IsNullOrWhiteSpace(tab.Icon))
return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag"; return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag";
if (tab.IsTempTab) if (tab.IsTempTab)
return "clock"; return autoTellGlyph ?? "clock";
if (tab.Name is { } name && NameDefaults.TryGetValue(name, out var byName)) if (tab.Name is { } name && NameDefaults.TryGetValue(name, out var byName))
return byName; return byName;
+12 -1
View File
@@ -53,7 +53,18 @@ internal static class TabIconMapping
/// </summary> /// </summary>
public static FontAwesomeIcon Resolve(Tab tab) public static FontAwesomeIcon Resolve(Tab tab)
{ {
var glyph = TabIconGlyphResolver.ResolveGlyphName(tab); // v1.2.0 — Auto-Tell-Tabs bekommen ein per-Partner gehashtes
// Icon aus dem Tell-Pool. Damit unterscheiden sich parallele
// Tells nicht nur über die Color (For), sondern auch über die
// Glyph-Form. Berechnung bleibt hier (Dalamud-bound), weil
// TellTarget Dalamud-Imports hat.
string? autoTellGlyph = null;
if (tab.IsTempTab && tab.TellTarget != null && tab.TellTarget.IsSet())
{
autoTellGlyph = AutoTellTabTint.IconFor(tab.TellTarget.Name, tab.TellTarget.World);
}
var glyph = TabIconGlyphResolver.ResolveGlyphName(tab, autoTellGlyph);
return GlyphLookup.TryGetValue(glyph, out var icon) return GlyphLookup.TryGetValue(glyph, out var icon)
? icon ? icon
: FontAwesomeIcon.Hashtag; : FontAwesomeIcon.Hashtag;