fix(chat): four things the header review found, all of them visible
The self-test was the worst of them, because it is the only tool that makes block A judgeable at all and it destroyed itself on use. It returned Fail while the atlas was not ready, and its own weight buttons trigger a rebuild -- which is asynchronous, not synchronous as the comment claimed. Click a weight, watch the step go red. It waits now, like the two existing steps that had already worked this out. The translated stand-in was drawn in the meta face, whose glyph range is ASCII plus a middle dot. Fifteen of the twenty-five translations reach outside that, so "not logged in" would have rendered as a row of question marks in Japanese, Russian, Korean, Greek and eleven others -- and the measured width would have been the width of the question marks, so the right edge would have drifted too. The plan said to keep it on the body face and the comment in FontManager says so as well; the code simply did not. The detail is two parts now rather than one string, and each part is measured under the face that draws it. The header was the only place in the UI pushing RegularFont directly, without the FontsEnabled-or-UseHellionFont check every other push site makes. With both toggles off the window draws in AXIS and the header would have drawn in Inter-Light, at a different size, in a band measured against a third one. And the icon sat on the text baseline. FontAwesome is a fixed-width handle built at Dalamud's own size and does not follow the plugin's font setting, so at any other body size it hangs. The sidebar already knew this and centres against the row; the header does the same now. Two smaller ones came along: the minimum-height threshold was compared unscaled, which would have dissolved it at higher display scales, and the height passed into that check included the input row -- so "is there still room to read" was measuring the wrong thing. Both callers now say what sits below them.
This commit is contained in:
@@ -1359,6 +1359,6 @@
|
|||||||
<value>obert</value>
|
<value>obert</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||||
<value>Sense sessió</value>
|
<value>Sessió no iniciada</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1359,6 +1359,6 @@
|
|||||||
<value>abierto</value>
|
<value>abierto</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||||
<value>Sin sesión</value>
|
<value>Sesión no iniciada</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1358,6 +1358,6 @@
|
|||||||
<value>auki</value>
|
<value>auki</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||||
<value>Ei kirjautunut sisään</value>
|
<value>Ei kirjautuneena</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1359,6 +1359,6 @@
|
|||||||
<value>열림</value>
|
<value>열림</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||||
<value>로그인하지 않음</value>
|
<value>로그인되지 않음</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1359,6 +1359,6 @@
|
|||||||
<value>открыт</value>
|
<value>открыт</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||||
<value>Не выполнен вход</value>
|
<value>Вход не выполнен</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -35,10 +35,14 @@ internal sealed class TypeScaleStep : ISelfTestStep
|
|||||||
return SelfTestStepResult.Fail;
|
return SelfTestStepResult.Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Waiting, not Fail. The atlas rebuild this step's own weight buttons
|
||||||
|
// trigger is asynchronous, so a Fail here would make the step destroy
|
||||||
|
// itself the moment it is used as intended. Same guard as
|
||||||
|
// AboutIntegrationsStatusStep and HonorificHeaderRenderStep.
|
||||||
if (!fm.FontsReady)
|
if (!fm.FontsReady)
|
||||||
{
|
{
|
||||||
ImGui.Text("FontsReady is false - atlas still building, run again in a moment");
|
ImGui.Text("Atlas still building - the step resumes on its own");
|
||||||
return SelfTestStepResult.Fail;
|
return SelfTestStepResult.Waiting;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fm.SenderFont is null || fm.MetaFont is null || fm.RegularFont is null)
|
if (fm.SenderFont is null || fm.MetaFont is null || fm.RegularFont is null)
|
||||||
@@ -70,11 +74,16 @@ internal sealed class TypeScaleStep : ISelfTestStep
|
|||||||
+ "Pick one and watch the row above change."
|
+ "Pick one and watch the row above change."
|
||||||
);
|
);
|
||||||
|
|
||||||
// Three builds would be the alternative, and nobody compares a face
|
// Three builds would be the alternative, and nobody compares a typeface
|
||||||
// across a restart. RebuildDelegateFonts runs synchronously on this
|
// across a plugin restart.
|
||||||
// thread -- self-test steps are on the draw thread, same as every push
|
//
|
||||||
// site -- so the sample row redraws in the next frame with the new
|
// The rebuild is asynchronous -- it goes through
|
||||||
// rasterisation.
|
// Framework.RunOnFrameworkThread and BuildFontsAsync -- so FontsReady
|
||||||
|
// drops to false for a moment after each click and the step reports
|
||||||
|
// Waiting until the atlas is back. It does NOT show the three weights
|
||||||
|
// side by side, which is what the plan asked for; one handle can only
|
||||||
|
// carry one weight, and three throwaway handles for a self-test is a
|
||||||
|
// worse trade than clicking through them.
|
||||||
DrawWeightPicker(fm, 1.2f);
|
DrawWeightPicker(fm, 1.2f);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawWeightPicker(fm, 1.3f);
|
DrawWeightPicker(fm, 1.3f);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
using HellionChat.Util;
|
using HellionChat.Util;
|
||||||
|
|
||||||
namespace HellionChat.Ui.StyleEngine.Widgets;
|
namespace HellionChat.Ui.StyleEngine.Widgets;
|
||||||
@@ -14,8 +15,10 @@ namespace HellionChat.Ui.StyleEngine.Widgets;
|
|||||||
// range, because tab names are free user input and can be CJK. Tracking carries
|
// range, because tab names are free user input and can be CJK. Tracking carries
|
||||||
// the same weight in every palette and costs nothing.
|
// 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
|
// Which face draws what is not a style choice here, it is a constraint. The meta
|
||||||
// clock use the meta face, whose glyph range is ASCII plus a middle dot.
|
// face has a glyph range of ASCII plus a middle dot, so only the world name and
|
||||||
|
// the clock can use it. The tab name and the translated "no world" stand-in go
|
||||||
|
// through the body face, or they come out as rows of question marks.
|
||||||
internal static class ChannelHeader
|
internal static class ChannelHeader
|
||||||
{
|
{
|
||||||
private const float InsetRaw = 14f;
|
private const float InsetRaw = 14f;
|
||||||
@@ -29,7 +32,27 @@ internal static class ChannelHeader
|
|||||||
internal static float Height =>
|
internal static float Height =>
|
||||||
ImGui.GetTextLineHeight() + MathF.Round(PadYRaw * 2f * Metrics.Scale);
|
ImGui.GetTextLineHeight() + MathF.Round(PadYRaw * 2f * Metrics.Scale);
|
||||||
|
|
||||||
internal static void Draw(Tab tab, ChannelHeaderMode mode, FontManager fonts, string detail)
|
// The body face follows the same switch every other push site follows. Two
|
||||||
|
// settings decide it, and reading only one of them is how a window ends up
|
||||||
|
// half in the game font and half in the bundled one.
|
||||||
|
private static IFontHandle BodyFace(FontManager fonts) =>
|
||||||
|
Plugin.Config.FontsEnabled || Plugin.Config.UseHellionFont
|
||||||
|
? fonts.RegularFont!
|
||||||
|
: fonts.Axis;
|
||||||
|
|
||||||
|
// Same switch for the meta face. With the game font selected there is no
|
||||||
|
// stepped-down variant to fall back to, so the size distinction is simply
|
||||||
|
// dropped -- the same honest limitation the sender weight has.
|
||||||
|
private static IFontHandle MetaFace(FontManager fonts) =>
|
||||||
|
Plugin.Config.FontsEnabled || Plugin.Config.UseHellionFont ? fonts.MetaFont! : fonts.Axis;
|
||||||
|
|
||||||
|
internal static void Draw(
|
||||||
|
Tab tab,
|
||||||
|
ChannelHeaderMode mode,
|
||||||
|
FontManager fonts,
|
||||||
|
ChannelHeaderDetailParts detail,
|
||||||
|
float reservedBelow
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var scale = Metrics.Scale;
|
var scale = Metrics.Scale;
|
||||||
var width = ImGui.GetContentRegionAvail().X;
|
var width = ImGui.GetContentRegionAvail().X;
|
||||||
@@ -39,30 +62,52 @@ internal static class ChannelHeader
|
|||||||
var theme = Plugin.Instance.ThemeRegistry.Active;
|
var theme = Plugin.Instance.ThemeRegistry.Active;
|
||||||
var surface = theme.Colors.Surface;
|
var surface = theme.Colors.Surface;
|
||||||
|
|
||||||
var name = tab.Name.ToUpperInvariant();
|
var body = BodyFace(fonts);
|
||||||
|
var meta = MetaFace(fonts);
|
||||||
|
|
||||||
|
var icon = Components.Sidebar.ResolveTabIcon(tab);
|
||||||
var track = TrackRaw * scale;
|
var track = TrackRaw * scale;
|
||||||
var detailTrack = DetailTrackRaw * scale;
|
var detailTrack = DetailTrackRaw * scale;
|
||||||
|
var showName = mode is ChannelHeaderMode.Full;
|
||||||
|
|
||||||
// Measure before deciding, the way the status bar does. Icon and gap are
|
// ToUpperInvariant allocates, so only where the name is actually drawn.
|
||||||
// part of the name run because they are dropped together with it.
|
var name = showName ? tab.Name.ToUpperInvariant() : string.Empty;
|
||||||
float nameRun;
|
|
||||||
using (fonts.RegularFont!.Push())
|
|
||||||
nameRun = DrawListExtensions.MeasureTrackedText(name, track);
|
|
||||||
|
|
||||||
var iconWidth = MeasureIcon(fonts, Components.Sidebar.ResolveTabIcon(tab));
|
Vector2 iconSize;
|
||||||
nameRun += iconWidth + IconGapRaw * scale;
|
using (fonts.FontAwesome.Push())
|
||||||
|
iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||||
|
|
||||||
float detailRun;
|
var nameRun = 0f;
|
||||||
using (fonts.MetaFont!.Push())
|
if (showName)
|
||||||
detailRun = DrawListExtensions.MeasureTrackedText(detail, detailTrack);
|
{
|
||||||
|
using (body.Push())
|
||||||
|
nameRun = DrawListExtensions.MeasureTrackedText(name, track);
|
||||||
|
nameRun += iconSize.X + IconGapRaw * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measured under the face that will draw it, which is the whole point of
|
||||||
|
// splitting the detail in two: the stand-in is translated and the meta
|
||||||
|
// face cannot render most of those translations.
|
||||||
|
float whereRun;
|
||||||
|
using ((detail.WhereIsTranslated ? body : meta).Push())
|
||||||
|
whereRun = DrawListExtensions.MeasureTrackedText(detail.Where, detailTrack);
|
||||||
|
|
||||||
|
var rest = ChannelHeaderDetailParts.Separator + detail.Clock;
|
||||||
|
|
||||||
|
float restRun;
|
||||||
|
using (meta.Push())
|
||||||
|
restRun = DrawListExtensions.MeasureTrackedText(rest, detailTrack);
|
||||||
|
|
||||||
|
var detailRun = whereRun + restRun;
|
||||||
var inset = InsetRaw * scale;
|
var inset = InsetRaw * scale;
|
||||||
|
|
||||||
var plan = ChannelHeaderLayout.Plan(
|
var plan = ChannelHeaderLayout.Plan(
|
||||||
mode,
|
mode,
|
||||||
width - inset * 2f,
|
width - inset * 2f,
|
||||||
ImGui.GetContentRegionAvail().Y - height,
|
ImGui.GetContentRegionAvail().Y - height - reservedBelow,
|
||||||
nameRun,
|
nameRun,
|
||||||
detailRun
|
detailRun,
|
||||||
|
scale
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!plan.ShowHeader)
|
if (!plan.ShowHeader)
|
||||||
@@ -87,14 +132,20 @@ internal static class ChannelHeader
|
|||||||
);
|
);
|
||||||
var x = origin.X + inset;
|
var x = origin.X + inset;
|
||||||
|
|
||||||
|
// Centred against the band, not aligned to the text baseline:
|
||||||
|
// FontAwesome is a fixed-width handle built at Dalamud's own size and
|
||||||
|
// does not follow Config.FontSizeV2, so the text line height would
|
||||||
|
// misplace it at any other body size. Same reasoning as the sidebar.
|
||||||
using (fonts.FontAwesome.Push())
|
using (fonts.FontAwesome.Push())
|
||||||
{
|
dl.AddText(
|
||||||
var glyph = Components.Sidebar.ResolveTabIcon(tab).ToIconString();
|
new Vector2(x, origin.Y + MetricsMath.CenterY(height, iconSize.Y)),
|
||||||
dl.AddText(new Vector2(x, textY), accent, glyph);
|
accent,
|
||||||
x += ImGui.CalcTextSize(glyph).X + IconGapRaw * scale;
|
icon.ToIconString()
|
||||||
}
|
);
|
||||||
|
|
||||||
using (fonts.RegularFont.Push())
|
x += iconSize.X + IconGapRaw * scale;
|
||||||
|
|
||||||
|
using (body.Push())
|
||||||
dl.DrawTrackedText(new Vector2(x, textY), name, accent, track);
|
dl.DrawTrackedText(new Vector2(x, textY), name, accent, track);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,22 +155,23 @@ internal static class ChannelHeader
|
|||||||
ColourUtil.EnsureContrast(theme.Colors.TextMuted, surface, 4.5f)
|
ColourUtil.EnsureContrast(theme.Colors.TextMuted, surface, 4.5f)
|
||||||
);
|
);
|
||||||
|
|
||||||
// The meta face is smaller, so it would hang from the top edge
|
var whereFace = detail.WhereIsTranslated ? body : meta;
|
||||||
// without the drop -- ImGui aligns by top, not by baseline.
|
var x = bottomRight.X - inset - detailRun;
|
||||||
float bodyAscent;
|
|
||||||
using (fonts.RegularFont.Push())
|
|
||||||
bodyAscent = ImGui.GetFont().Ascent;
|
|
||||||
|
|
||||||
float metaAscent;
|
using (whereFace.Push())
|
||||||
using (fonts.MetaFont.Push())
|
|
||||||
metaAscent = ImGui.GetFont().Ascent;
|
|
||||||
|
|
||||||
var drop = BaselineMath.OffsetFor(bodyAscent, metaAscent, scale);
|
|
||||||
|
|
||||||
using (fonts.MetaFont.Push())
|
|
||||||
dl.DrawTrackedText(
|
dl.DrawTrackedText(
|
||||||
new Vector2(bottomRight.X - inset - detailRun, textY + drop),
|
new Vector2(x, textY + DropFor(body, whereFace, scale)),
|
||||||
detail,
|
detail.Where,
|
||||||
|
muted,
|
||||||
|
detailTrack
|
||||||
|
);
|
||||||
|
|
||||||
|
x += whereRun;
|
||||||
|
|
||||||
|
using (meta.Push())
|
||||||
|
dl.DrawTrackedText(
|
||||||
|
new Vector2(x, textY + DropFor(body, meta, scale)),
|
||||||
|
rest,
|
||||||
muted,
|
muted,
|
||||||
detailTrack
|
detailTrack
|
||||||
);
|
);
|
||||||
@@ -131,12 +183,11 @@ internal static class ChannelHeader
|
|||||||
ImGuiP.ItemSize(new Vector2(width, height - ImGui.GetStyle().ItemSpacing.Y));
|
ImGuiP.ItemSize(new Vector2(width, height - ImGui.GetStyle().ItemSpacing.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both windows need the same string, and only one of them should know how to
|
internal static ChannelHeaderDetailParts CurrentDetail()
|
||||||
// get at the world. IsValid is the guard the payload handler already uses --
|
|
||||||
// HomeWorld is a row reference that stays unresolved until a character is
|
|
||||||
// actually logged in.
|
|
||||||
internal static string CurrentDetail()
|
|
||||||
{
|
{
|
||||||
|
// IsValid guards the row reference, but the case that actually happens is
|
||||||
|
// subtler: logged out resolves to row zero, which exists and carries an
|
||||||
|
// empty name. Format treats blank as missing, which covers both.
|
||||||
var world = Plugin.PlayerState.HomeWorld.IsValid
|
var world = Plugin.PlayerState.HomeWorld.IsValid
|
||||||
? Plugin.PlayerState.HomeWorld.Value.Name.ExtractText()
|
? Plugin.PlayerState.HomeWorld.Value.Name.ExtractText()
|
||||||
: null;
|
: null;
|
||||||
@@ -149,9 +200,20 @@ internal static class ChannelHeader
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float MeasureIcon(FontManager fonts, FontAwesomeIcon icon)
|
// Zero whenever both runs use the same handle, which is the common case.
|
||||||
|
private static float DropFor(IFontHandle body, IFontHandle other, float scale)
|
||||||
{
|
{
|
||||||
using (fonts.FontAwesome.Push())
|
if (ReferenceEquals(body, other))
|
||||||
return ImGui.CalcTextSize(icon.ToIconString()).X;
|
return 0f;
|
||||||
|
|
||||||
|
float bodyAscent;
|
||||||
|
using (body.Push())
|
||||||
|
bodyAscent = ImGui.GetFont().Ascent;
|
||||||
|
|
||||||
|
float otherAscent;
|
||||||
|
using (other.Push())
|
||||||
|
otherAscent = ImGui.GetFont().Ascent;
|
||||||
|
|
||||||
|
return BaselineMath.OffsetFor(bodyAscent, otherAscent, scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,43 @@ using System.Globalization;
|
|||||||
|
|
||||||
namespace HellionChat.Ui.StyleEngine.Widgets;
|
namespace HellionChat.Ui.StyleEngine.Widgets;
|
||||||
|
|
||||||
// TEST-MIRROR: Ui/ChannelHeaderDetailTests.cs
|
// Where you are and what time it is, as two parts rather than one string.
|
||||||
//
|
//
|
||||||
// The trailing half of the channel header: where you are and what time it is.
|
// They are split because they cannot share a face. The clock and a world name are
|
||||||
|
// Latin in every client, which is why the meta face gets away with a glyph range
|
||||||
|
// of ASCII plus a middle dot. The stand-in for "no world known" is translated
|
||||||
|
// into 25 languages, and fifteen of those reach outside that range -- drawn in the
|
||||||
|
// meta face they would come out as rows of question marks. So the caller draws
|
||||||
|
// WhereIsTranslated text in the body face and the rest in the meta face.
|
||||||
//
|
//
|
||||||
// The clock follows the same Use24HourClock setting the message timestamps do.
|
// The clock follows the same Use24HourClock setting the message timestamps do.
|
||||||
// Two clock formats in one window would be a defect rather than a preference, and
|
// Two clock formats in one window, with the header sitting directly above a
|
||||||
// the header sits directly above a column of timestamps.
|
// column of timestamps, would be a defect rather than a preference. Culture is
|
||||||
//
|
// pinned for the same reason it is pinned in the message list: a German machine
|
||||||
// Culture is pinned for the same reason it is pinned in the message list: a
|
// renders "PM" as "nachm." under its own culture.
|
||||||
// German machine renders "PM" as "nachm." under its own culture, and the two
|
internal readonly record struct ChannelHeaderDetailParts(
|
||||||
// would then disagree on the same screen.
|
string Where,
|
||||||
internal static class ChannelHeaderDetail
|
string Clock,
|
||||||
|
bool WhereIsTranslated
|
||||||
|
)
|
||||||
{
|
{
|
||||||
internal const string Separator = " · ";
|
internal const string Separator = " · ";
|
||||||
|
}
|
||||||
|
|
||||||
internal static string Format(
|
internal static class ChannelHeaderDetail
|
||||||
|
{
|
||||||
|
internal static ChannelHeaderDetailParts Format(
|
||||||
string? world,
|
string? world,
|
||||||
DateTimeOffset now,
|
DateTimeOffset now,
|
||||||
bool use24Hour,
|
bool use24Hour,
|
||||||
string fallback
|
string fallback
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var where = string.IsNullOrWhiteSpace(world) ? fallback : world;
|
var missing = string.IsNullOrWhiteSpace(world);
|
||||||
var clock = use24Hour
|
var clock = use24Hour
|
||||||
? now.ToString("HH:mm", CultureInfo.InvariantCulture)
|
? now.ToString("HH:mm", CultureInfo.InvariantCulture)
|
||||||
: now.ToString("h:mm tt", CultureInfo.InvariantCulture);
|
: now.ToString("h:mm tt", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return string.Concat(where, Separator, clock);
|
return new ChannelHeaderDetailParts(missing ? fallback : world!, clock, missing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,21 +29,31 @@ internal static class ChannelHeaderLayout
|
|||||||
{
|
{
|
||||||
// Below this the message area stops being a conversation and starts being a
|
// Below this the message area stops being a conversation and starts being a
|
||||||
// peephole. Four body lines at a typical scale.
|
// peephole. Four body lines at a typical scale.
|
||||||
internal const float MinMessageAreaHeight = 90f;
|
//
|
||||||
|
// Raw, like every other layout value in the project: the caller passes the
|
||||||
|
// display scale so this stays arithmetic. Comparing an unscaled 90 against
|
||||||
|
// scaled pixels would dissolve the threshold as the scale goes up -- at 200%
|
||||||
|
// it would be worth 45 logical pixels.
|
||||||
|
internal const float MinMessageAreaHeightRaw = 90f;
|
||||||
|
|
||||||
|
// Keeps the name and the trailing detail from touching at the exact pixel
|
||||||
|
// where they both still "fit".
|
||||||
|
internal const float MinGapRaw = 12f;
|
||||||
|
|
||||||
internal static ChannelHeaderPlan Plan(
|
internal static ChannelHeaderPlan Plan(
|
||||||
ChannelHeaderMode mode,
|
ChannelHeaderMode mode,
|
||||||
float availableWidth,
|
float availableWidth,
|
||||||
float availableHeight,
|
float availableHeight,
|
||||||
float nameWidth,
|
float nameWidth,
|
||||||
float detailWidth
|
float detailWidth,
|
||||||
|
float scale
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (availableHeight < MinMessageAreaHeight)
|
if (availableHeight < MinMessageAreaHeightRaw * scale)
|
||||||
return new ChannelHeaderPlan(false, false, false);
|
return new ChannelHeaderPlan(false, false, false);
|
||||||
|
|
||||||
var showName = mode is ChannelHeaderMode.Full;
|
var showName = mode is ChannelHeaderMode.Full;
|
||||||
var used = showName ? nameWidth : 0f;
|
var used = showName ? nameWidth + MinGapRaw * scale : 0f;
|
||||||
var showDetail = used + detailWidth <= availableWidth;
|
var showDetail = used + detailWidth <= availableWidth;
|
||||||
|
|
||||||
return new ChannelHeaderPlan(true, showName, showDetail);
|
return new ChannelHeaderPlan(true, showName, showDetail);
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
|
|||||||
? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly
|
? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly
|
||||||
: StyleEngine.Widgets.ChannelHeaderMode.Full,
|
: StyleEngine.Widgets.ChannelHeaderMode.Full,
|
||||||
Plugin.Instance.FontManager,
|
Plugin.Instance.FontManager,
|
||||||
StyleEngine.Widgets.ChannelHeader.CurrentDetail()
|
StyleEngine.Widgets.ChannelHeader.CurrentDetail(),
|
||||||
|
InputBar.Height
|
||||||
);
|
);
|
||||||
|
|
||||||
// The header close button can unbind us mid-frame (CloseRequested ->
|
// The header close button can unbind us mid-frame (CloseRequested ->
|
||||||
|
|||||||
@@ -400,11 +400,15 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
|
|||||||
? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly
|
? StyleEngine.Widgets.ChannelHeaderMode.DetailOnly
|
||||||
: StyleEngine.Widgets.ChannelHeaderMode.Full;
|
: StyleEngine.Widgets.ChannelHeaderMode.Full;
|
||||||
|
|
||||||
|
// What follows the log in this column, so the header can tell how
|
||||||
|
// much room the conversation is actually left with. Without it the
|
||||||
|
// drop-out rule would measure the input row as readable chat.
|
||||||
StyleEngine.Widgets.ChannelHeader.Draw(
|
StyleEngine.Widgets.ChannelHeader.Draw(
|
||||||
headerTab,
|
headerTab,
|
||||||
mode,
|
mode,
|
||||||
Plugin.Instance.FontManager,
|
Plugin.Instance.FontManager,
|
||||||
StyleEngine.Widgets.ChannelHeader.CurrentDetail()
|
StyleEngine.Widgets.ChannelHeader.CurrentDetail(),
|
||||||
|
inputHeight + previewHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ internal sealed class WidgetGalleryWindow : Window
|
|||||||
private int _badgeCount = 3;
|
private int _badgeCount = 3;
|
||||||
private Tab? _headerSample;
|
private Tab? _headerSample;
|
||||||
private int _headerMode;
|
private int _headerMode;
|
||||||
|
private bool _headerLoggedOut;
|
||||||
private bool _rowActive = true;
|
private bool _rowActive = true;
|
||||||
private bool _toggleA = true;
|
private bool _toggleA = true;
|
||||||
private bool _toggleB;
|
private bool _toggleB;
|
||||||
@@ -87,7 +88,18 @@ internal sealed class WidgetGalleryWindow : Window
|
|||||||
fonts.RebuildDelegateFonts();
|
fonts.RebuildDelegateFonts();
|
||||||
|
|
||||||
var mode = _headerMode == 0 ? ChannelHeaderMode.Full : ChannelHeaderMode.DetailOnly;
|
var mode = _headerMode == 0 ? ChannelHeaderMode.Full : ChannelHeaderMode.DetailOnly;
|
||||||
ChannelHeader.Draw(_headerSample, mode, fonts, "Ravana · 14:32");
|
|
||||||
|
// Both detail shapes, because they take different faces: a known world
|
||||||
|
// rides the meta face, the translated stand-in has to use the body face.
|
||||||
|
ImGui.Checkbox("logged out##hdr", ref _headerLoggedOut);
|
||||||
|
var detail = ChannelHeaderDetail.Format(
|
||||||
|
_headerLoggedOut ? null : "Ravana",
|
||||||
|
DateTimeOffset.Now,
|
||||||
|
Plugin.Config.Use24HourClock,
|
||||||
|
Resources.HellionStrings.ChannelHeader_NotLoggedIn
|
||||||
|
);
|
||||||
|
|
||||||
|
ChannelHeader.Draw(_headerSample, mode, fonts, detail, 0f);
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user