diff --git a/src/AcDream.App/UI/IUiViewportRenderer.cs b/src/AcDream.App/UI/IUiViewportRenderer.cs new file mode 100644 index 00000000..13023078 --- /dev/null +++ b/src/AcDream.App/UI/IUiViewportRenderer.cs @@ -0,0 +1,11 @@ +namespace AcDream.App.UI; + +/// Renders a 3-D mini-scene into an off-screen buffer and returns the GL color-texture +/// handle. Called by the per-frame pre-UI hook (GameWindow), NOT from UiViewport.OnDraw. Implemented +/// by PaperdollViewportRenderer in AcDream.App.Rendering. Intra-App decoupling so the UI widget +/// doesn't depend on WbDrawDispatcher/GameWindow. +public interface IUiViewportRenderer +{ + /// Render at (width,height); return the color-texture GL handle, or 0 if nothing rendered. + uint Render(int width, int height); +} diff --git a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs index 2fb75977..125f174a 100644 --- a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs +++ b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs @@ -62,13 +62,14 @@ public static class DatWidgetFactory // still ships — it just isn't wired into the factory switch yet. UiElement e = info.Type switch { - 1 => new UiButton(info, resolve), // UIElement_Button (reg :125828) - 6 => new UiMenu(), // UIElement_Menu (reg :120163) - 7 => BuildMeter(info, resolve, datFont), // UIElement_Meter - 11 => new UiScrollbar(), // UIElement_Scrollbar (reg :124137) - 12 => BuildText(info, resolve), // UIElement_Text (reg :115655) + 1 => new UiButton(info, resolve), // UIElement_Button (reg :125828) + 6 => new UiMenu(), // UIElement_Menu (reg :120163) + 7 => BuildMeter(info, resolve, datFont), // UIElement_Meter + 0xD => new UiViewport(), // UIElement_Viewport — 3-D mini-scene blit leaf + 11 => new UiScrollbar(), // UIElement_Scrollbar (reg :124137) + 12 => BuildText(info, resolve), // UIElement_Text (reg :115655) 0x10000031u => new UiItemList(resolve), // UIElement_ItemList — toolbar/inventory/paperdoll slots - _ => new UiDatElement(info, resolve), // generic fallback (incl. Type 3 chrome/containers) + _ => new UiDatElement(info, resolve), // generic fallback (incl. Type 3 chrome/containers) }; // Propagate position + size (pixel-exact from the dat). diff --git a/src/AcDream.App/UI/UiViewport.cs b/src/AcDream.App/UI/UiViewport.cs new file mode 100644 index 00000000..22ceabf5 --- /dev/null +++ b/src/AcDream.App/UI/UiViewport.cs @@ -0,0 +1,25 @@ +using System.Numerics; + +namespace AcDream.App.UI; + +/// Leaf widget for dat Type 0xD (UIElement_Viewport). Blits the texture produced by its +/// IUiViewportRenderer (run in the pre-UI hook) as a single sprite at its own rect. The 3-D render +/// does NOT happen here (OnDraw only has a 2-D context). +public sealed class UiViewport : UiElement +{ + public override bool ConsumesDatChildren => true; + + /// Renderer that produces the off-screen texture. Set by GameWindow wiring (later task). + public IUiViewportRenderer? Renderer { get; set; } + + /// Last GL color-texture handle produced by the pre-UI hook. 0 = nothing to blit. + public uint TextureHandle { get; set; } + + protected override void OnDraw(UiRenderContext ctx) + { + if (!Visible || TextureHandle == 0) return; + // Local origin is already at this widget's Left/Top (PushTransform applied by DrawSelfAndChildren). + // Draw the full-texture sprite with UV 0..1 and white tint (no color modulation). + ctx.DrawSprite(TextureHandle, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/UiViewportFactoryTests.cs b/tests/AcDream.App.Tests/UI/Layout/UiViewportFactoryTests.cs new file mode 100644 index 00000000..03a4873c --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/UiViewportFactoryTests.cs @@ -0,0 +1,29 @@ +using AcDream.App.UI; +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI.Layout; + +public class UiViewportFactoryTests +{ + private static (uint, int, int) NoTex(uint _) => (0, 0, 0); + + [Fact] + public void Factory_builds_UiViewport_for_dat_type_0xD() + { + var info = new ElementInfo { Type = 0xD, Width = 200, Height = 300 }; + var widget = DatWidgetFactory.Create(info, NoTex, null); + var viewport = Assert.IsType(widget); + Assert.True(viewport.ConsumesDatChildren); + } + + [Fact] + public void UiViewport_rect_set_from_ElementInfo() + { + var info = new ElementInfo { Type = 0xD, X = 10, Y = 20, Width = 180, Height = 240 }; + var widget = DatWidgetFactory.Create(info, NoTex, null)!; + Assert.Equal(10f, widget.Left); + Assert.Equal(20f, widget.Top); + Assert.Equal(180f, widget.Width); + Assert.Equal(240f, widget.Height); + } +}