Rename UiChatView -> UiText (the retail UIElement_Text class, RegisterElementClass(0xc) @ acclient_2013_pseudo_c.txt:115655). Factory changes (DatWidgetFactory.cs): - Remove the Type-12 skip (was: no-media -> null, with-media -> UiDatElement). - Add Type 12 -> BuildText() -> UiText in the switch. - BuildText extracts the element's Direct/Normal sprite as BackgroundSprite so any dat-media the element carried keeps rendering under the text. UiText changes (renamed from UiChatView.cs): - BackgroundColor default: (0,0,0,0.35) -> (0,0,0,0) (transparent). An unbound UiText draws nothing; the controller opts in to the translucent bg. - New BackgroundSprite + SpriteResolve: optional dat state-sprite background drawn UNDER DrawFill+text (faithful UIElement_Text media support). ChatWindowController.cs (Task 5 Step 8): - Transcript property: UiChatView -> UiText. - Bind() now uses layout.FindElement(TranscriptId) as UiText (factory-built) instead of manually constructing + AddChild-ing a new UiChatView. - Sets BackgroundColor = (0,0,0,0.35) on the found widget (retail translucent bg). - Removes the tInfo null-check from the early guard (transcript is factory-built; iInfo lookup kept for the input widget which is still manually constructed). - BuildLines: UiChatView.Line -> UiText.Line throughout. Vitals frozen: the Type-12 vitals number elements are meter children and are never recursed by BuildWidget (the `if (w is not UiMeter)` gate), so they are not built as widgets and keep rendering via UiMeter.Label. Vitals fixture vitals_2100006C.json unchanged; LayoutConformanceTests + VitalsBindingTests green. Tests: - UiChatViewTests.cs -> UiTextTests.cs (class: UiTextTests, all UiChatView.* -> UiText.*) - UiChatViewDatFontTests.cs -> UiTextDatFontTests.cs (same) - DatWidgetFactoryTests: delete Type12_StylePrototype_ReturnsNull + DatWidgetFactory_Type12WithMedia_Renders; add Type12_Text_MakesUiText + DatWidgetFactory_Type12_AlwaysMakesUiText. - LayoutImporterTests: BuildFromInfos_Type12Child_IsSkipped_Type3Present updated to assert IsType<UiText> (element is now in tree, transparent, not skipped). Divergence register: AP-37 amended -- removed the "standalone Type-0 text elements skipped / dat-text widget is Plan 2" clause (now shipped as UiText); kept the meter-collapse clause and the vitals-numbers-via-UiMeter.Label clause. AP-38/AP-39/AD-28 file references updated UiChatView.cs -> UiText.cs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
4 KiB
C#
110 lines
4 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using Silk.NET.Input;
|
|
using Silk.NET.OpenGL;
|
|
|
|
namespace AcDream.App.UI;
|
|
|
|
/// <summary>
|
|
/// Packages the <see cref="UiRoot"/>, the 2D sprite batcher
|
|
/// (<see cref="Rendering.TextRenderer"/>), and a default font so
|
|
/// <c>GameWindow</c> can wire the retail-style UI in with one
|
|
/// construction and a handful of input callbacks.
|
|
///
|
|
/// Usage (from <c>GameWindow.OnLoad</c>):
|
|
/// <code>
|
|
/// _uiHost = new UiHost(_gl, shadersDir, _debugFont);
|
|
/// _uiHost.Root.WorldMouseFallThrough += (btn, x, y, f) => HandleWorldClick(btn, x, y);
|
|
/// _uiHost.Root.WorldKeyFallThrough += (vk, lp) => HandleHotkey(vk);
|
|
///
|
|
/// foreach (var mouse in _input.Mice)
|
|
/// _uiHost.WireMouse(mouse);
|
|
/// foreach (var kb in _input.Keyboards)
|
|
/// _uiHost.WireKeyboard(kb);
|
|
/// </code>
|
|
///
|
|
/// And per frame (from <c>GameWindow.OnRender</c>):
|
|
/// <code>
|
|
/// _uiHost.Tick(deltaSeconds);
|
|
/// _uiHost.Draw(new Vector2(_window!.Size.X, _window.Size.Y));
|
|
/// </code>
|
|
///
|
|
/// Retail analog: the trio of <c>DAT_00870340</c> (Core, owns fonts/atlases),
|
|
/// <c>DAT_00837ff4</c> (Device, owns input state), <c>DAT_00870c2c</c>
|
|
/// (Keystone root, widget tree). We fuse them into a single host class
|
|
/// because we're not linking to Keystone.
|
|
/// </summary>
|
|
public sealed class UiHost : System.IDisposable
|
|
{
|
|
public UiRoot Root { get; } = new();
|
|
public TextRenderer TextRenderer { get; }
|
|
public BitmapFont? DefaultFont { get; set; }
|
|
|
|
/// <summary>The last wired keyboard. Exposed so widgets that need clipboard
|
|
/// access (<see cref="IKeyboard.ClipboardText"/>) or modifier-key state
|
|
/// (<see cref="IKeyboard.IsKeyPressed"/>) — e.g. <see cref="UiText"/>'s
|
|
/// Ctrl+C copy — can reach the device. One-keyboard desktop: last wins.</summary>
|
|
public IKeyboard? Keyboard { get; private set; }
|
|
|
|
private long _startTicks = System.Environment.TickCount64;
|
|
|
|
public UiHost(GL gl, string shaderDir, BitmapFont? defaultFont = null)
|
|
{
|
|
TextRenderer = new TextRenderer(gl, shaderDir);
|
|
DefaultFont = defaultFont;
|
|
}
|
|
|
|
// ── Per-frame ──────────────────────────────────────────────────────
|
|
|
|
public void Tick(double deltaSeconds)
|
|
{
|
|
long now = System.Environment.TickCount64 - _startTicks;
|
|
Root.Tick(deltaSeconds, now);
|
|
}
|
|
|
|
public void Draw(Vector2 screenSize)
|
|
{
|
|
// Set UiRoot bounds to full screen so HitTestTopDown works.
|
|
Root.Width = screenSize.X;
|
|
Root.Height = screenSize.Y;
|
|
var ctx = new UiRenderContext(TextRenderer, screenSize, DefaultFont);
|
|
TextRenderer.Begin(screenSize);
|
|
Root.Draw(ctx);
|
|
TextRenderer.Flush(DefaultFont);
|
|
}
|
|
|
|
// ── Input wiring helpers ───────────────────────────────────────────
|
|
|
|
public void WireMouse(IMouse mouse)
|
|
{
|
|
mouse.MouseDown += (_, b) =>
|
|
Root.OnMouseDown(MapButton(b), (int)mouse.Position.X, (int)mouse.Position.Y);
|
|
mouse.MouseUp += (_, b) =>
|
|
Root.OnMouseUp(MapButton(b), (int)mouse.Position.X, (int)mouse.Position.Y);
|
|
mouse.MouseMove += (_, p) =>
|
|
Root.OnMouseMove((int)p.X, (int)p.Y);
|
|
mouse.Scroll += (_, s) =>
|
|
Root.OnScroll((int)s.Y);
|
|
}
|
|
|
|
public void WireKeyboard(IKeyboard kb)
|
|
{
|
|
Keyboard = kb; // last wired keyboard wins (one-keyboard desktop)
|
|
kb.KeyDown += (_, k, _) => Root.OnKeyDown((int)k);
|
|
kb.KeyUp += (_, k, _) => Root.OnKeyUp((int)k);
|
|
kb.KeyChar += (_, c) => Root.OnChar(c);
|
|
}
|
|
|
|
private static UiMouseButton MapButton(MouseButton b) => b switch
|
|
{
|
|
MouseButton.Left => UiMouseButton.Left,
|
|
MouseButton.Right => UiMouseButton.Right,
|
|
MouseButton.Middle => UiMouseButton.Middle,
|
|
_ => UiMouseButton.Left,
|
|
};
|
|
|
|
public void Dispose()
|
|
{
|
|
TextRenderer.Dispose();
|
|
}
|
|
}
|