feat(ui): persist retained window layouts
This commit is contained in:
parent
a8e9503d2e
commit
921c388e2c
22 changed files with 705 additions and 141 deletions
|
|
@ -180,8 +180,9 @@ public class ChatLayoutConformanceTests
|
|||
maxMin.OnClick!();
|
||||
|
||||
Assert.True(controller.IsMaximized);
|
||||
Assert.Equal(90f, handle.Top);
|
||||
Assert.Equal(130f, handle.Top);
|
||||
Assert.Equal(370f, handle.Height); // DAT max 360 + 2*5px shared chrome
|
||||
Assert.Equal(500f, handle.Top + handle.Height); // lower edge stays fixed while growing upward
|
||||
Assert.Equal(RetailUiStateIds.Maximized, maxMin.ActiveRetailStateId);
|
||||
controller.Root.ApplyAnchor(handle.Width, handle.Height);
|
||||
Assert.Equal(360f, controller.Root.Height);
|
||||
|
|
|
|||
|
|
@ -106,6 +106,59 @@ public sealed class RetailWindowFrameTests
|
|||
Assert.False(content.Resizable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenInventory_RestoredExpandedBeforeFirstDraw_GrowsContentsRows()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadInventory();
|
||||
UiItemList grid = Assert.IsType<UiItemList>(
|
||||
layout.FindElement(InventoryController.ContentsGridId));
|
||||
grid.Columns = 6;
|
||||
grid.CellWidth = 32f;
|
||||
grid.CellHeight = 32f;
|
||||
grid.Flush();
|
||||
for (int i = 0; i < 102; i++)
|
||||
grid.AddItem(new UiItemSlot());
|
||||
|
||||
var root = new UiRoot { Width = 1280, Height = 720 };
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
root,
|
||||
layout.Root,
|
||||
NoTex,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = WindowNames.Inventory,
|
||||
Chrome = RetailWindowChrome.NineSlice,
|
||||
ContentWidth = layout.Root.Width,
|
||||
ContentHeight = layout.Root.Height,
|
||||
Visible = false,
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = ResizeEdges.Bottom,
|
||||
MaxHeight = 560f,
|
||||
});
|
||||
|
||||
// Production installs the controller-owned policies after mounting, while
|
||||
// inventory is still hidden, and captures their design baseline immediately.
|
||||
InventoryController.ConfigureResizeLayout(layout);
|
||||
|
||||
// Persistence restores the hidden window before its first visible draw.
|
||||
handle.ResizeTo(handle.Width, 527f);
|
||||
ApplyAnchors(handle.OuterFrame);
|
||||
grid.LayoutCells();
|
||||
|
||||
Assert.Equal(251f, grid.Height);
|
||||
Assert.Equal(42, grid.Children.Count(child => child.Visible));
|
||||
}
|
||||
|
||||
private static void ApplyAnchors(UiElement parent)
|
||||
{
|
||||
foreach (UiElement child in parent.Children)
|
||||
{
|
||||
child.ApplyAnchor(parent.Width, parent.Height);
|
||||
ApplyAnchors(child);
|
||||
}
|
||||
}
|
||||
|
||||
private static ElementInfo Constraints(int minHeight, int maxHeight)
|
||||
{
|
||||
var info = new ElementInfo();
|
||||
|
|
|
|||
102
tests/AcDream.App.Tests/UI/RetailWindowLayoutPersistenceTests.cs
Normal file
102
tests/AcDream.App.Tests/UI/RetailWindowLayoutPersistenceTests.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public sealed class RetailWindowLayoutPersistenceTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(
|
||||
Path.GetTempPath(), "acdream-window-layout-tests-" + Guid.NewGuid().ToString("N"));
|
||||
private string PathName => Path.Combine(_directory, "settings.json");
|
||||
|
||||
[Fact]
|
||||
public void CompletedInteractions_SaveCompleteCharacterResolutionLayout()
|
||||
{
|
||||
var store = new SettingsStore(PathName);
|
||||
var root = new UiRoot { Width = 800, Height = 600 };
|
||||
RetailWindowHandle handle = Mount(root, "chat");
|
||||
using var persistence = new RetailWindowLayoutPersistence(
|
||||
root.WindowManager, store, () => "Alice", () => (800, 600));
|
||||
|
||||
handle.MoveTo(44f, 55f);
|
||||
handle.ResizeTo(320f, 180f);
|
||||
handle.Hide();
|
||||
|
||||
UiWindowLayout saved = Assert.IsType<UiWindowLayout>(
|
||||
store.LoadWindowLayout("Alice", "800x600", "chat", default));
|
||||
Assert.Equal((44f, 55f, 320f, 180f), (saved.X, saved.Y, saved.Width, saved.Height));
|
||||
Assert.False(saved.Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Restore_ClampsBoundsAndReappliesPanelStateAndVisibility()
|
||||
{
|
||||
var store = new SettingsStore(PathName);
|
||||
store.SaveWindowLayout(
|
||||
"Alice",
|
||||
"640x480",
|
||||
"chat",
|
||||
new UiWindowLayout(900f, 700f, 500f, 300f, false, false, true));
|
||||
|
||||
var root = new UiRoot { Width = 640, Height = 480 };
|
||||
var state = new FakeWindowState();
|
||||
RetailWindowHandle handle = Mount(root, "chat", state);
|
||||
using var persistence = new RetailWindowLayoutPersistence(
|
||||
root.WindowManager, store, () => "Alice", () => (640, 480));
|
||||
|
||||
persistence.RestoreAll();
|
||||
|
||||
Assert.Equal((500f, 300f), (handle.Width, handle.Height));
|
||||
Assert.Equal((140f, 180f), (handle.Left, handle.Top));
|
||||
Assert.False(handle.IsVisible);
|
||||
Assert.True(state.Restored.Maximized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultPreLoginKey_NeverOverwritesCharacterLayouts()
|
||||
{
|
||||
var store = new SettingsStore(PathName);
|
||||
var root = new UiRoot { Width = 800, Height = 600 };
|
||||
RetailWindowHandle handle = Mount(root, "radar");
|
||||
using var persistence = new RetailWindowLayoutPersistence(
|
||||
root.WindowManager, store, () => "default", () => (800, 600));
|
||||
|
||||
handle.MoveTo(123f, 234f);
|
||||
|
||||
Assert.Null(store.LoadWindowLayout("default", "800x600", "radar", default));
|
||||
}
|
||||
|
||||
private static RetailWindowHandle Mount(
|
||||
UiRoot root,
|
||||
string name,
|
||||
IRetainedWindowStateController? state = null)
|
||||
{
|
||||
var frame = new UiPanel
|
||||
{
|
||||
Left = 10f,
|
||||
Top = 20f,
|
||||
Width = 200f,
|
||||
Height = 100f,
|
||||
MinWidth = 100f,
|
||||
MinHeight = 80f,
|
||||
MaxWidth = 600f,
|
||||
MaxHeight = 400f,
|
||||
Draggable = true,
|
||||
Resizable = true,
|
||||
};
|
||||
root.AddChild(frame);
|
||||
return root.RegisterWindow(name, frame, stateController: state);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true);
|
||||
}
|
||||
|
||||
private sealed class FakeWindowState : IRetainedWindowStateController
|
||||
{
|
||||
public RetainedWindowState Restored { get; private set; }
|
||||
public RetainedWindowState CaptureWindowState() => Restored;
|
||||
public void RestoreWindowState(RetainedWindowState state) => Restored = state;
|
||||
}
|
||||
}
|
||||
|
|
@ -384,4 +384,54 @@ public sealed class SettingsStoreTests : System.IDisposable
|
|||
Assert.Null(store.LoadWindowPosition("Alice", "inventory"));
|
||||
Assert.True(store.LoadGameplay().LockUI);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WindowLayouts_RoundTripPerCharacterResolutionAndPreserveSettings()
|
||||
{
|
||||
var store = new SettingsStore(_tempPath);
|
||||
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
|
||||
var alice1080 = new UiWindowLayout(10f, 20f, 500f, 300f, true, false, true);
|
||||
var alice1440 = new UiWindowLayout(30f, 40f, 650f, 420f, false, true, false);
|
||||
var bob1080 = new UiWindowLayout(50f, 60f, 310f, 132f, true, false, false);
|
||||
|
||||
store.SaveWindowLayout("Alice", "1920x1080", "chat", alice1080);
|
||||
store.SaveWindowLayout("Alice", "2560x1440", "chat", alice1440);
|
||||
store.SaveWindowLayout("Bob", "1920x1080", "toolbar", bob1080);
|
||||
|
||||
Assert.Equal(alice1080, store.LoadWindowLayout("Alice", "1920x1080", "chat", default));
|
||||
Assert.Equal(alice1440, store.LoadWindowLayout("Alice", "2560x1440", "chat", default));
|
||||
Assert.Equal(bob1080, store.LoadWindowLayout("Bob", "1920x1080", "toolbar", default));
|
||||
Assert.Null(store.LoadWindowLayout("Alice", "1920x1080", "inventory", default));
|
||||
Assert.True(store.LoadGameplay().LockUI);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WindowLayouts_MigrateLegacyRadarPositionOverCurrentDefaults()
|
||||
{
|
||||
var store = new SettingsStore(_tempPath);
|
||||
store.SaveWindowPosition("Alice", "radar", new UiWindowPosition(321.5f, 18f));
|
||||
var current = new UiWindowLayout(10f, 10f, 160f, 180f, true, false, false);
|
||||
|
||||
UiWindowLayout migrated = Assert.IsType<UiWindowLayout>(
|
||||
store.LoadWindowLayout("Alice", "1920x1080", "radar", current));
|
||||
|
||||
Assert.Equal(321.5f, migrated.X);
|
||||
Assert.Equal(18f, migrated.Y);
|
||||
Assert.Equal(current.Width, migrated.Width);
|
||||
Assert.Equal(current.Height, migrated.Height);
|
||||
Assert.Equal(current.Visible, migrated.Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WindowLayouts_FallBackToNearestSavedResolution()
|
||||
{
|
||||
var store = new SettingsStore(_tempPath);
|
||||
var hd = new UiWindowLayout(100f, 110f, 400f, 250f, true, false, false);
|
||||
var uhd = new UiWindowLayout(300f, 310f, 600f, 450f, true, false, false);
|
||||
store.SaveWindowLayout("Alice", "1280x720", "chat", hd);
|
||||
store.SaveWindowLayout("Alice", "3840x2160", "chat", uhd);
|
||||
|
||||
Assert.Equal(hd, store.LoadWindowLayout("Alice", "1600x900", "chat", default));
|
||||
Assert.Equal(uhd, store.LoadWindowLayout("Alice", "3200x1800", "chat", default));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue