Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@ internal sealed class ProjectileDebugOverlayController
private static readonly Vector4 ClearColor = new(0f, 1f, 0f, 0.95f);
private static readonly Vector4 BlockedColor = new(1f, 0f, 0f, 0.95f);

private readonly UiOverlayHost _host;
private readonly UiPanel _root;
private readonly Func<IReadOnlyList<PluginProjectileDebugSample>> _samples;
private readonly Func<(Matrix4x4 View, Matrix4x4 Projection, Vector2 Viewport)>
_camera;
private readonly List<UiPanel> _markers = [];

private ProjectileDebugOverlayController(
UiOverlayHost host,
UiPanel root,
Func<IReadOnlyList<PluginProjectileDebugSample>> samples,
Func<(Matrix4x4 View, Matrix4x4 Projection, Vector2 Viewport)> camera)
{
_host = host;
_root = root;
_samples = samples;
_camera = camera;
Expand All @@ -42,7 +39,7 @@ internal static ProjectileDebugOverlayController Mount(
ArgumentNullException.ThrowIfNull(samples);
ArgumentNullException.ThrowIfNull(camera);
UiPanel root = host.AddLayer("PluginProjectileDebugOverlay");
return new ProjectileDebugOverlayController(host, root, samples, camera);
return new ProjectileDebugOverlayController(root, samples, camera);
}

internal void Tick()
Expand All @@ -58,7 +55,6 @@ internal void Tick()
}

EnsureMarkerCount(samples.Count);
_host.SetViewport(camera.Viewport);
int visible = 0;
for (int index = 0; index < samples.Count; index++)
{
Expand Down
26 changes: 16 additions & 10 deletions src/AcDream.App/UI/Layout/UiOverlayHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,20 @@ private static void MakeSubtreeClickThrough(UiElement element)
/// <para>Children are clipped to their parent's rectangle by default, so an
/// overlay whose rectangle is still zero-sized shows nothing at all. That is
/// why the host seeds its rectangle from the interface root at mount and
/// re-applies it to the root and every layer on
/// <see cref="SetViewport(Vector2)"/>.</para>
/// re-applies it to the root and every layer on <see cref="FollowRoot"/>.
/// The interface root is the one source of that rectangle: a world overlay
/// projects into its camera's viewport but never resizes the layers.</para>
/// </summary>
internal sealed class UiOverlayHost
{
private readonly UiRoot _host;
private readonly UiOverlayLayer _root;
private readonly List<UiOverlayLayer> _layers = [];
private Vector2 _viewport;

private UiOverlayHost(UiOverlayLayer root, Vector2 viewport)
private UiOverlayHost(UiRoot host, UiOverlayLayer root, Vector2 viewport)
{
_host = host;
_root = root;
_viewport = viewport;
}
Expand All @@ -144,7 +147,7 @@ internal static UiOverlayHost Mount(UiRoot host)
ZOrder = UiOverlayZOrder.SharedHostRoot,
};
host.AddChild(root);
var overlayHost = new UiOverlayHost(root, new Vector2(host.Width, host.Height));
var overlayHost = new UiOverlayHost(host, root, new Vector2(host.Width, host.Height));
overlayHost.ApplyViewport();
return overlayHost;
}
Expand All @@ -166,14 +169,17 @@ internal UiOverlayLayer AddLayer(string name)
}

/// <summary>
/// Points the host at the rectangle the world is being drawn into. Every
/// layer covers the same rectangle: an overlay is by definition the whole
/// screen, and one shared value keeps two owners from clipping each other.
/// Brings the root and every layer to the interface root's current size.
/// The interface calls it every frame, after sizing its root to the
/// screen: the root can also be resized elsewhere (restoring the layout
/// on entering the world does), so the host follows the root rather than
/// waiting to be told of a change.
/// </summary>
internal void SetViewport(Vector2 viewport)
internal void FollowRoot()
{
if (_viewport == viewport) return;
_viewport = viewport;
var size = new Vector2(_host.Width, _host.Height);
if (_viewport == size) return;
_viewport = size;
ApplyViewport();
}

Expand Down
6 changes: 1 addition & 5 deletions src/AcDream.App/UI/Layout/WorldLabelOverlayController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ protected override void OnDraw(UiRenderContext ctx)
/// </summary>
internal sealed class WorldLabelOverlayController
{
private readonly UiOverlayHost _host;
private readonly UiOverlayLayer _layer;
private readonly WorldLabelLayerElement _element;
private readonly UiDatFont _font;
Expand All @@ -234,15 +233,13 @@ internal sealed class WorldLabelOverlayController
private List<WorldLabelPlacement> _back = [];

private WorldLabelOverlayController(
UiOverlayHost host,
UiOverlayLayer layer,
WorldLabelLayerElement element,
UiDatFont font,
Func<IReadOnlyList<PluginWorldLabel>> labels,
Func<uint, WorldLabelAnchor?> anchor,
Func<(Matrix4x4 View, Matrix4x4 Projection, Vector2 Viewport)> camera)
{
_host = host;
_layer = layer;
_element = element;
_font = font;
Expand Down Expand Up @@ -277,7 +274,7 @@ internal static WorldLabelOverlayController Mount(
};
layer.AddChild(element);
return new WorldLabelOverlayController(
host, layer, element, font, labels, anchor, camera);
layer, element, font, labels, anchor, camera);
}

internal void Tick()
Expand All @@ -293,7 +290,6 @@ internal void Tick()
return;
}

_host.SetViewport(camera.Viewport);
_element.Width = camera.Viewport.X;
_element.Height = camera.Viewport.Y;
WorldLabelLayout.Place(
Expand Down
8 changes: 5 additions & 3 deletions src/AcDream.App/UI/RetailUiRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,12 @@ public void Draw(System.Numerics.Vector2 screenSize)
_persistence?.SetGameplayActive(gameplay);
if (gameplay) _persistence?.ReflowToScreen();
_lastScreenSize = screenSize;
// The overlay band covers the whole screen; a canvas anchored to
// a right or bottom edge is placed against this rectangle.
_overlayHost?.SetViewport(screenSize);
}
// The overlay band covers the whole screen; a canvas anchored to a
// right or bottom edge is placed against this rectangle. Followed
// every frame, not only on a change seen above: restoring the layout
// resizes the root without passing through here.
_overlayHost?.FollowRoot();

Host.Draw(screenSize);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,9 @@ public void ACanvasIsPlacedByAnchorPlusOffsetAndFollowsTheViewport()

registration.Anchor = PluginCanvasAnchor.BottomRight;
registration.Offset = new PluginPoint(-10, -10);
harness.Host.SetViewport(new Vector2(1024f, 768f));
harness.Root.Width = 1024f;
harness.Root.Height = 768f;
harness.Host.FollowRoot();
harness.Frame();
Assert.Equal((814f, 658f), (element.Left, element.Top));
}
Expand Down
40 changes: 37 additions & 3 deletions tests/AcDream.App.Tests/UI/Layout/UiOverlayHostTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Plugin.Abstractions;

namespace AcDream.App.Tests.UI.Layout;

Expand Down Expand Up @@ -151,14 +153,20 @@ public void TheRootTakesItsRectangleFromTheInterfaceRootAtMount()
}

[Fact]
public void ANewViewportReachesTheRootAndEveryLayerIncludingOnesAddedLater()
public void ANewRootSizeReachesTheRootAndEveryLayerIncludingOnesAddedLater()
{
UiOverlayHost host = UiOverlayHost.Mount(Root());
UiRoot interfaceRoot = Root();
UiOverlayHost host = UiOverlayHost.Mount(interfaceRoot);
UiOverlayLayer first = host.AddLayer("First");

host.SetViewport(new Vector2(1920f, 1080f));
// Resized behind the host's back, the way restoring the layout on
// entering the world resizes it: the host is never told the size.
interfaceRoot.Width = 1920f;
interfaceRoot.Height = 1080f;
host.FollowRoot();
UiOverlayLayer second = host.AddLayer("Second");

Assert.Equal(new Vector2(1920f, 1080f), host.Viewport);
foreach (UiElement element in new UiElement[] { host.Root, first, second })
{
Assert.Equal(0f, element.Left);
Expand All @@ -168,6 +176,32 @@ public void ANewViewportReachesTheRootAndEveryLayerIncludingOnesAddedLater()
}
}

[Fact]
public void AWorldOverlayDrawingIntoADifferentViewportLeavesTheLayersAtTheScreenSize()
{
var interfaceRoot = new UiRoot { Width = 1920f, Height = 1080f };
UiOverlayHost host = UiOverlayHost.Mount(interfaceRoot);
UiOverlayLayer canvases = host.AddLayer("Canvases");
var samples = new List<PluginProjectileDebugSample>
{
new(new Vector3(0f, 0f, -10f), true, 0.4f),
};
ProjectileDebugOverlayController overlay = ProjectileDebugOverlayController.Mount(
host,
() => samples,
() => (Matrix4x4.Identity,
Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 2f, 4f / 3f, 0.1f, 100f),
new Vector2(800f, 600f)));

overlay.Tick();

foreach (UiElement element in new UiElement[] { host.Root, canvases })
{
Assert.Equal(1920f, element.Width);
Assert.Equal(1080f, element.Height);
}
}

[Fact]
public void ALayerStartsHiddenClickThroughAnchorlessAndWithoutChrome()
{
Expand Down
Loading