using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Avalonia.Automation.Peers; using Avalonia.Controls.Chrome; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Reactive; using Avalonia.Styling; using Avalonia.Utilities; namespace Avalonia.Controls { /// /// Determines how a will size itself to fit its content. /// [Flags] public enum SizeToContent { /// /// The window will not automatically size itself to fit its content. /// Manual = 0, /// /// The window will size itself horizontally to fit its content. /// Width = 1, /// /// The window will size itself vertically to fit its content. /// Height = 2, /// /// The window will size itself horizontally and vertically to fit its content. /// WidthAndHeight = 3, } /// /// Determines window decorations (title bar, border, etc) for a /// public enum WindowDecorations { /// /// No decorations /// None = 0, /// /// Window border without titlebar /// BorderOnly = 1, /// /// Fully decorated (default) /// Full = 2 } /// /// Describes how the event behaves in the presence of child windows. /// public enum WindowClosingBehavior { /// /// When the owner window is closed, the child windows' event /// will be raised, followed by the owner window's events. A child /// canceling the close will result in the owner Window's close being cancelled. /// OwnerAndChildWindows, /// /// When the owner window is closed, only the owner window's event /// will be raised. This behavior is the same as WPF's. /// OwnerWindowOnly, } /// /// A top-level window. /// public class Window : WindowBase, IFocusScope { private static readonly Lazy s_defaultIcon = new(LoadDefaultIcon); private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); private bool _isExtendedIntoWindowDecorations; private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; private bool _canHandleResized = false; private Size _arrangeBounds; /// /// Defines the property. /// public static readonly StyledProperty SizeToContentProperty = AvaloniaProperty.Register(nameof(SizeToContent)); /// /// Defines the property. /// public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); public static readonly StyledProperty ExtendClientAreaTitleBarHeightHintProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaTitleBarHeightHint), -1); /// /// Defines the property. /// public static readonly DirectProperty IsExtendedIntoWindowDecorationsProperty = AvaloniaProperty.RegisterDirect(nameof(IsExtendedIntoWindowDecorations), o => o.IsExtendedIntoWindowDecorations, unsetValue: false); /// /// Defines the property. /// public static readonly DirectProperty WindowDecorationMarginProperty = AvaloniaProperty.RegisterDirect(nameof(WindowDecorationMargin), o => o.WindowDecorationMargin); public static readonly DirectProperty OffScreenMarginProperty = AvaloniaProperty.RegisterDirect(nameof(OffScreenMargin), o => o.OffScreenMargin); /// /// Defines the property. /// public static readonly StyledProperty WindowDecorationsProperty = AvaloniaProperty.Register(nameof(WindowDecorations), WindowDecorations.Full); /// /// Defines the property. /// public static readonly StyledProperty ShowActivatedProperty = AvaloniaProperty.Register(nameof(ShowActivated), true); /// /// Enables or disables the taskbar icon /// public static readonly StyledProperty ShowInTaskbarProperty = AvaloniaProperty.Register(nameof(ShowInTaskbar), true); /// /// Defines the property. /// public static readonly StyledProperty ClosingBehaviorProperty = AvaloniaProperty.Register(nameof(ClosingBehavior)); /// /// Represents the current window state (normal, minimized, maximized) /// public static readonly StyledProperty WindowStateProperty = AvaloniaProperty.Register(nameof(WindowState)); /// /// Defines the property. /// public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register(nameof(Title), "Window"); /// /// Defines the property. /// public static readonly StyledProperty IconProperty = AvaloniaProperty.Register(nameof(Icon)); /// /// Defines the property. /// public static readonly StyledProperty WindowStartupLocationProperty = AvaloniaProperty.Register(nameof(WindowStartupLocation)); /// /// Defines the property. /// public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); /// /// Defines the property. /// public static readonly StyledProperty CanMinimizeProperty = AvaloniaProperty.Register(nameof(CanMinimize), true); /// /// Defines the property. /// public static readonly StyledProperty CanMaximizeProperty = AvaloniaProperty.Register(nameof(CanMaximize), true, coerce: CoerceCanMaximize); /// /// Routed event that can be used for global tracking of window destruction /// public static readonly RoutedEvent WindowClosedEvent = RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct); /// /// Routed event that can be used for global tracking of opening windows /// public static readonly RoutedEvent WindowOpenedEvent = RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); private object? _dialogResult; private readonly Size _maxPlatformClientSize; private bool _shown; private bool _showingAsDialog; private bool _positionWasSet; private bool _wasShownBefore; private IDisposable? _modalSubscription; /// /// Initializes static members of the class. /// static Window() { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler((w, _) => w.OnTitleBarHeightHintChanged()); } /// /// Initializes a new instance of the class. /// public Window() : this(PlatformManager.CreateWindow()) { } /// /// Initializes a new instance of the class. /// /// The window implementation. public Window(IWindowImpl impl) : base(impl) { impl.Closing = HandleClosing; impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, WindowResizeReason.Application)); CreatePlatformImplBinding(TitleProperty, title => PlatformImpl!.SetTitle(title)); CreatePlatformImplBinding(IconProperty, icon => PlatformImpl!.SetIcon((icon ?? s_defaultIcon.Value)?.PlatformImpl)); CreatePlatformImplBinding(CanResizeProperty, canResize => PlatformImpl!.CanResize(canResize)); CreatePlatformImplBinding(CanMinimizeProperty, canMinimize => PlatformImpl!.SetCanMinimize(canMinimize)); CreatePlatformImplBinding(CanMaximizeProperty, canMaximize => PlatformImpl!.SetCanMaximize(canMaximize)); CreatePlatformImplBinding(ShowInTaskbarProperty, show => PlatformImpl!.ShowTaskbarIcon(show)); CreatePlatformImplBinding(WindowStateProperty, state => PlatformImpl!.WindowState = state); CreatePlatformImplBinding(ExtendClientAreaToDecorationsHintProperty, hint => PlatformImpl!.SetExtendClientAreaToDecorationsHint(hint)); CreatePlatformImplBinding(ExtendClientAreaTitleBarHeightHintProperty, height => PlatformImpl!.SetExtendClientAreaTitleBarHeightHint(height)); CreatePlatformImplBinding(MinWidthProperty, UpdateMinMaxSize); CreatePlatformImplBinding(MaxWidthProperty, UpdateMinMaxSize); CreatePlatformImplBinding(MinHeightProperty, UpdateMinMaxSize); CreatePlatformImplBinding(MaxHeightProperty, UpdateMinMaxSize); void UpdateMinMaxSize(double _) => PlatformImpl!.SetMinMaxSize(new Size(MinWidth, MinHeight), new Size(MaxWidth, MaxHeight)); } /// /// Gets the platform-specific window implementation. /// public new IWindowImpl? PlatformImpl => (IWindowImpl?)base.PlatformImpl; /// /// Gets a collection of child windows owned by this window. /// public IReadOnlyList OwnedWindows => _children.Select(x => x.child).ToArray(); /// /// Gets or sets a value indicating how the window will size itself to fit its content. /// /// /// If has a value other than , /// is automatically set to /// if a user resizes the window by using the resize grip or dragging the border. /// /// NOTE: Because of a limitation of X11, will be reset on X11 to /// on any resize - including the resize that happens when /// the window is first shown. This is because X11 resize notifications are asynchronous and /// there is no way to know whether a resize came from the user or the layout system. To avoid /// this, consider setting to false, which will disable user resizing /// of the window. /// public SizeToContent SizeToContent { get => GetValue(SizeToContentProperty); set => SetValue(SizeToContentProperty, value); } /// /// Gets or sets the title of the window. /// public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); } /// /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). /// public bool ExtendClientAreaToDecorationsHint { get => GetValue(ExtendClientAreaToDecorationsHintProperty); set => SetValue(ExtendClientAreaToDecorationsHintProperty, value); } /// /// Gets or Sets the TitlebarHeightHint for when the client area is extended. /// A value of -1 will cause the titlebar to be auto sized to the OS default. /// Any other positive value will cause the titlebar to assume that height. /// public double ExtendClientAreaTitleBarHeightHint { get => GetValue(ExtendClientAreaTitleBarHeightHintProperty); set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value); } /// /// Gets if the ClientArea is Extended into the Window Decorations. /// public bool IsExtendedIntoWindowDecorations { get => _isExtendedIntoWindowDecorations; private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value); } /// /// Gets the WindowDecorationMargin. /// This tells you the thickness around the window that is used by borders and the titlebar. /// public Thickness WindowDecorationMargin { get => _windowDecorationMargin; private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value); } /// /// Gets the window margin that is hidden off the screen area. /// This is generally only the case on Windows when in Maximized where the window border /// is hidden off the screen. This Margin may be used to ensure user content doesnt overlap this space. /// public Thickness OffScreenMargin { get => _offScreenMargin; private set => SetAndRaise(OffScreenMarginProperty, ref _offScreenMargin, value); } /// /// Gets or sets the window decorations (title bar, border, etc). /// public WindowDecorations WindowDecorations { get => GetValue(WindowDecorationsProperty); set => SetValue(WindowDecorationsProperty, value); } [Obsolete("Use WindowDecorations instead.")] public WindowDecorations SystemDecorations { get => WindowDecorations; set => WindowDecorations = value; } /// /// Gets or sets a value that indicates whether a window is activated when first shown. /// public bool ShowActivated { get => GetValue(ShowActivatedProperty); set => SetValue(ShowActivatedProperty, value); } /// /// Enables or disables the taskbar icon /// /// public bool ShowInTaskbar { get => GetValue(ShowInTaskbarProperty); set => SetValue(ShowInTaskbarProperty, value); } /// /// Gets or sets a value indicating how the event behaves in the presence /// of child windows. /// public WindowClosingBehavior ClosingBehavior { get => GetValue(ClosingBehaviorProperty); set => SetValue(ClosingBehaviorProperty, value); } /// /// Gets or sets the minimized/maximized state of the window. /// public WindowState WindowState { get => GetValue(WindowStateProperty); set => SetValue(WindowStateProperty, value); } /// /// Enables or disables resizing of the window. /// public bool CanResize { get => GetValue(CanResizeProperty); set => SetValue(CanResizeProperty, value); } /// /// Enables or disables minimizing the window. /// /// /// This property might be ignored by some window managers on Linux. /// public bool CanMinimize { get => GetValue(CanMinimizeProperty); set => SetValue(CanMinimizeProperty, value); } /// /// Enables or disables maximizing the window. /// /// /// When is false, this property is always false. /// On macOS, setting this property to false also disables the full screen mode. /// This property might be ignored by some window managers on Linux. /// public bool CanMaximize { get => GetValue(CanMaximizeProperty); set => SetValue(CanMaximizeProperty, value); } /// /// Gets or sets the icon of the window. /// public WindowIcon? Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); } /// /// Gets or sets the startup location of the window. /// public WindowStartupLocation WindowStartupLocation { get => GetValue(WindowStartupLocationProperty); set => SetValue(WindowStartupLocationProperty, value); } /// /// Gets or sets the window position in screen coordinates. /// public PixelPoint Position { get => PlatformImpl?.Position ?? PixelPoint.Origin; set { PlatformImpl?.Move(value); _positionWasSet = true; } } /// /// Gets whether this window was opened as a dialog /// public bool IsDialog => _showingAsDialog; /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// public void BeginMoveDrag(PointerPressedEventArgs e) => PlatformImpl?.BeginMoveDrag(e); /// /// Starts resizing a window. This function is used if an application has window resizing controls. /// Should be called from left mouse button press event handler /// public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e); /// protected override Type StyleKeyOverride => typeof(Window); /// /// Fired before a window is closed. /// public event EventHandler? Closing; /// /// Closes the window. /// public void Close() { CloseCore(WindowCloseReason.WindowClosing, true, false); } /// /// Closes a dialog window with the specified result. /// /// The dialog result. /// /// When the window is shown with the /// or method, the /// resulting task will produce the value when the window /// is closed. /// public void Close(object? dialogResult) { _dialogResult = dialogResult; CloseCore(WindowCloseReason.WindowClosing, true, false); } internal void CloseCore(WindowCloseReason reason, bool isProgrammatic, bool ignoreCancel) { bool close = true; try { if (ShouldCancelClose(new WindowClosingEventArgs(reason, isProgrammatic))) { close = false; } } finally { if (close || ignoreCancel) { CloseInternal(); } } } /// /// Handles a closing notification from . /// true if closing is cancelled. Otherwise false. /// /// The reason the window is closing. private protected virtual bool HandleClosing(WindowCloseReason reason) { if (!ShouldCancelClose(new WindowClosingEventArgs(reason, false))) { CloseInternal(); return false; } return true; } private void CloseInternal() { foreach (var (child, _) in _children.ToArray()) { child.CloseInternal(); } PlatformImpl?.Dispose(); _showingAsDialog = false; Owner = null; } private bool ShouldCancelClose(WindowClosingEventArgs args) { switch (ClosingBehavior) { case WindowClosingBehavior.OwnerAndChildWindows: bool canClose = true; if (_children.Count > 0) { var childArgs = args.CloseReason == WindowCloseReason.WindowClosing ? new WindowClosingEventArgs(WindowCloseReason.OwnerWindowClosing, args.IsProgrammatic) : args; foreach (var (child, _) in _children.ToArray()) { if (child.ShouldCancelClose(childArgs)) { canClose = false; } } } if (canClose) { OnClosing(args); return args.Cancel; } return true; case WindowClosingBehavior.OwnerWindowOnly: OnClosing(args); return args.Cancel; } return false; } private void HandleWindowStateChanged(WindowState state) { WindowState = state; if (state == WindowState.Minimized) { StopRendering(); } else { StartRendering(); } // Update decoration parts and fullscreen popover state for the new window state UpdateDrawnDecorationParts(); } protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended) { IsExtendedIntoWindowDecorations = isExtended; OffScreenMargin = PlatformImpl?.OffScreenMargin ?? default; UpdateDrawnDecorations(); } private void UpdateDrawnDecorations() { var parts = ComputeDecorationParts(); TopLevelHost.UpdateDrawnDecorations(parts, WindowState); if (parts != null) { // Forward ExtendClientAreaTitleBarHeightHint to decoration TitleBarHeight var decorations = TopLevelHost.Decorations; if (decorations != null) { var hint = ExtendClientAreaTitleBarHeightHint; if (hint >= 0) decorations.TitleBarHeightOverride = hint; } } UpdateDrawnDecorationMargins(); } /// /// Updates decoration parts based on current window state without /// re-creating the decorations instance. /// private void UpdateDrawnDecorationParts() { if (TopLevelHost.Decorations == null) return; TopLevelHost.UpdateDrawnDecorations(ComputeDecorationParts(), WindowState); } private Chrome.DrawnWindowDecorationParts? ComputeDecorationParts() { if (!(PlatformImpl?.NeedsManagedDecorations ?? false)) return null; var platformNeeds = PlatformImpl?.RequestedDrawnDecorations ?? PlatformRequestedDrawnDecoration.None; var parts = Chrome.DrawnWindowDecorationParts.None; if (WindowDecorations != WindowDecorations.None) { if (platformNeeds.HasFlag(PlatformRequestedDrawnDecoration.TitleBar) && WindowDecorations == WindowDecorations.Full) parts |= Chrome.DrawnWindowDecorationParts.TitleBar; if (platformNeeds.HasFlag(PlatformRequestedDrawnDecoration.Shadow)) parts |= Chrome.DrawnWindowDecorationParts.Shadow; if (platformNeeds.HasFlag(PlatformRequestedDrawnDecoration.Border)) parts |= Chrome.DrawnWindowDecorationParts.Border; if (platformNeeds.HasFlag(PlatformRequestedDrawnDecoration.ResizeGrips) && CanResize) parts |= Chrome.DrawnWindowDecorationParts.ResizeGrips; // In fullscreen: no shadow, border, resize grips, or titlebar (popover takes over) if (WindowState == WindowState.FullScreen) { parts &= ~(Chrome.DrawnWindowDecorationParts.Shadow | Chrome.DrawnWindowDecorationParts.Border | Chrome.DrawnWindowDecorationParts.ResizeGrips | Chrome.DrawnWindowDecorationParts.TitleBar); } // In maximized: no shadow, border, or resize grips (titlebar stays) else if (WindowState == WindowState.Maximized) { parts &= ~(Chrome.DrawnWindowDecorationParts.Shadow | Chrome.DrawnWindowDecorationParts.Border | Chrome.DrawnWindowDecorationParts.ResizeGrips); } } return parts; } private void UpdateDrawnDecorationMargins() { var decorations = TopLevelHost.Decorations; if (decorations == null) { WindowDecorationMargin = PlatformImpl?.ExtendedMargins ?? default; return; } var parts = decorations.EnabledParts; var titleBarHeight = parts.HasFlag(Chrome.DrawnWindowDecorationParts.TitleBar) ? decorations.TitleBarHeight : 0; var frame = parts.HasFlag(Chrome.DrawnWindowDecorationParts.Border) ? decorations.FrameThickness : default; var shadow = parts.HasFlag(Chrome.DrawnWindowDecorationParts.Shadow) ? decorations.ShadowThickness : default; WindowDecorationMargin = new Thickness( frame.Left + shadow.Left, titleBarHeight + frame.Top + shadow.Top, frame.Right + shadow.Right, frame.Bottom + shadow.Bottom); } private void OnTitleBarHeightHintChanged() { var decorations = TopLevelHost.Decorations; if (decorations == null) return; decorations.TitleBarHeightOverride = ExtendClientAreaTitleBarHeightHint; UpdateDrawnDecorationMargins(); } /// /// Called by TopLevelHost when decoration effective geometry changes /// (e.g. theme changes Default* values, or EnabledParts changes). /// internal void OnDrawnDecorationsGeometryChanged() { UpdateDrawnDecorationMargins(); } /// /// Hides the window but does not close it. /// public override void Hide() { using (FreezeVisibilityChangeHandling()) { if (!_shown) { return; } StopRendering(); if (_children.Count > 0) { foreach (var child in _children.ToArray()) { child.child.Hide(); } } Owner = null; PlatformImpl?.Hide(); IsVisible = false; _modalSubscription?.Dispose(); _shown = false; } } /// /// Shows the window. /// /// /// The window has already been closed. /// public override void Show() { ShowCore(null, false); } protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { if (!IgnoreVisibilityChanges) { var isVisible = e.GetNewValue(); if (_shown != isVisible) { if (!_shown) { Show(); } else { Hide(); } } } } /// /// Shows the window as a child of . /// /// Window that will be the owner of the shown window. /// /// The window has already been closed. /// public void Show(Window owner) { if (owner is null) { throw new ArgumentNullException(nameof(owner), "Showing a child window requires valid parent."); } ShowCore(owner, false); } private void EnsureStateBeforeShow() { if (PlatformImpl == null) { throw new InvalidOperationException("Cannot re-show a closed window."); } } private void EnsureParentStateBeforeShow(Window owner) { if (owner.PlatformImpl == null) { throw new InvalidOperationException("Cannot show a window with a closed owner."); } if (owner == this) { throw new InvalidOperationException("A Window cannot be its own owner."); } if (!owner.IsVisible) { throw new InvalidOperationException("Cannot show window with non-visible owner."); } } private Task? ShowCore(Window? owner, bool modal) { using (FreezeVisibilityChangeHandling()) { EnsureStateBeforeShow(); if (modal && owner == null) { throw new ArgumentNullException(nameof(owner)); } if (owner != null) { EnsureParentStateBeforeShow(owner); } if (_shown) { if (modal) throw new InvalidOperationException("The window is already being shown."); return null; } _showingAsDialog = modal; RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); ApplyStyling(); // Enable drawn decorations before layout so margins are computed UpdateDrawnDecorations(); _shown = true; IsVisible = true; // If window position was not set before then platform may provide incorrect scaling at this time, // but we need it for proper calculation of position and in some cases size (size to content) SetExpectedScaling(owner); var initialSize = new Size( double.IsNaN(Width) ? ClientSize.Width : Width, double.IsNaN(Height) ? ClientSize.Height : Height); var minMax = new MinMax(this); initialSize = new Size( MathUtilities.Clamp(initialSize.Width, minMax.MinWidth, minMax.MaxWidth), MathUtilities.Clamp(initialSize.Height, minMax.MinHeight, minMax.MaxHeight)); var clientSizeChanged = initialSize != ClientSize; ClientSize = initialSize; // ClientSize is required for Measure and Arrange // this will call ArrangeSetBounds LayoutManager.ExecuteInitialLayoutPass(); if (SizeToContent.HasFlag(SizeToContent.Width)) { initialSize = initialSize.WithWidth(MathUtilities.Clamp(_arrangeBounds.Width, minMax.MinWidth, minMax.MaxWidth)); clientSizeChanged |= initialSize != ClientSize; ClientSize = initialSize; } if (SizeToContent.HasFlag(SizeToContent.Height)) { initialSize = initialSize.WithHeight(MathUtilities.Clamp(_arrangeBounds.Height, minMax.MinHeight, minMax.MaxHeight)); clientSizeChanged |= initialSize != ClientSize; ClientSize = initialSize; } Owner = owner; SetWindowStartupLocation(owner); DesktopScalingOverride = null; if (clientSizeChanged || ClientSize != PlatformImpl?.ClientSize) { // Previously it was called before ExecuteInitialLayoutPass PlatformImpl?.Resize(ClientSize, WindowResizeReason.Layout); // we do not want PlatformImpl?.Resize to trigger HandleResized yet because it will set Width and Height. // So perform some important actions from HandleResized Renderer.Resized(ClientSize); OnResized(new WindowResizedEventArgs(ClientSize, WindowResizeReason.Layout)); if (!double.IsNaN(Width)) Width = ClientSize.Width; if (!double.IsNaN(Height)) Height = ClientSize.Height; } FrameSize = PlatformImpl?.FrameSize; _canHandleResized = true; StartRendering(); PlatformImpl?.Show(ShowActivated, modal); Task? result = null; if (modal) { var tcs = new TaskCompletionSource(); var disposables = new CompositeDisposable( [ Observable.FromEventPattern( x => Closed += x, x => Closed -= x) .Take(1) .Subscribe(_ => { _modalSubscription?.Dispose(); }), Disposable.Create(() => { _modalSubscription = null; owner!.Activate(); tcs.SetResult((TResult)(_dialogResult ?? default(TResult)!)); }) ]); _modalSubscription = disposables; result = tcs.Task; } OnOpened(EventArgs.Empty); if (!modal) _wasShownBefore = true; return result; } } /// /// Shows the window as a dialog. /// /// The dialog's owner window. /// /// The window has already been closed. /// /// /// A task that can be used to track the lifetime of the dialog. /// public Task ShowDialog(Window owner) { return ShowDialog(owner); } /// /// Shows the window as a dialog. /// /// /// The type of the result produced by the dialog. /// /// The dialog's owner window. /// . /// A task that can be used to retrieve the result of the dialog when it closes. /// public Task ShowDialog(Window owner) => ShowCore(owner, true)!; /// /// Sorts the windows ascending by their Z order - the topmost window will be the last in the list. /// /// The windows to sort. public static void SortWindowsByZOrder(Span windows) { if (windows.Length <= 1) return; var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpls = new IWindowImpl[windows.Length]; for (var i = 0; i < windows.Length; ++i) { windowImpls[i] = windows[i].PlatformImpl ?? throw new ArgumentException($"Invalid window at index {i}", nameof(windows)); } const int stackAllocThreshold = 128; var zOrder = windows.Length > stackAllocThreshold ? new long[windows.Length] : stackalloc long[windows.Length]; platform.GetWindowsZOrder(windowImpls, zOrder); zOrder.Sort(windows); } private void UpdateEnabled() { bool isEnabled = true; foreach (var (_, isDialog) in _children) { if (isDialog) { isEnabled = false; break; } } PlatformImpl?.SetEnabled(isEnabled); } private void AddChild(Window window, bool isDialog) { _children.Add((window, isDialog)); UpdateEnabled(); } private void RemoveChild(Window window) { for (int i = _children.Count - 1; i >= 0; i--) { var (child, _) = _children[i]; if (ReferenceEquals(child, window)) { _children.RemoveAt(i); } } UpdateEnabled(); } private void OnGotInputWhenDisabled() { Window? firstDialogChild = null; foreach (var (child, isDialog) in _children) { if (isDialog) { firstDialogChild = child; break; } } if (firstDialogChild != null) { firstDialogChild.OnGotInputWhenDisabled(); } else { Activate(); } } private void SetExpectedScaling(WindowBase? owner) { if (_wasShownBefore) { return; } var location = GetEffectiveWindowStartupLocation(owner); switch (location) { case WindowStartupLocation.CenterOwner: DesktopScalingOverride = owner?.DesktopScaling; break; case WindowStartupLocation.CenterScreen: DesktopScalingOverride = owner?.DesktopScaling ?? Screens.ScreenFromPoint(Position)?.Scaling ?? Screens.Primary?.Scaling; break; case WindowStartupLocation.Manual: DesktopScalingOverride = Screens.ScreenFromPoint(Position)?.Scaling; break; } } private WindowStartupLocation GetEffectiveWindowStartupLocation(WindowBase? owner) { var startupLocation = WindowStartupLocation; if (startupLocation == WindowStartupLocation.CenterOwner && (owner is null || (owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized)) ) { // If startup location is CenterOwner, but owner is null or minimized then fall back // to CenterScreen. This behavior is consistent with WPF. startupLocation = WindowStartupLocation.CenterScreen; } return startupLocation; } private void SetWindowStartupLocation(Window? owner = null) { if (_wasShownBefore) { return; } var startupLocation = GetEffectiveWindowStartupLocation(owner); PixelRect rect; // Use frame size, falling back to client size if the platform can't give it to us. if (PlatformImpl?.FrameSize.HasValue == true) { // Platform may calculate FrameSize with incorrect scaling, so do not trust the value. var diff = PlatformImpl.FrameSize.Value - PlatformImpl.ClientSize; rect = new PixelRect(PixelSize.FromSize(ClientSize + diff, DesktopScaling)); } else { rect = new PixelRect(PixelSize.FromSize(ClientSize, DesktopScaling)); } if (startupLocation == WindowStartupLocation.CenterScreen) { Screen? screen = null; if (owner is not null) { screen = Screens.ScreenFromWindow(owner) ?? Screens.ScreenFromPoint(owner.Position); } screen ??= Screens.ScreenFromPoint(Position); screen ??= Screens.Primary; if (screen is not null) { var childRect = screen.WorkingArea.CenterRect(rect); if (Screens.ScreenFromPoint(childRect.Position) == null) childRect = ApplyScreenConstraint(screen, childRect); Position = childRect.Position; } } else if (startupLocation == WindowStartupLocation.CenterOwner) { var ownerSize = owner!.FrameSize ?? owner.ClientSize; var ownerRect = new PixelRect( owner.Position, PixelSize.FromSize(ownerSize, owner.DesktopScaling)); var childRect = ownerRect.CenterRect(rect); var screen = Screens.ScreenFromWindow(owner); childRect = ApplyScreenConstraint(screen, childRect); Position = childRect.Position; } if (!_positionWasSet && DesktopScaling != PlatformImpl?.DesktopScaling) // Platform returns incorrect scaling, forcing setting position may fix it PlatformImpl?.Move(Position); PixelRect ApplyScreenConstraint(Screen? screen, PixelRect childRect) { if (screen?.WorkingArea is { } constraint) { var maxX = constraint.Right - rect.Width; var maxY = constraint.Bottom - rect.Height; if (constraint.X <= maxX) childRect = childRect.WithX(MathUtilities.Clamp(childRect.X, constraint.X, maxX)); if (constraint.Y <= maxY) childRect = childRect.WithY(MathUtilities.Clamp(childRect.Y, constraint.Y, maxY)); } return childRect; } } protected override Size MeasureOverride(Size availableSize) { var sizeToContent = SizeToContent; var clientSize = ClientSize; var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity; var useAutoWidth = sizeToContent.HasAllFlags(SizeToContent.Width); var useAutoHeight = sizeToContent.HasAllFlags(SizeToContent.Height); var constraint = new Size( useAutoWidth || double.IsInfinity(availableSize.Width) ? clientSize.Width : availableSize.Width, useAutoHeight || double.IsInfinity(availableSize.Height) ? clientSize.Height : availableSize.Height); if (MaxWidth > 0 && MaxWidth < maxAutoSize.Width) { maxAutoSize = maxAutoSize.WithWidth(MaxWidth); } if (MaxHeight > 0 && MaxHeight < maxAutoSize.Height) { maxAutoSize = maxAutoSize.WithHeight(MaxHeight); } if (useAutoWidth) { constraint = constraint.WithWidth(maxAutoSize.Width); } if (useAutoHeight) { constraint = constraint.WithHeight(maxAutoSize.Height); } var result = base.MeasureOverride(constraint); if (!useAutoWidth) { if (!double.IsInfinity(availableSize.Width)) { result = result.WithWidth(availableSize.Width); } else { result = result.WithWidth(clientSize.Width); } } if (!useAutoHeight) { if (!double.IsInfinity(availableSize.Height)) { result = result.WithHeight(availableSize.Height); } else { result = result.WithHeight(clientSize.Height); } } return result; } private protected sealed override Size ArrangeSetBounds(Size size) { _arrangeBounds = size; if (_canHandleResized) PlatformImpl?.Resize(size, WindowResizeReason.Layout); return ClientSize; } private protected sealed override void HandleClosed() { _shown = false; base.HandleClosed(); RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); Owner = null; } /// internal override void HandleResized(Size clientSize, WindowResizeReason reason) { if (_canHandleResized && (ClientSize != clientSize || double.IsNaN(Width) || double.IsNaN(Height))) { var sizeToContent = SizeToContent; // If auto-sizing is enabled, and the resize came from a user resize (or the reason was // unspecified) then turn off auto-resizing for any window dimension that is not equal // to the requested size. if (sizeToContent != SizeToContent.Manual && CanResize && reason == WindowResizeReason.Unspecified || reason == WindowResizeReason.User) { if (clientSize.Width != ClientSize.Width) sizeToContent &= ~SizeToContent.Width; if (clientSize.Height != ClientSize.Height) sizeToContent &= ~SizeToContent.Height; SizeToContent = sizeToContent; } Width = clientSize.Width; Height = clientSize.Height; } base.HandleResized(clientSize, reason); } /// /// Raises the event. /// /// The event args. /// /// A type that derives from may override . The /// overridden method must call on the base class if the /// event needs to be raised. /// protected virtual void OnClosing(WindowClosingEventArgs e) => Closing?.Invoke(this, e); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == WindowDecorationsProperty) { var (_, typedNewValue) = change.GetOldAndNewValue(); PlatformImpl?.SetWindowDecorations(typedNewValue); } else if (change.Property == OwnerProperty) { var oldParent = change.OldValue as Window; var newParent = change.NewValue as Window; oldParent?.RemoveChild(this); newParent?.AddChild(this, _showingAsDialog); if (PlatformImpl is IWindowImpl impl) { impl.SetParent(_showingAsDialog ? newParent?.PlatformImpl! : (newParent?.PlatformImpl ?? null)); } } else if (change.Property == CanResizeProperty) { CoerceValue(CanMaximizeProperty); } } protected override AutomationPeer OnCreateAutomationPeer() { return new WindowAutomationPeer(this); } private static WindowIcon? LoadDefaultIcon() { // Use AvaloniaLocator instead of static AssetLoader, so it won't fail on Unit Tests without any asset loader. if (AvaloniaLocator.Current.GetService() is { } assetLoader && Assembly.GetEntryAssembly()?.GetName()?.Name is { } assemblyName && Uri.TryCreate($"avares://{assemblyName}/!__AvaloniaDefaultWindowIcon", UriKind.Absolute, out var path) && assetLoader.Exists(path)) { using var stream = assetLoader.Open(path); return new WindowIcon(stream); } return null; } private static bool CoerceCanMaximize(AvaloniaObject target, bool value) => value && target is not Window { CanResize: false }; } }