diff --git a/HellionChat/Ui/StyleEngine/Metrics.cs b/HellionChat/Ui/StyleEngine/Metrics.cs new file mode 100644 index 0000000..8ed96f0 --- /dev/null +++ b/HellionChat/Ui/StyleEngine/Metrics.cs @@ -0,0 +1,112 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility; +using HellionChat.Util; + +namespace HellionChat.Ui.StyleEngine; + +// Layout values, authored at 100% display scaling. Draw code reads the scaled +// properties; the Raw constants exist only for the few places that must store or +// compare an unscaled value (Sidebar.GetWidth, the width slider bounds). +// +// Deliberately NOT part of ThemeLayout: that record is serialised into theme +// JSON, and layout customisation is out of scope per the master spec. +internal static class Metrics +{ + // --- Sidebar --- + internal const float SidebarIconOnlyWidthRaw = 38f; + internal const float SidebarMinWidthRaw = 40f; + internal const float SidebarMaxWidthRaw = 300f; + internal const float SidebarRowHeightRaw = 32f; + internal const float SidebarPopOutHitWidthRaw = 22f; + internal const float SidebarGreetedHitWidthRaw = 22f; + internal const float SidebarHitSlackRaw = 4f; + internal const float SidebarMinDrawWidthRaw = 2f; + internal const float SidebarUnreadRadiusRaw = 4f; + internal const float SidebarGlyphInsetRaw = 4f; + + // --- Input bar --- + internal const float InputBarHeightRaw = 32f; + internal const float InputPillHeightRaw = 22f; + internal const float InputPillPaddingXRaw = 8f; + internal const float InputPillRoundingRaw = 6f; + internal const float InputQuickButtonsReserveRaw = 130f; + + // --- Honorific header --- + internal const float HonorificHeightRaw = 30f; + internal const float HonorificInsetRaw = 8f; + internal const float HonorificBracketGapRaw = 6f; + + // --- Top tab bar --- + internal const float TopTabUnreadRadiusRaw = 3.5f; + internal const float TopTabUnreadInsetRaw = 4f; + + // --- Status bar --- + internal const float StatusBorderThicknessRaw = 1f; + internal const float StatusTopSpacerRaw = 2f; + internal const float StatusMinOtherSlotsWidthRaw = 200f; + internal const float StatusDotRadiusRaw = 4f; + + // --- Message list --- + internal const float MessageDummyWidthRaw = 10f; + + private static float _cachedScale = 1f; + private static int _cachedFrame = -1; + + // GlobalScale resolves to ImGui.GetIO().FontGlobalScale, which the Dalamud + // settings slider moves on every frame while it is dragged. Pinning it once + // per frame keeps a CalcSize and its matching Draw on the same value, and + // saves three native calls per access. + internal static float Scale + { + get + { + var frame = ImGui.GetFrameCount(); + if (frame == _cachedFrame) + return _cachedScale; + + _cachedFrame = frame; + _cachedScale = ImGuiHelpers.GlobalScale; + return _cachedScale; + } + } + + internal static float SidebarIconOnlyWidth => MetricsMath.Scale(SidebarIconOnlyWidthRaw, Scale); + internal static float SidebarRowHeight => MetricsMath.Scale(SidebarRowHeightRaw, Scale); + internal static float SidebarPopOutHitWidth => + MetricsMath.Scale(SidebarPopOutHitWidthRaw, Scale); + internal static float SidebarGreetedHitWidth => + MetricsMath.Scale(SidebarGreetedHitWidthRaw, Scale); + internal static float SidebarHitSlack => MetricsMath.Scale(SidebarHitSlackRaw, Scale); + internal static float SidebarMinDrawWidth => MetricsMath.Scale(SidebarMinDrawWidthRaw, Scale); + internal static float SidebarUnreadRadius => MetricsMath.Scale(SidebarUnreadRadiusRaw, Scale); + internal static float SidebarGlyphInset => MetricsMath.Scale(SidebarGlyphInsetRaw, Scale); + + internal static float InputBarHeight => MetricsMath.Scale(InputBarHeightRaw, Scale); + internal static float InputPillHeight => MetricsMath.Scale(InputPillHeightRaw, Scale); + internal static float InputPillPaddingX => MetricsMath.Scale(InputPillPaddingXRaw, Scale); + internal static float InputPillRounding => MetricsMath.Scale(InputPillRoundingRaw, Scale); + internal static float InputQuickButtonsReserve => + MetricsMath.Scale(InputQuickButtonsReserveRaw, Scale); + + internal static float HonorificHeight => MetricsMath.Scale(HonorificHeightRaw, Scale); + internal static float HonorificInset => MetricsMath.Scale(HonorificInsetRaw, Scale); + internal static float HonorificBracketGap => MetricsMath.Scale(HonorificBracketGapRaw, Scale); + + internal static float TopTabUnreadRadius => MetricsMath.Scale(TopTabUnreadRadiusRaw, Scale); + internal static float TopTabUnreadInset => MetricsMath.Scale(TopTabUnreadInsetRaw, Scale); + + internal static float StatusBorderThickness => + MetricsMath.Scale(StatusBorderThicknessRaw, Scale); + internal static float StatusTopSpacer => MathF.Round(StatusTopSpacerRaw * Scale); + internal static float StatusMinOtherSlotsWidth => + MetricsMath.Scale(StatusMinOtherSlotsWidthRaw, Scale); + internal static float StatusDotRadius => MetricsMath.Scale(StatusDotRadiusRaw, Scale); + + internal static float MessageDummyWidth => MetricsMath.Scale(MessageDummyWidthRaw, Scale); + + // Text-dependent heights are measured, never scaled: the font is built from + // Config.FontSizeV2, which GlobalScale does not feed into. StatusBar.Height + // has done it this way since 1.8.x. + internal static float CenterY(float rowHeight) => + MetricsMath.CenterY(rowHeight, ImGui.GetTextLineHeight()); +} diff --git a/HellionChat/Util/MetricsMath.cs b/HellionChat/Util/MetricsMath.cs new file mode 100644 index 0000000..afedc0d --- /dev/null +++ b/HellionChat/Util/MetricsMath.cs @@ -0,0 +1,13 @@ +namespace HellionChat.Util; + +// Pure scaling arithmetic, split out from Metrics so the build suite can pin it +// without standing up an ImGui frame. +internal static class MetricsMath +{ + internal static float Scale(float raw, float scale) => raw * scale; + + // Vertical centering for text inside a fixed-height row. Frozen offsets keep + // their mis-centering at every other font size; a computed one does not. + internal static float CenterY(float rowHeight, float textHeight) => + (rowHeight - textHeight) * 0.5f; +}