using System.Numerics;
using AcDream.App.Rendering;
using Silk.NET.Input;
using Silk.NET.OpenGL;
namespace AcDream.App.UI;
///
/// Packages the , the 2D sprite batcher
/// (), and a default font so
/// GameWindow can wire the retail-style UI in with one
/// construction and a handful of input callbacks.
///
/// Usage (from GameWindow.OnLoad):
///
/// _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);
///
///
/// And per frame (from GameWindow.OnRender):
///
/// _uiHost.Tick(deltaSeconds);
/// _uiHost.Draw(new Vector2(_window!.Size.X, _window.Size.Y));
///
///
/// Retail analog: the trio of DAT_00870340 (Core, owns fonts/atlases),
/// DAT_00837ff4 (Device, owns input state), DAT_00870c2c
/// (Keystone root, widget tree). We fuse them into a single host class
/// because we're not linking to Keystone.
///
public sealed class UiHost : System.IDisposable
{
public UiRoot Root { get; } = new();
public TextRenderer TextRenderer { get; }
public BitmapFont? DefaultFont { get; set; }
/// The last wired keyboard. Exposed so widgets that need clipboard
/// access () or modifier-key state
/// () — e.g. 's
/// Ctrl+C copy — can reach the device. One-keyboard desktop: last wins.
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();
}
}