fix(combat): preserve authored meter child geometry

Carry the consumed dark-range child's own retail edge policy into the procedural scrollbar, widening it to the authored post-reflow interval without covering the rendered labels. Restore the bright layer to live attack-charge feedback across the full bar from the left.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-12 21:57:53 +02:00
parent f8c174d707
commit e671b8a5c4
10 changed files with 66 additions and 105 deletions

View file

@ -49,13 +49,10 @@ public sealed class UiScrollbar : UiElement
public float ScalarRangeLeft { get; set; }
public float ScalarRangeWidth { get; set; } = float.PositiveInfinity;
/// <summary>
/// Optional live range geometry. Imported sibling controls reflow immediately
/// before drawing, so combat derives its centered range from their current
/// rectangles instead of freezing pre-reflow prototype coordinates.
/// Authored layout policy for a consumed range child. This preserves the
/// child's own DAT edge modes even though the scrollbar draws it procedurally.
/// </summary>
public Func<(float Left, float Width)>? ScalarRangeProvider { get; set; }
/// <summary>Draw the scalar fill from the absolute left edge to the thumb center.</summary>
public bool ScalarFillFollowsThumb { get; set; }
public UiLayoutPolicy? ScalarRangeLayoutPolicy { get; set; }
/// <summary>
/// Draw the scalar meter from the power end (right) toward the speed end
/// (left). This is the authored gmCombatUI power meter direction.
@ -150,20 +147,15 @@ public sealed class UiScrollbar : UiElement
DrawTiled(ctx, resolve, ScalarRangeSprite,
rangeLeft, 0f, rangeWidth, Height);
float thumbWidth = ScalarThumbWidth(resolve);
if (ScalarFillSprite != 0 && ScalarFillFollowsThumb)
if (ScalarFill() is float fill && ScalarFillSprite != 0)
{
var (fillX, visibleWidth) = ScalarThumbFillRect(
Width, thumbWidth, ScalarPosition);
// The authored charge child spans the entire scrollbar; it does
// not share the dark range child's inset geometry.
var (fillX, visibleWidth) = ScalarFillRect(
Width, fill, ScalarFillFromRight);
DrawTiledClipped(ctx, resolve, ScalarFillSprite,
0f, fillX, visibleWidth, Height);
}
else if (ScalarFill() is float fill && ScalarFillSprite != 0)
{
var (fillX, visibleWidth) = ScalarFillRect(
rangeLeft, rangeWidth, fill, ScalarFillFromRight);
DrawTiledClipped(ctx, resolve, ScalarFillSprite,
rangeLeft, fillX, visibleWidth, Height);
}
float travel = MathF.Max(0f, Width - thumbWidth);
float x = travel * ScalarPosition;
DrawSprite(ctx, resolve, ThumbSprite, x, 0f, thumbWidth, Height);
@ -246,10 +238,18 @@ public sealed class UiScrollbar : UiElement
ctx.DrawSprite(tex, x, 0f, w, h, u0, 0f, u1, h / th, Vector4.One);
}
private (float left, float width) ScalarRangeRect()
internal (float left, float width) ScalarRangeRect()
{
(float configuredLeft, float configuredWidth) = ScalarRangeProvider?.Invoke()
?? (ScalarRangeLeft, ScalarRangeWidth);
float configuredLeft = ScalarRangeLeft;
float configuredWidth = ScalarRangeWidth;
if (ScalarRangeLayoutPolicy is { } policy)
{
UiPixelRect currentParent = UiPixelRect.FromPositionAndSize(
0, 0, (int)Width, (int)Height);
UiPixelRect range = policy.Apply(policy.OriginalChild, currentParent);
configuredLeft = range.X0;
configuredWidth = range.Width;
}
float left = Math.Clamp(configuredLeft, 0f, Width);
float requestedWidth = float.IsPositiveInfinity(configuredWidth)
? Width - left
@ -258,17 +258,6 @@ public sealed class UiScrollbar : UiElement
return (left, width);
}
/// <summary>Returns the absolute-left fill ending at the scalar thumb center.</summary>
public static (float x, float width) ScalarThumbFillRect(
float totalWidth, float thumbWidth, float position)
{
float safeWidth = MathF.Max(0f, totalWidth);
float safeThumb = Math.Clamp(thumbWidth, 0f, safeWidth);
float travel = safeWidth - safeThumb;
float center = travel * Math.Clamp(position, 0f, 1f) + safeThumb * 0.5f;
return (0f, center);
}
public override bool OnEvent(in UiEvent e)
{
if (Horizontal && ScalarChanged is not null)