using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using HellionChat.Util; namespace HellionChat.Ui.StyleEngine.Widgets; // The band above the conversation: which channel you are in on the left, where // you are and what time it is on the right. // // Set apart by small caps with wide tracking rather than by size. That is a // deliberate departure from the mockup, which asks for one pixel smaller than // body text: one pixel would cost a whole additional font handle at full glyph // range, because tab names are free user input and can be CJK. Tracking carries // the same weight in every palette and costs nothing. // // The tab name draws in the body face for the same reason. Only the world and the // clock use the meta face, whose glyph range is ASCII plus a middle dot. internal static class ChannelHeader { private const float InsetRaw = 14f; private const float PadYRaw = 7f; private const float TrackRaw = 1.8f; private const float DetailTrackRaw = 0.9f; private const float IconGapRaw = 8f; // Measured, never a fixed 32px: the band has to hold a line of text, and the // font comes from Config, which display scaling does not feed into. internal static float Height => ImGui.GetTextLineHeight() + MathF.Round(PadYRaw * 2f * Metrics.Scale); internal static void Draw(Tab tab, ChannelHeaderMode mode, FontManager fonts, string detail) { var scale = Metrics.Scale; var width = ImGui.GetContentRegionAvail().X; var height = Height; var origin = ImGui.GetCursorScreenPos(); var theme = Plugin.Instance.ThemeRegistry.Active; var surface = theme.Colors.Surface; var name = tab.Name.ToUpperInvariant(); var track = TrackRaw * scale; var detailTrack = DetailTrackRaw * scale; // Measure before deciding, the way the status bar does. Icon and gap are // part of the name run because they are dropped together with it. float nameRun; using (fonts.RegularFont!.Push()) nameRun = DrawListExtensions.MeasureTrackedText(name, track); var iconWidth = MeasureIcon(fonts, Components.Sidebar.ResolveTabIcon(tab)); nameRun += iconWidth + IconGapRaw * scale; float detailRun; using (fonts.MetaFont!.Push()) detailRun = DrawListExtensions.MeasureTrackedText(detail, detailTrack); var inset = InsetRaw * scale; var plan = ChannelHeaderLayout.Plan( mode, width - inset * 2f, ImGui.GetContentRegionAvail().Y - height, nameRun, detailRun ); if (!plan.ShowHeader) return; var dl = ImGui.GetWindowDrawList(); var bottomRight = origin + new Vector2(width, height); dl.AddRectFilled(origin, bottomRight, ColourUtil.RgbaToAbgr(surface)); dl.AddLine( new Vector2(origin.X, bottomRight.Y - 1f), new Vector2(bottomRight.X, bottomRight.Y - 1f), ColourUtil.RgbaToAbgr(theme.Colors.Border), MathF.Max(1f, scale) ); var textY = origin.Y + MathF.Round(PadYRaw * scale); if (plan.ShowName) { var accent = ColourUtil.RgbaToAbgr( ColourUtil.EnsureContrast(theme.Colors.Accent, surface, 4.5f) ); var x = origin.X + inset; using (fonts.FontAwesome.Push()) { var glyph = Components.Sidebar.ResolveTabIcon(tab).ToIconString(); dl.AddText(new Vector2(x, textY), accent, glyph); x += ImGui.CalcTextSize(glyph).X + IconGapRaw * scale; } using (fonts.RegularFont.Push()) dl.DrawTrackedText(new Vector2(x, textY), name, accent, track); } if (plan.ShowDetail) { var muted = ColourUtil.RgbaToAbgr( ColourUtil.EnsureContrast(theme.Colors.TextMuted, surface, 4.5f) ); // The meta face is smaller, so it would hang from the top edge // without the drop -- ImGui aligns by top, not by baseline. float bodyAscent; using (fonts.RegularFont.Push()) bodyAscent = ImGui.GetFont().Ascent; float metaAscent; using (fonts.MetaFont.Push()) metaAscent = ImGui.GetFont().Ascent; var drop = BaselineMath.OffsetFor(bodyAscent, metaAscent, scale); using (fonts.MetaFont.Push()) dl.DrawTrackedText( new Vector2(bottomRight.X - inset - detailRun, textY + drop), detail, muted, detailTrack ); } // ItemSize rather than a cursor move: it advances the cursor AND extends // CursorMaxPos, which is what the enclosing layout measures. ImGui.SetCursorScreenPos(origin); ImGuiP.ItemSize(new Vector2(width, height - ImGui.GetStyle().ItemSpacing.Y)); } private static float MeasureIcon(FontManager fonts, FontAwesomeIcon icon) { using (fonts.FontAwesome.Push()) return ImGui.CalcTextSize(icon.ToIconString()).X; } }