feat(ui): port retained widget foundations

This commit is contained in:
Erik 2026-07-10 17:55:41 +02:00
parent 44f9ec13d9
commit d825572e31
44 changed files with 84813 additions and 292 deletions

View file

@ -22,6 +22,7 @@ namespace AcDream.App.UI;
/// </summary>
public sealed class UiField : UiElement
{
public uint ElementId { get; set; }
public UiDatFont? DatFont { get; set; }
public AcDream.App.Rendering.BitmapFont? Font { get; set; }
public Vector4 TextColor { get; set; } = new(1f, 1f, 1f, 1f);
@ -30,6 +31,8 @@ public sealed class UiField : UiElement
public Vector4 SelectionColor { get; set; } = new(0.25f, 0.45f, 0.85f, 0.5f);
public float Padding { get; set; } = 4f;
public int MaxCharacters { get; set; } = 0xFFFF;
public bool OneLine { get; set; } = true;
public bool Selectable { get; set; }
/// <summary>Keyboard device for clipboard (Ctrl+C/X/V) + modifier state (Ctrl/Shift).
/// Wired by the host from <see cref="UiHost.Keyboard"/>.</summary>
@ -38,6 +41,8 @@ public sealed class UiField : UiElement
/// <summary>Dat sprite resolver (id → GL texture + size) for the focused-field
/// background. Null = fall back to the flat <see cref="BackgroundColor"/> rect.</summary>
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
/// <summary>Unfocused/default state sprite imported from the DAT.</summary>
public uint BackgroundSprite { get; set; }
/// <summary>Gold "lit" field background drawn when focused (retail Normal_focussed
/// state, RenderSurface 0x060011AB). 0 = no focus sprite.</summary>
public uint FocusFieldSprite { get; set; }
@ -262,7 +267,8 @@ public sealed class UiField : UiElement
protected override void OnDraw(UiRenderContext ctx)
{
// Focused = "write mode": draw the gold lit field sprite (retail Normal_focussed).
// Unfocused: the flat translucent rect. Both go through the sprite bucket
// Unfocused: draw the imported default state, then the flat translucent fallback.
// Both go through the sprite bucket
// (DrawFill / DrawSprite) so the text — also sprite-bucket — draws on top.
bool lit = _focused && SpriteResolve is not null && FocusFieldSprite != 0;
if (lit)
@ -271,6 +277,16 @@ public sealed class UiField : UiElement
if (tex != 0 && tw > 0) ctx.DrawSprite(tex, 0, 0, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
else lit = false;
}
if (!lit && SpriteResolve is not null && BackgroundSprite != 0)
{
var (tex, tw, th) = SpriteResolve(BackgroundSprite);
if (tex != 0 && tw > 0 && th > 0)
{
ctx.DrawSprite(tex, 0, 0, Width, Height, 0f, 0f,
Width / tw, Height / th, Vector4.One);
lit = true;
}
}
if (!lit) ctx.DrawFill(0, 0, Width, Height, BackgroundColor);
float lh = DatFont?.LineHeight ?? Font?.LineHeight ?? 14f;
@ -366,11 +382,11 @@ public sealed class UiField : UiElement
case UiEventType.MouseDown:
_caret = HitCharX(e.Data1);
_selAnchor = _caret; // anchor; a drag will extend, a plain click won't
_selecting = true;
_selAnchor = Selectable ? _caret : null;
_selecting = Selectable;
return true;
case UiEventType.MouseMove:
if (_selecting) _caret = HitCharX(e.Data1);
if (Selectable && _selecting) _caret = HitCharX(e.Data1);
return true;
case UiEventType.MouseUp:
_selecting = false;
@ -387,15 +403,15 @@ public sealed class UiField : UiElement
{
switch (key)
{
case Silk.NET.Input.Key.A: SelectAll(); return true;
case Silk.NET.Input.Key.C: CopySelection(); return true;
case Silk.NET.Input.Key.X: CutSelection(); return true;
case Silk.NET.Input.Key.A when Selectable: SelectAll(); return true;
case Silk.NET.Input.Key.C when Selectable: CopySelection(); return true;
case Silk.NET.Input.Key.X when Selectable: CutSelection(); return true;
case Silk.NET.Input.Key.V: Paste(); return true;
}
return true; // swallow other Ctrl combos while typing
}
bool shift = ShiftHeld();
bool shift = Selectable && ShiftHeld();
switch (key)
{
case Silk.NET.Input.Key.Enter: