Reviews of blocks C and D found five things a user would see immediately. Row fills ignored the window's own opacity. Theme surfaces are fully opaque, and GlobalStyleScope zeroes ChildBg below full opacity so WindowBg alone carries the coverage -- with the default of 0.85 that made the sidebar a solid block inside a translucent window. Idle rows now draw no fill at all, and the active and hover fills are scaled by the current window opacity. The unread badge landed on the tab icon at the default sidebar width of 44px. Right-aligning it needs roughly 70px for one digit and 90px for three, and the old placement also subtracted the popout column even when there was no popout button. It is only drawn where it clears the icon; below that a plain dot takes over, which is what the sidebar did before this cycle anyway. The same collision existed in the top-tab strip, worse: the badge sat in the trailing padding, which is 10px against a badge at least 14px wide, so it covered the label on every tab that had one. The badge is part of the tab width now, and vertically centred rather than top-aligned. The context menu's spacing guard read the pushed zero back out of GetStyle, so the max never did anything and X stayed at zero -- which is what HelpMarker's SameLine uses, so the "(?)" clung to its label. It sets both axes outright now. Section captions had all their padding above them and one pixel below, so with zero item spacing the next row started immediately under the text. Three smaller items: the tab icon was centred against the text font's line height although FontAwesome is a fixed-width handle that ignores Config.FontSizeV2; IconButton interpolated a label string per button per frame, now a PushID over a u8 literal; and the alpha scaling that had grown four copies now goes through ColourUtil.ApplyAlpha everywhere.
525 lines
22 KiB
C#
525 lines
22 KiB
C#
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using HellionChat.Code;
|
|
using HellionChat.Resources;
|
|
using HellionChat.Themes;
|
|
using HellionChat.Ui.StyleEngine;
|
|
using HellionChat.Ui.StyleEngine.Widgets;
|
|
using HellionChat.Util;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace HellionChat.Ui.Components;
|
|
|
|
// Channel-list panel pinned to the left of the chat window. Auto-switches
|
|
// between an icon-only column (38px) and an expanded column (Config.SidebarWidth) once
|
|
// the outer window crosses Config.SidebarAutoSwitchThresholdPx. The
|
|
// pop-out affordance (hover button + right-click menu) routes through the
|
|
// injected ChannelPopoutPool via TryOpen, which reserves a slot and binds
|
|
// the tab to a pre-allocated pop-out window.
|
|
internal sealed class Sidebar
|
|
{
|
|
public const float IconOnlyWidth = 38f;
|
|
|
|
// B1-3a: 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;
|
|
public const float MaxSidebarWidth = 300f;
|
|
|
|
private static float RowHeight => Metrics.SidebarRowHeight;
|
|
private static float PopOutHitWidth => Metrics.SidebarPopOutHitWidth;
|
|
private static float GreetedHitWidth => Metrics.SidebarGreetedHitWidth;
|
|
|
|
// Counts rows that got the active surface this frame. At most one, with two
|
|
// exceptions: zero when every tab is popped out (PickMainActiveTab returns
|
|
// null), and two in the frame a click lands on a row drawn after the
|
|
// 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.
|
|
// 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;
|
|
internal int LastRenderedUnreadDotCount;
|
|
|
|
// B3-4 render observability: section headers actually drawn this frame.
|
|
// Incremented only in the real header branch; reset at Draw start.
|
|
internal int LastDrawnSectionHeaderCount;
|
|
|
|
// Inline mirror of the old TabIconMapping table so the Ui layer carries
|
|
// its own glyph lookup once the standalone file is removed.
|
|
private static readonly Dictionary<string, FontAwesomeIcon> IconByName = new(
|
|
StringComparer.OrdinalIgnoreCase
|
|
)
|
|
{
|
|
["comment"] = FontAwesomeIcon.Comment,
|
|
["comments"] = FontAwesomeIcon.Comments,
|
|
["cog"] = FontAwesomeIcon.Cog,
|
|
["users"] = FontAwesomeIcon.Users,
|
|
["user-friends"] = FontAwesomeIcon.UserFriends,
|
|
["link"] = FontAwesomeIcon.Link,
|
|
["envelope"] = FontAwesomeIcon.Envelope,
|
|
["clock"] = FontAwesomeIcon.Clock,
|
|
["hashtag"] = FontAwesomeIcon.Hashtag,
|
|
["star"] = FontAwesomeIcon.Star,
|
|
["heart"] = FontAwesomeIcon.Heart,
|
|
["bell"] = FontAwesomeIcon.Bell,
|
|
["bookmark"] = FontAwesomeIcon.Bookmark,
|
|
["flag"] = FontAwesomeIcon.Flag,
|
|
["fire"] = FontAwesomeIcon.Fire,
|
|
};
|
|
|
|
private readonly ThemeRegistry _themes;
|
|
private readonly TokenResolver _resolver;
|
|
private readonly WidgetPalette _palette;
|
|
private readonly FontManager _fonts;
|
|
private readonly ILogger<Sidebar> _logger;
|
|
private readonly Windows.ChannelPopoutPool _pool;
|
|
|
|
public Sidebar(
|
|
ThemeRegistry themes,
|
|
TokenResolver resolver,
|
|
FontManager fonts,
|
|
ILogger<Sidebar> logger,
|
|
Windows.ChannelPopoutPool pool
|
|
)
|
|
{
|
|
_themes = themes;
|
|
_resolver = resolver;
|
|
_palette = new WidgetPalette(resolver);
|
|
_fonts = fonts;
|
|
_logger = logger;
|
|
_pool = pool;
|
|
}
|
|
|
|
// Both stay unscaled. The stored width and the switch threshold are user
|
|
// settings in design pixels, and SidebarModeAutoSwitchStep compares this
|
|
// return value against the raw bounds. Display scaling is applied once, at
|
|
// the single draw call site below.
|
|
public bool IsExpanded(float windowWidth) =>
|
|
windowWidth >= Plugin.Config.SidebarAutoSwitchThresholdPx;
|
|
|
|
public float GetWidth(float windowWidth) =>
|
|
IsExpanded(windowWidth)
|
|
? Math.Clamp((float)Plugin.Config.SidebarWidth, MinSidebarWidth, MaxSidebarWidth)
|
|
: IconOnlyWidth;
|
|
|
|
// Factored click logic so the SelfTest exercises the real toggle, not a direct
|
|
// MarkGreeted call (which would be a dead path the render never takes).
|
|
internal void ToggleGreetedForSelfTest(Tab tab)
|
|
{
|
|
if (Plugin.Instance.AutoTellTabsService.IsGreeted(tab))
|
|
Plugin.Instance.AutoTellTabsService.UnmarkGreeted(tab);
|
|
else
|
|
Plugin.Instance.AutoTellTabsService.MarkGreeted(tab);
|
|
}
|
|
|
|
public void Draw(float windowWidth, IReadOnlyList<Tab> tabs, ref Tab? activeTab)
|
|
{
|
|
LastRenderedGreetedGlyphCount = 0;
|
|
LastRenderedUnreadDotCount = 0;
|
|
LastDrawnSectionHeaderCount = 0;
|
|
LastRenderedActiveSurfaceCount = 0;
|
|
|
|
if (!_fonts.FontsReady)
|
|
{
|
|
// A scale change triggers a font rebuild, so this branch is really
|
|
// hit while GlobalScale is moving -- an unscaled width here makes
|
|
// the sidebar jump.
|
|
ImGui.Dummy(new Vector2(Metrics.SidebarIconOnlyWidth, 0));
|
|
return;
|
|
}
|
|
|
|
var expanded = IsExpanded(windowWidth);
|
|
var width = GetWidth(windowWidth) * Metrics.Scale;
|
|
using var child = ImRaii.Child("##hellion-sidebar", new Vector2(width, 0));
|
|
if (!child.Success)
|
|
return;
|
|
|
|
var theme = _themes.Active;
|
|
var accentRgba = _resolver.Resolve(Token.AccentPrimary, theme.Colors);
|
|
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
|
|
var mutedAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
|
|
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
|
|
var dl = ImGui.GetWindowDrawList();
|
|
|
|
// B3-4 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
|
|
// stays bound to its tab when sectioning moves it visually.
|
|
var renderOrder = TabLifecycleHelpers.BuildRenderOrder(
|
|
tabs,
|
|
t => _pool.IsOpen(t.Identifier)
|
|
);
|
|
var pinnedHeaderRendered = false;
|
|
var unpinnedHeaderRendered = false;
|
|
|
|
// Rows carry their own full-height surface now, so the default gap
|
|
// between them would read as a stripe of window background. The section
|
|
// headers are unaffected: LineDivider brings its own padding.
|
|
using var rowSpacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
|
|
foreach (var i in renderOrder)
|
|
{
|
|
var tab = tabs[i];
|
|
if (TabLifecycleHelpers.IsInPinnedPool(tab) && !pinnedHeaderRendered)
|
|
{
|
|
DrawSectionHeader(
|
|
HellionStrings.PinTab_SectionHeader,
|
|
TabLifecycleHelpers.CountPinnedPool(tabs, t => _pool.IsOpen(t.Identifier))
|
|
);
|
|
pinnedHeaderRendered = true;
|
|
}
|
|
else if (TabLifecycleHelpers.IsInUnpinnedPool(tab) && !unpinnedHeaderRendered)
|
|
{
|
|
DrawSectionHeader(
|
|
HellionStrings.AutoTellTabs_SectionHeader,
|
|
TabLifecycleHelpers.CountUnpinnedPool(tabs, t => _pool.IsOpen(t.Identifier))
|
|
);
|
|
unpinnedHeaderRendered = true;
|
|
}
|
|
|
|
DrawRow(tab, expanded, accentRgba, textAbgr, mutedAbgr, dimAbgr, dl, ref activeTab);
|
|
}
|
|
}
|
|
|
|
// Section transition marker (1.5.6 parity): the rule always renders, compact
|
|
// mode suppresses only the caption. LineDivider submits its own layout item
|
|
// and carries its own padding, which is what lets the rows below sit flush
|
|
// without the header collapsing onto them.
|
|
private void DrawSectionHeader(string header, int count)
|
|
{
|
|
var colors = _themes.Active.Colors;
|
|
var compact = Plugin.Config.AutoTellTabsCompactDisplay;
|
|
|
|
LineDivider.Draw(
|
|
compact ? null : $"{header} ({count})",
|
|
_palette.Abgr(Token.Border, colors),
|
|
_palette.Abgr(Token.TextMuted, colors)
|
|
);
|
|
|
|
if (!compact)
|
|
LastDrawnSectionHeaderCount++;
|
|
}
|
|
|
|
private void DrawRow(
|
|
Tab tab,
|
|
bool expanded,
|
|
uint accentRgba,
|
|
uint textAbgr,
|
|
uint mutedAbgr,
|
|
uint dimAbgr,
|
|
ImDrawListPtr dl,
|
|
ref Tab? activeTab
|
|
)
|
|
{
|
|
// Identity, not position: ImGui keeps popup state across frames under this
|
|
// ID, so an index would re-bind an open context menu to a different tab as
|
|
// soon as the list shifts. String, not GetHashCode — hashes collide.
|
|
ImGui.PushID(tab.Identifier.ToString());
|
|
|
|
var origin = ImGui.GetCursorScreenPos();
|
|
var avail = ImGui.GetContentRegionAvail().X;
|
|
|
|
// Drop the row entirely when the sidebar is dragged below the width
|
|
// of a single hit target. ImGui's InvisibleButton asserts on a
|
|
// zero-width size, which crashes the whole window at min-drag.
|
|
if (avail < Metrics.SidebarMinDrawWidth)
|
|
{
|
|
ImGui.PopID();
|
|
return;
|
|
}
|
|
|
|
// 1.5.6 parity: greeted state dims the tab icon whenever the toggle is
|
|
// configured on. The clickable affordance additionally needs an expanded
|
|
// sidebar with room for a third hit area beside the pop-out slot — in
|
|
// icon-only or min-drag mode it is skipped entirely.
|
|
var greetedConfigured = tab.IsTempTab && Plugin.Config.AutoTellTabsShowGreetedToggle;
|
|
var showGreeted =
|
|
greetedConfigured
|
|
&& expanded
|
|
&& avail > GreetedHitWidth + PopOutHitWidth + Metrics.SidebarHitSlack;
|
|
|
|
// 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
|
|
// 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.
|
|
var hasPopOut = expanded && avail > PopOutHitWidth + Metrics.SidebarHitSlack;
|
|
var tabHitWidth = hasPopOut ? avail - PopOutHitWidth : avail;
|
|
if (showGreeted)
|
|
{
|
|
// Greeted slot sits at the left edge (1.5.6 placement); the row
|
|
// button starts after it so the three hit areas never overlap.
|
|
tabHitWidth -= GreetedHitWidth;
|
|
ImGui.SetCursorScreenPos(origin + new Vector2(GreetedHitWidth, 0f));
|
|
}
|
|
|
|
ImGui.InvisibleButton("row", new Vector2(tabHitWidth, RowHeight));
|
|
if (ImGui.IsItemClicked())
|
|
{
|
|
var previous = activeTab;
|
|
activeTab = tab;
|
|
TabLifecycleHelpers.OnTabActivated(tab, previous);
|
|
}
|
|
|
|
// Not IsItemHovered: the row button is up to two hit widths narrower than
|
|
// the row, so a full-width surface driven by it would flicker at the
|
|
// edges. AllowWhenBlockedByActiveItem keeps the surface while the button
|
|
// is held down; without it the fill vanishes on press.
|
|
var rowMax = origin + new Vector2(avail, RowHeight);
|
|
var surfaceHovered =
|
|
ImGui.IsMouseHoveringRect(origin, rowMax)
|
|
&& ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
|
|
|
// GetID is seeded from the window's ID stack, so the same "row" literal
|
|
// stays distinct per window and per PushID'd tab. The old interpolated
|
|
// key allocated two strings per row per frame.
|
|
var hoverId = ImGui.GetID("row"u8);
|
|
var hoverAmount = HoverState.Query(hoverId, surfaceHovered);
|
|
|
|
var isActiveRow = ReferenceEquals(tab, activeTab);
|
|
if (isActiveRow)
|
|
LastRenderedActiveSurfaceCount++;
|
|
|
|
var colors = _themes.Active.Colors;
|
|
|
|
// Row fills follow the window's own opacity. Theme surfaces are fully
|
|
// opaque and the window is translucent by default (0.85 focused, 0.65
|
|
// not), so unscaled fills would sit on top as solid blocks.
|
|
var opacity = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootWindow)
|
|
? Plugin.Config.WindowOpacity
|
|
: Plugin.Config.WindowOpacityInactive;
|
|
|
|
Row.Draw(
|
|
origin,
|
|
new Vector2(avail, RowHeight),
|
|
new RowVisualState
|
|
{
|
|
IsActive = isActiveRow,
|
|
HoverAmount = hoverAmount,
|
|
SurfaceHoverAbgr = ColourUtil.ApplyAlpha(
|
|
_palette.Abgr(Token.SurfaceHover, colors),
|
|
opacity
|
|
),
|
|
SurfaceActiveAbgr = ColourUtil.ApplyAlpha(
|
|
_palette.Abgr(Token.SurfaceActive, colors),
|
|
opacity
|
|
),
|
|
AccentAbgr = _palette.Abgr(Token.AccentPrimary, colors),
|
|
BorderAbgr = ColourUtil.ApplyAlpha(_palette.Abgr(Token.Border, colors), opacity),
|
|
}
|
|
);
|
|
|
|
dl.DrawHoverSheen(origin, rowMax, accentRgba, hoverAmount, surfaceHovered);
|
|
|
|
var icon = ResolveTabIcon(tab);
|
|
|
|
// Dim precedence (1.5.6): the active tab always keeps its regular
|
|
// color; only greeted, non-active tabs drop to TextDim.
|
|
var isCurrentTab = tab == activeTab;
|
|
var iconColor = textAbgr;
|
|
if (
|
|
!isCurrentTab
|
|
&& greetedConfigured
|
|
&& Plugin.Instance.AutoTellTabsService.IsGreeted(tab)
|
|
)
|
|
iconColor = dimAbgr;
|
|
|
|
// Icon and label shift right by the greeted slot when it is shown.
|
|
var contentX = showGreeted ? GreetedHitWidth : 0f;
|
|
var scale = Metrics.Scale;
|
|
var iconInset = 10f * scale;
|
|
|
|
// Centred, not a frozen offset: the old 8f was (32 - 16) / 2 for a 16px
|
|
// font and stays wrong at any other Config.FontSizeV2.
|
|
var contentY = Metrics.CenterY(RowHeight);
|
|
|
|
float iconRight;
|
|
using (_fonts.FontAwesome.Push())
|
|
{
|
|
// Measured inside the push: FontAwesome is a fixed-width icon handle
|
|
// that does not follow Config.FontSizeV2, so the text font's line
|
|
// height would misplace the glyph at any other body size.
|
|
var iconStr = icon.ToIconString();
|
|
var iconSize = ImGui.CalcTextSize(iconStr);
|
|
dl.AddText(
|
|
origin
|
|
+ new Vector2(iconInset + contentX, MetricsMath.CenterY(RowHeight, iconSize.Y)),
|
|
iconColor,
|
|
iconStr
|
|
);
|
|
iconRight = iconInset + contentX + iconSize.X;
|
|
}
|
|
|
|
if (expanded)
|
|
dl.AddText(origin + new Vector2(iconRight + 6f * scale, contentY), textAbgr, tab.Name);
|
|
|
|
// Unread count. Drawn outside the icon-font scope on purpose: the
|
|
// FontAwesome atlas carries no ASCII digits, so the number would come out
|
|
// blank. The active tab is zeroed every frame (MainWindow.Draw), so it
|
|
// never shows on the tab you are viewing; UnreadMode.None opts out.
|
|
if (!isCurrentTab && tab.UnreadMode != UnreadMode.None && tab.Unread > 0)
|
|
{
|
|
var unread = (int)Math.Min(tab.Unread, int.MaxValue);
|
|
var badgeSize = Badge.CalcSize(unread);
|
|
var accentAbgr = _palette.Abgr(Token.AccentEmber, colors);
|
|
var slack = Metrics.SidebarHitSlack;
|
|
var reserved = hasPopOut ? PopOutHitWidth : 0f;
|
|
var badgeX = avail - reserved - badgeSize.X - slack;
|
|
|
|
// The count only fits where it can sit clear of the icon. At the
|
|
// default sidebar width of 44 it cannot, so a plain dot takes over
|
|
// rather than the badge landing on the glyph.
|
|
if (badgeX >= iconRight + slack)
|
|
{
|
|
Badge.Draw(
|
|
origin + new Vector2(badgeX, MetricsMath.CenterY(RowHeight, badgeSize.Y)),
|
|
unread,
|
|
accentAbgr,
|
|
textAbgr
|
|
);
|
|
}
|
|
else
|
|
{
|
|
var r = Metrics.SidebarUnreadRadius;
|
|
dl.AddCircleFilled(
|
|
origin + new Vector2(iconRight - r * 0.5f, RowHeight * 0.5f - r),
|
|
r,
|
|
accentAbgr,
|
|
12
|
|
);
|
|
}
|
|
|
|
LastRenderedUnreadDotCount++;
|
|
}
|
|
|
|
TabContextMenu.Draw(tab, "ctx", _pool);
|
|
|
|
if (hasPopOut)
|
|
{
|
|
ImGui.SameLine(0f, 0f);
|
|
|
|
// Glyph follows the row surface, not the button's own hover: the
|
|
// button sits inside the row, and the old pairing needed a hover
|
|
// state one line before it existed.
|
|
var (popClicked, _) = IconButton.Draw(
|
|
ImGui.GetID("popout"u8),
|
|
new Vector2(PopOutHitWidth, RowHeight),
|
|
surfaceHovered ? FontAwesomeIcon.ArrowUpRightFromSquare : null,
|
|
mutedAbgr,
|
|
_palette.Abgr(Token.SurfaceHover, colors),
|
|
_fonts.FontAwesome
|
|
);
|
|
if (popClicked)
|
|
_pool.TryOpen(tab);
|
|
}
|
|
|
|
if (showGreeted)
|
|
{
|
|
// 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).
|
|
ImGui.SetCursorScreenPos(origin);
|
|
|
|
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
|
|
var greetedGlyph = Plugin.Instance.AutoTellTabsService.IsGreeted(tab)
|
|
? FontAwesomeIcon.CheckCircle
|
|
: FontAwesomeIcon.Check;
|
|
|
|
var (greetedClicked, _) = IconButton.Draw(
|
|
ImGui.GetID("greeted"u8),
|
|
new Vector2(GreetedHitWidth, RowHeight),
|
|
greetedGlyph,
|
|
mutedAbgr,
|
|
_palette.Abgr(Token.SurfaceHover, colors),
|
|
_fonts.FontAwesome
|
|
);
|
|
if (greetedClicked)
|
|
ToggleGreetedForSelfTest(tab);
|
|
|
|
LastRenderedGreetedGlyphCount++;
|
|
}
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
private static FontAwesomeIcon ResolveTabIcon(Tab tab)
|
|
{
|
|
if (
|
|
!string.IsNullOrWhiteSpace(tab.Icon) && IconByName.TryGetValue(tab.Icon, out var mapped)
|
|
)
|
|
return mapped;
|
|
|
|
// Auto-tell tabs always show the envelope, regardless of what their
|
|
// SelectedChannels filter is set to.
|
|
if (tab.IsTempTab)
|
|
return FontAwesomeIcon.Envelope;
|
|
|
|
// Channel-type fallback. Walk every selected key, not just the first,
|
|
// so a System tab that filters multiple system-flavoured ChatTypes
|
|
// still picks up fa-cog when one of the later keys carries the match.
|
|
// The Comment default only wins when every key falls into the
|
|
// generic-text bucket (Say / Yell / Shout etc.).
|
|
foreach (var chatType in tab.SelectedChannels.Keys)
|
|
{
|
|
var glyph = ResolveByChannelType(chatType);
|
|
if (glyph != FontAwesomeIcon.Comment)
|
|
return glyph;
|
|
}
|
|
|
|
// Last-resort name match for tabs that filter exotic ChatTypes the
|
|
// mapping above doesn't cover — keeps the System tab visually
|
|
// distinct even with a custom channel set.
|
|
if (tab.Name.Contains("system", StringComparison.OrdinalIgnoreCase))
|
|
return FontAwesomeIcon.Cog;
|
|
|
|
return FontAwesomeIcon.Comment;
|
|
}
|
|
|
|
private static FontAwesomeIcon ResolveByChannelType(ChatType type) =>
|
|
type switch
|
|
{
|
|
ChatType.TellIncoming or ChatType.TellOutgoing => FontAwesomeIcon.Envelope,
|
|
ChatType.FreeCompany
|
|
or ChatType.FreeCompanyAnnouncement
|
|
or ChatType.FreeCompanyLoginLogout => FontAwesomeIcon.Users,
|
|
ChatType.Linkshell1
|
|
or ChatType.Linkshell2
|
|
or ChatType.Linkshell3
|
|
or ChatType.Linkshell4
|
|
or ChatType.Linkshell5
|
|
or ChatType.Linkshell6
|
|
or ChatType.Linkshell7
|
|
or ChatType.Linkshell8
|
|
or ChatType.CrossLinkshell1
|
|
or ChatType.CrossLinkshell2
|
|
or ChatType.CrossLinkshell3
|
|
or ChatType.CrossLinkshell4
|
|
or ChatType.CrossLinkshell5
|
|
or ChatType.CrossLinkshell6
|
|
or ChatType.CrossLinkshell7
|
|
or ChatType.CrossLinkshell8 => FontAwesomeIcon.Link,
|
|
ChatType.Party or ChatType.CrossParty => FontAwesomeIcon.UserFriends,
|
|
ChatType.Alliance => FontAwesomeIcon.Users,
|
|
ChatType.NoviceNetwork or ChatType.NoviceNetworkSystem => FontAwesomeIcon.Users,
|
|
ChatType.PvpTeam or ChatType.PvpTeamAnnouncement or ChatType.PvpTeamLoginLogout =>
|
|
FontAwesomeIcon.Users,
|
|
ChatType.System
|
|
or ChatType.BattleSystem
|
|
or ChatType.GatheringSystem
|
|
or ChatType.Error
|
|
or ChatType.Notice
|
|
or ChatType.LootNotice
|
|
or ChatType.Echo => FontAwesomeIcon.Cog,
|
|
ChatType.CustomEmote or ChatType.StandardEmote => FontAwesomeIcon.Comments,
|
|
_ => FontAwesomeIcon.Comment,
|
|
};
|
|
}
|