feat(ui): port retail selected-stack quantity
Bind the authored stack count entry and horizontal slider to one Core split-quantity owner, preserve retail count-first naming and exact 1000-step rounding, refresh on stack changes, and consume the selected amount during merges. Conformance covers the production DAT fixture and retained pointer/focus paths. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
dc1649c493
commit
a20e5c68c7
19 changed files with 595 additions and 51 deletions
|
|
@ -25,6 +25,14 @@ public sealed class UiScrollbar : UiElement
|
|||
/// <summary>The scroll model this bar reflects + drives (shared with the transcript).</summary>
|
||||
public UiScrollable? Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional scalar mode used by retail's horizontal stack-split control. When set,
|
||||
/// the bar reflects/drives one normalized value rather than a <see cref="UiScrollable"/>.
|
||||
/// </summary>
|
||||
public Func<float>? ScalarValue { get; set; }
|
||||
public Action<float>? ScalarChanged { get; set; }
|
||||
public bool Horizontal { get; set; }
|
||||
|
||||
/// <summary>RenderSurface id → (GL tex, w, h). 0 id = skip.</summary>
|
||||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||
|
||||
|
|
@ -58,6 +66,7 @@ public sealed class UiScrollbar : UiElement
|
|||
|
||||
private bool _draggingThumb;
|
||||
private float _dragOffsetY;
|
||||
private float _dragOffsetX;
|
||||
|
||||
public UiScrollbar() { CapturesPointerDrag = true; }
|
||||
|
||||
|
|
@ -85,7 +94,18 @@ public sealed class UiScrollbar : UiElement
|
|||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
if (Model is not { } m || SpriteResolve is not { } resolve) return;
|
||||
if (SpriteResolve is not { } resolve) return;
|
||||
if (Horizontal && ScalarValue is not null)
|
||||
{
|
||||
DrawTiled(ctx, resolve, TrackSprite, 0f, 0f, Width, Height);
|
||||
float thumbWidth = ScalarThumbWidth(resolve);
|
||||
float travel = MathF.Max(0f, Width - thumbWidth);
|
||||
float x = travel * Math.Clamp(ScalarValue(), 0f, 1f);
|
||||
DrawSprite(ctx, resolve, ThumbSprite, x, 0f, thumbWidth, Height);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Model is not { } m) return;
|
||||
|
||||
// Track background — TILED vertically (retail DrawMode=Normal). The native track
|
||||
// sprite (~16×32) repeats to fill the element height instead of stretch-distorting.
|
||||
|
|
@ -142,6 +162,9 @@ public sealed class UiScrollbar : UiElement
|
|||
|
||||
public override bool OnEvent(in UiEvent e)
|
||||
{
|
||||
if (Horizontal && ScalarValue is not null && ScalarChanged is not null)
|
||||
return OnScalarEvent(e);
|
||||
|
||||
if (Model is not { } m) return false;
|
||||
|
||||
switch (e.Type)
|
||||
|
|
@ -196,4 +219,53 @@ public sealed class UiScrollbar : UiElement
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnScalarEvent(in UiEvent e)
|
||||
{
|
||||
switch (e.Type)
|
||||
{
|
||||
case UiEventType.MouseDown:
|
||||
{
|
||||
float thumbWidth = ScalarThumbWidth(SpriteResolve);
|
||||
float travel = MathF.Max(1f, Width - thumbWidth);
|
||||
float thumbX = travel * Math.Clamp(ScalarValue!(), 0f, 1f);
|
||||
float x = e.Data1;
|
||||
if (x >= thumbX && x <= thumbX + thumbWidth)
|
||||
{
|
||||
_dragOffsetX = x - thumbX;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dragOffsetX = thumbWidth * 0.5f;
|
||||
ScalarChanged!(Math.Clamp((x - _dragOffsetX) / travel, 0f, 1f));
|
||||
}
|
||||
_draggingThumb = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
case UiEventType.MouseMove when _draggingThumb:
|
||||
{
|
||||
float thumbWidth = ScalarThumbWidth(SpriteResolve);
|
||||
float travel = MathF.Max(1f, Width - thumbWidth);
|
||||
ScalarChanged!(Math.Clamp(((float)e.Data1 - _dragOffsetX) / travel, 0f, 1f));
|
||||
return true;
|
||||
}
|
||||
|
||||
case UiEventType.MouseUp:
|
||||
_draggingThumb = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private float ScalarThumbWidth(Func<uint, (uint tex, int w, int h)>? resolve)
|
||||
{
|
||||
if (resolve is not null && ThumbSprite != 0)
|
||||
{
|
||||
var (_, width, _) = resolve(ThumbSprite);
|
||||
if (width > 0) return MathF.Min(width, Width);
|
||||
}
|
||||
return MathF.Min(16f, Width);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue