using System; namespace AcDream.App.UI; /// /// 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. /// 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? Shown; public event Action? Hidden; public event Action? Moved; public event Action? Resized; public event Action? Closed; public event Action? LockChanged; public event Action? DescendantFocusChanged; public event Action? 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(); } }