using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; using JetBrains.Annotations; 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 system decorations (title bar, border, etc) for a /// public enum SystemDecorations { /// /// No decorations /// None = 0, /// /// Window border without titlebar /// BorderOnly = 1, /// /// Fully decorated (default) /// Full = 2 } /// /// A top-level window. /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot { private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); private bool _isExtendedIntoWindowDecorations; private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; /// /// Defines the property. /// public static readonly StyledProperty SizeToContentProperty = AvaloniaProperty.Register(nameof(SizeToContent)); /// /// Enables or disables system window decorations (title bar, buttons, etc) /// [Obsolete("Use SystemDecorationsProperty instead")] public static readonly DirectProperty HasSystemDecorationsProperty = AvaloniaProperty.RegisterDirect( nameof(HasSystemDecorations), o => o.HasSystemDecorations, (o, v) => o.HasSystemDecorations = v); /// /// Defines the property. /// public static readonly StyledProperty ExtendClientAreaToDecorationsHintProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaToDecorationsHint), false); public static readonly StyledProperty ExtendClientAreaChromeHintsProperty = AvaloniaProperty.Register(nameof(ExtendClientAreaChromeHints), ExtendClientAreaChromeHints.Default); 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 SystemDecorationsProperty = AvaloniaProperty.Register(nameof(SystemDecorations), SystemDecorations.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); /// /// 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 DirectProperty WindowStartupLocationProperty = AvaloniaProperty.RegisterDirect( nameof(WindowStartupLocation), o => o.WindowStartupLocation, (o, v) => o.WindowStartupLocation = v); public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); /// /// 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 readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; private WindowStartupLocation _windowStartupLocation; /// /// Initializes static members of the class. /// static Window() { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue)); ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl)); CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); WindowStateProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; }); ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue); }); ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) { w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue); } }); ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler( (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue); }); MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue))); } /// /// 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, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); } /// /// Gets the platform-specific window implementation. /// [CanBeNull] public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; /// /// 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 { return GetValue(SizeToContentProperty); } set { SetValue(SizeToContentProperty, value); } } /// /// Gets or sets the title of the window. /// public string Title { get { return GetValue(TitleProperty); } set { SetValue(TitleProperty, value); } } /// /// Enables or disables system window decorations (title bar, buttons, etc) /// [Obsolete("Use SystemDecorations instead")] public bool HasSystemDecorations { get => SystemDecorations == SystemDecorations.Full; set { var oldValue = HasSystemDecorations; if (oldValue != value) { SystemDecorations = value ? SystemDecorations.Full : SystemDecorations.None; RaisePropertyChanged(HasSystemDecorationsProperty, oldValue, value); } } } /// /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). /// public bool ExtendClientAreaToDecorationsHint { get { return GetValue(ExtendClientAreaToDecorationsHintProperty); } set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); } } /// /// Gets or Sets the that control /// how the chrome looks when the client area is extended. /// public ExtendClientAreaChromeHints ExtendClientAreaChromeHints { get => GetValue(ExtendClientAreaChromeHintsProperty); set => SetValue(ExtendClientAreaChromeHintsProperty, 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); } /// /// Sets the system decorations (title bar, border, etc) /// public SystemDecorations SystemDecorations { get { return GetValue(SystemDecorationsProperty); } set { SetValue(SystemDecorationsProperty, value); } } /// /// Gets or sets a value that indicates whether a window is activated when first shown. /// public bool ShowActivated { get { return GetValue(ShowActivatedProperty); } set { SetValue(ShowActivatedProperty, value); } } /// /// Enables or disables the taskbar icon /// /// public bool ShowInTaskbar { get { return GetValue(ShowInTaskbarProperty); } set { SetValue(ShowInTaskbarProperty, value); } } /// /// Gets or sets the minimized/maximized state of the window. /// public WindowState WindowState { get { return GetValue(WindowStateProperty); } set { SetValue(WindowStateProperty, value); } } /// /// Enables or disables resizing of the window. /// Note that if is set to False then this property /// has no effect and should be treated as a recommendation for the user setting HasSystemDecorations. /// public bool CanResize { get { return GetValue(CanResizeProperty); } set { SetValue(CanResizeProperty, value); } } /// /// Gets or sets the icon of the window. /// public WindowIcon Icon { get { return GetValue(IconProperty); } set { SetValue(IconProperty, value); } } /// /// Gets or sets the startup location of the window. /// public WindowStartupLocation WindowStartupLocation { get { return _windowStartupLocation; } set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); } } /// /// Gets or sets the window position in screen coordinates. /// public PixelPoint Position { get { return PlatformImpl?.Position ?? PixelPoint.Origin; } set { PlatformImpl?.Move(value); } } /// /// 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); /// Type IStyleable.StyleKey => typeof(Window); /// /// Fired before a window is closed. /// public event EventHandler Closing; /// /// Closes the window. /// public void Close() { Close(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; Close(false); } internal void Close(bool ignoreCancel) { bool close = true; try { if (!ignoreCancel && ShouldCancelClose()) { close = false; } } finally { if (close) { CloseInternal(); } } } /// /// Handles a closing notification from . /// true if closing is cancelled. Otherwise false. /// protected virtual bool HandleClosing() { if (!ShouldCancelClose()) { CloseInternal(); return false; } return true; } private void CloseInternal() { foreach (var (child, _) in _children.ToList()) { child.CloseInternal(); } if (Owner is Window owner) { owner.RemoveChild(this); } Owner = null; PlatformImpl?.Dispose(); } private bool ShouldCancelClose(CancelEventArgs args = null) { if (args is null) { args = new CancelEventArgs(); } bool canClose = true; foreach (var (child, _) in _children.ToList()) { if (child.ShouldCancelClose(args)) { canClose = false; } } if (canClose) { OnClosing(args); return args.Cancel; } return true; } protected virtual void HandleWindowStateChanged(WindowState state) { WindowState = state; if (state == WindowState.Minimized) { Renderer.Stop(); } else { Renderer.Start(); } } protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended) { IsExtendedIntoWindowDecorations = isExtended; WindowDecorationMargin = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; } /// /// Hides the window but does not close it. /// public override void Hide() { if (!IsVisible) { return; } Renderer?.Stop(); if (Owner is Window owner) { owner.RemoveChild(this); } if (_children.Count > 0) { foreach (var child in _children.ToArray()) { child.child.Hide(); } } Owner = null; PlatformImpl?.Hide(); IsVisible = false; } /// /// Shows the window. /// /// /// The window has already been closed. /// public override void Show() { ShowCore(null); } /// /// Shows the window as a child of . /// /// Window that will be a parent of the shown window. /// /// The window has already been closed. /// public void Show(Window parent) { if (parent is null) { throw new ArgumentNullException(nameof(parent), "Showing a child window requires valid parent."); } ShowCore(parent); } private void ShowCore(Window parent) { if (PlatformImpl == null) { throw new InvalidOperationException("Cannot re-show a closed window."); } if (parent != null) { if (parent.PlatformImpl == null) { throw new InvalidOperationException("Cannot show a window with a closed parent."); } else if (parent == this) { throw new InvalidOperationException("A Window cannot be its own parent."); } else if (!parent.IsVisible) { throw new InvalidOperationException("Cannot show window with non-visible parent."); } } if (IsVisible) { return; } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; var initialSize = new Size( double.IsNaN(Width) ? ClientSize.Width : Width, double.IsNaN(Height) ? ClientSize.Height : Height); if (initialSize != ClientSize) { PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); if (parent != null) { PlatformImpl?.SetParent(parent.PlatformImpl); } Owner = parent; parent?.AddChild(this, false); SetWindowStartupLocation(Owner?.PlatformImpl); PlatformImpl?.Show(ShowActivated, false); Renderer?.Start(); OnOpened(EventArgs.Empty); } /// /// 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) { if (owner == null) { throw new ArgumentNullException(nameof(owner)); } else if (owner.PlatformImpl == null) { throw new InvalidOperationException("Cannot show a window with a closed owner."); } else if (owner == this) { throw new InvalidOperationException("A Window cannot be its own owner."); } else if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); } else if (!owner.IsVisible) { throw new InvalidOperationException("Cannot show window with non-visible parent."); } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; var initialSize = new Size( double.IsNaN(Width) ? ClientSize.Width : Width, double.IsNaN(Height) ? ClientSize.Height : Height); if (initialSize != ClientSize) { PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); var result = new TaskCompletionSource(); PlatformImpl?.SetParent(owner.PlatformImpl); Owner = owner; owner.AddChild(this, true); SetWindowStartupLocation(owner.PlatformImpl); PlatformImpl?.Show(ShowActivated, true); Renderer?.Start(); Observable.FromEventPattern( x => Closed += x, x => Closed -= x) .Take(1) .Subscribe(_ => { owner.Activate(); result.SetResult((TResult)(_dialogResult ?? default(TResult))); }); OnOpened(EventArgs.Empty); return result.Task; } 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 SetWindowStartupLocation(IWindowBaseImpl owner = null) { var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; // TODO: We really need non-client size here. var rect = new PixelRect( PixelPoint.Origin, PixelSize.FromSize(ClientSize, scaling)); if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { var screen = Screens.ScreenFromPoint(owner?.Position ?? Position); if (screen != null) { Position = screen.WorkingArea.CenterRect(rect).Position; } } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { if (owner != null) { // TODO: We really need non-client size here. var ownerRect = new PixelRect( owner.Position, PixelSize.FromSize(owner.ClientSize, scaling)); Position = ownerRect.CenterRect(rect).Position; } } } protected override Size MeasureOverride(Size availableSize) { var sizeToContent = SizeToContent; var clientSize = ClientSize; var constraint = clientSize; var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity; if (sizeToContent.HasAllFlags(SizeToContent.Width)) { constraint = constraint.WithWidth(maxAutoSize.Width); } if (sizeToContent.HasAllFlags(SizeToContent.Height)) { constraint = constraint.WithHeight(maxAutoSize.Height); } var result = base.MeasureOverride(constraint); if (!sizeToContent.HasAllFlags(SizeToContent.Width)) { if (!double.IsInfinity(availableSize.Width)) { result = result.WithWidth(availableSize.Width); } else { result = result.WithWidth(clientSize.Width); } } if (!sizeToContent.HasAllFlags(SizeToContent.Height)) { if (!double.IsInfinity(availableSize.Height)) { result = result.WithHeight(availableSize.Height); } else { result = result.WithHeight(clientSize.Height); } } return result; } protected sealed override Size ArrangeSetBounds(Size size) { PlatformImpl?.Resize(size, PlatformResizeReason.Layout); return ClientSize; } protected sealed override void HandleClosed() { RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); base.HandleClosed(); if (Owner is Window owner) { owner.RemoveChild(this); } Owner = null; } [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { if (ClientSize == clientSize) return; 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 == PlatformResizeReason.Unspecified || reason == PlatformResizeReason.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(CancelEventArgs e) => Closing?.Invoke(this, e); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { if (change.Property == SystemDecorationsProperty) { var typedNewValue = change.NewValue.GetValueOrDefault(); PlatformImpl?.SetSystemDecorations(typedNewValue); var o = change.OldValue.GetValueOrDefault() == SystemDecorations.Full; var n = typedNewValue == SystemDecorations.Full; if (o != n) { #pragma warning disable CS0618 // Type or member is obsolete RaisePropertyChanged(HasSystemDecorationsProperty, o, n); #pragma warning restore CS0618 // Type or member is obsolete } } } } }