acdream/src/AcDream.App/UI/RetailWindowHandle.cs

131 lines
4.4 KiB
C#

using System;
namespace AcDream.App.UI;
/// <summary>
/// Typed ownership handle for one named retained window. Geometry and opacity
/// mutations flow through this handle so lifecycle observers see the same state
/// regardless of whether a change came from input, restore, or controller logic.
/// </summary>
public sealed class RetailWindowHandle
{
private readonly RetailWindowManager _owner;
private bool _notifiedVisible;
private bool _closedSinceShown;
private bool _disposed;
internal RetailWindowHandle(
RetailWindowManager owner,
string name,
UiElement outerFrame,
UiElement contentRoot,
IRetainedPanelController? controller,
IRetainedWindowStateController? stateController)
{
_owner = owner;
Name = name;
OuterFrame = outerFrame;
ContentRoot = contentRoot;
Controller = controller;
StateController = stateController;
_notifiedVisible = outerFrame.Visible;
}
public string Name { get; }
public UiElement OuterFrame { get; }
public UiElement ContentRoot { get; }
public IRetainedPanelController? Controller { get; private set; }
public IRetainedWindowStateController? StateController { get; }
public bool IsRegistered => !_disposed;
public bool IsVisible => OuterFrame.Visible;
public bool IsLocked => _owner.IsLocked;
public float Left => OuterFrame.Left;
public float Top => OuterFrame.Top;
public float Width => OuterFrame.Width;
public float Height => OuterFrame.Height;
public float Opacity => OuterFrame.Opacity;
public event Action<RetailWindowHandle>? Shown;
public event Action<RetailWindowHandle>? Hidden;
public event Action<RetailWindowHandle>? Moved;
public event Action<RetailWindowHandle>? Resized;
public event Action<RetailWindowHandle>? Closed;
public event Action<RetailWindowHandle, bool>? LockChanged;
public event Action<RetailWindowHandle, UiElement?>? DescendantFocusChanged;
public event Action<RetailWindowHandle, UiElement?>? DescendantCaptureChanged;
public bool Show() => IsRegistered && _owner.Show(Name);
public bool Hide() => IsRegistered && _owner.Hide(Name);
public bool Close() => IsRegistered && _owner.Close(Name);
public bool MoveTo(float left, float top)
=> IsRegistered && _owner.MoveTo(Name, left, top);
public bool ResizeTo(float width, float height)
=> IsRegistered && _owner.ResizeTo(Name, width, height);
public bool SetOpacity(float opacity)
=> IsRegistered && _owner.SetOpacity(Name, opacity);
internal void NotifyInitialState()
{
if (_notifiedVisible)
Controller?.OnShown();
}
internal void AttachController(IRetainedPanelController controller)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ArgumentNullException.ThrowIfNull(controller);
if (ReferenceEquals(Controller, controller)) return;
if (Controller is not null)
throw new InvalidOperationException($"Retained window '{Name}' already owns a controller.");
Controller = controller;
if (_notifiedVisible)
Controller.OnShown();
}
internal void NotifyVisibility(bool visible)
{
if (_notifiedVisible == visible) return;
_notifiedVisible = visible;
if (visible)
{
_closedSinceShown = false;
Controller?.OnShown();
Shown?.Invoke(this);
}
else
{
Controller?.OnHidden();
Hidden?.Invoke(this);
}
}
internal void NotifyMoved() => Moved?.Invoke(this);
internal void NotifyResized() => Resized?.Invoke(this);
internal void NotifyClosed()
{
if (_closedSinceShown) return;
_closedSinceShown = true;
Closed?.Invoke(this);
}
internal void NotifyLockChanged(bool locked) => LockChanged?.Invoke(this, locked);
internal void NotifyDescendantFocusChanged(UiElement? focusedDescendant)
{
Controller?.OnDescendantFocusChanged(focusedDescendant);
DescendantFocusChanged?.Invoke(this, focusedDescendant);
}
internal void NotifyDescendantCaptureChanged(UiElement? capturedDescendant)
=> DescendantCaptureChanged?.Invoke(this, capturedDescendant);
internal void DisposeController()
{
if (_disposed) return;
_disposed = true;
Controller?.Dispose();
}
}