diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 37699082ed..7b911ef945 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -45,6 +45,10 @@ BEGIN_INTERFACE_MAP() void DoZoom(); virtual HRESULT SetCanResize(bool value) override; + + virtual HRESULT SetCanMinimize(bool value) override; + + virtual HRESULT SetCanMaximize(bool value) override; virtual HRESULT SetDecorations(SystemDecorations value) override; @@ -82,7 +86,7 @@ BEGIN_INTERFACE_MAP() bool CanBecomeKeyWindow (); - bool CanZoom() override { return _isEnabled && _canResize; } + bool CanZoom() override { return _isEnabled && _canMaximize; } protected: virtual NSWindowStyleMask CalculateStyleMask() override; @@ -94,6 +98,8 @@ private: NSString *_lastTitle; bool _isEnabled; bool _canResize; + bool _canMinimize; + bool _canMaximize; bool _fullScreenActive; SystemDecorations _decorations; AvnWindowState _lastWindowState; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 341085ec08..5a57715b55 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -16,6 +16,8 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events) : TopLevelImpl(events), WindowB _extendClientHints = AvnDefaultChrome; _fullScreenActive = false; _canResize = true; + _canMinimize = true; + _canMaximize = true; _decorations = SystemDecorationsFull; _transitioningWindowState = false; _inSetWindowState = false; @@ -191,7 +193,8 @@ bool WindowImpl::IsZoomed() { void WindowImpl::DoZoom() { if (_decorations == SystemDecorationsNone || _decorations == SystemDecorationsBorderOnly || - _canResize == false) { + _canResize == false || + _canMaximize == false) { [Window setFrame:[Window screen].visibleFrame display:true]; } else { [Window performZoom:Window]; @@ -208,6 +211,22 @@ HRESULT WindowImpl::SetCanResize(bool value) { } } +HRESULT WindowImpl::SetCanMinimize(bool value) { + START_COM_ARP_CALL; + + _canMinimize = value; + UpdateAppearance(); + return S_OK; +} + +HRESULT WindowImpl::SetCanMaximize(bool value) { + START_COM_ARP_CALL; + + _canMaximize = value; + UpdateAppearance(); + return S_OK; +} + HRESULT WindowImpl::SetDecorations(SystemDecorations value) { START_COM_CALL; @@ -583,7 +602,7 @@ NSWindowStyleMask WindowImpl::CalculateStyleMask() { break; } - if (!IsOwned()) { + if (_canMinimize && !IsOwned()) { s |= NSWindowStyleMaskMiniaturizable; } @@ -611,9 +630,9 @@ void WindowImpl::UpdateAppearance() { [closeButton setHidden:!hasTrafficLights]; [closeButton setEnabled:_isEnabled]; [miniaturizeButton setHidden:!hasTrafficLights]; - [miniaturizeButton setEnabled:_isEnabled]; + [miniaturizeButton setEnabled:_isEnabled && _canMinimize]; [zoomButton setHidden:!hasTrafficLights]; - [zoomButton setEnabled:CanZoom()]; + [zoomButton setEnabled:CanZoom() || (([Window styleMask] & NSWindowStyleMaskFullScreen) != 0 && _isEnabled)]; } extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index adcfc285cd..53f41672a9 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -10,6 +10,9 @@ ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" ExtendClientAreaChromeHints="{Binding ChromeHints}" ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" + CanResize="{Binding CanResize}" + CanMinimize="{Binding CanMinimize}" + CanMaximize="{Binding CanMaximize}" x:Name="MainWindow" Background="Transparent" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index bcc1a71243..a647d34356 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -8,18 +8,55 @@ x:DataType="viewModels:MainWindowViewModel" x:CompileBindings="True"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 1050beedf2..716675f2c6 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; using System; @@ -22,6 +21,9 @@ namespace ControlCatalog.ViewModels private bool _isSystemBarVisible; private bool _displayEdgeToEdge; private Thickness _safeAreaPadding; + private bool _canResize; + private bool _canMinimize; + private bool _canMaximize; public MainWindowViewModel() { @@ -49,7 +51,7 @@ namespace ControlCatalog.ViewModels WindowState.FullScreen, }; - this.PropertyChanged += (s, e) => + PropertyChanged += (s, e) => { if (e.PropertyName is nameof(SystemTitleBarEnabled) or nameof(PreferSystemChromeEnabled)) { @@ -67,70 +69,104 @@ namespace ControlCatalog.ViewModels } }; - SystemTitleBarEnabled = true; + SystemTitleBarEnabled = true; TitleBarHeight = -1; + CanResize = true; + CanMinimize = true; + CanMaximize = true; } public ExtendClientAreaChromeHints ChromeHints { get { return _chromeHints; } - set { this.RaiseAndSetIfChanged(ref _chromeHints, value); } + set { RaiseAndSetIfChanged(ref _chromeHints, value); } } public bool ExtendClientAreaEnabled { get { return _extendClientAreaEnabled; } - set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); } + set + { + if (RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value) && !value) + { + SystemTitleBarEnabled = true; + } + } } public bool SystemTitleBarEnabled { get { return _systemTitleBarEnabled; } - set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); } + set + { + if (RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value) && !value) + { + TitleBarHeight = -1; + } + } } public bool PreferSystemChromeEnabled { get { return _preferSystemChromeEnabled; } - set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); } + set { RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); } } public double TitleBarHeight { get { return _titleBarHeight; } - set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); } + set { RaiseAndSetIfChanged(ref _titleBarHeight, value); } } public WindowState WindowState { get { return _windowState; } - set { this.RaiseAndSetIfChanged(ref _windowState, value); } + set { RaiseAndSetIfChanged(ref _windowState, value); } } public WindowState[] WindowStates { get { return _windowStates; } - set { this.RaiseAndSetIfChanged(ref _windowStates, value); } + set { RaiseAndSetIfChanged(ref _windowStates, value); } } public bool IsSystemBarVisible { get { return _isSystemBarVisible; } - set { this.RaiseAndSetIfChanged(ref _isSystemBarVisible, value); } + set { RaiseAndSetIfChanged(ref _isSystemBarVisible, value); } } public bool DisplayEdgeToEdge { get { return _displayEdgeToEdge; } - set { this.RaiseAndSetIfChanged(ref _displayEdgeToEdge, value); } + set { RaiseAndSetIfChanged(ref _displayEdgeToEdge, value); } } public Thickness SafeAreaPadding { get { return _safeAreaPadding; } - set { this.RaiseAndSetIfChanged(ref _safeAreaPadding, value); } + set { RaiseAndSetIfChanged(ref _safeAreaPadding, value); } + } + + public bool CanResize + { + get { return _canResize; } + set { RaiseAndSetIfChanged(ref _canResize, value); } + } + + public bool CanMinimize + { + get { return _canMinimize; } + set { RaiseAndSetIfChanged(ref _canMinimize, value); } } + public bool CanMaximize + { + get { return _canMaximize; } + set { RaiseAndSetIfChanged(ref _canMaximize, value); } + } + + public MiniCommand AboutCommand { get; } public MiniCommand ExitCommand { get; } @@ -144,7 +180,7 @@ namespace ControlCatalog.ViewModels public DateTime? ValidatedDateExample { get => _validatedDateExample; - set => this.RaiseAndSetIfChanged(ref _validatedDateExample, value); + set => RaiseAndSetIfChanged(ref _validatedDateExample, value); } } } diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml b/samples/IntegrationTestApp/Pages/WindowPage.axaml index db2298b06e..60900a4778 100644 --- a/samples/IntegrationTestApp/Pages/WindowPage.axaml +++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml @@ -30,14 +30,16 @@ ExtendClientAreaToDecorationsHint Can Resize + Can Minimize + Can Maximize + + - - diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs index 5549e537d3..b7f505a7b2 100644 --- a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs +++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs @@ -23,10 +23,13 @@ public partial class WindowPage : UserControl private void ShowWindow_Click(object? sender, RoutedEventArgs e) { var size = !string.IsNullOrWhiteSpace(ShowWindowSize.Text) ? Size.Parse(ShowWindowSize.Text) : (Size?)null; + var canResize = ShowWindowCanResize.IsChecked ?? false; var window = new ShowWindowTest { WindowStartupLocation = (WindowStartupLocation)ShowWindowLocation.SelectedIndex, - CanResize = ShowWindowCanResize.IsChecked ?? false, + CanResize = canResize, + CanMinimize = ShowWindowCanMinimize.IsChecked ?? false, + CanMaximize = canResize && (ShowWindowCanMaximize.IsChecked ?? false) }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 07ecbb188e..32bdd8fa96 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -21,6 +21,8 @@ namespace Avalonia.Controls.Chrome internal const string PART_FullScreenButton = "PART_FullScreenButton"; private Button? _restoreButton; + private Button? _minimizeButton; + private Button? _fullScreenButton; private IDisposable? _disposables; /// @@ -36,11 +38,16 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable { - HostWindow.GetObservable(Window.CanResizeProperty) - .Subscribe(x => + HostWindow.GetObservable(Window.CanMaximizeProperty) + .Subscribe(_ => + { + UpdateRestoreButtonState(); + UpdateFullScreenButtonState(); + }), + HostWindow.GetObservable(Window.CanMinimizeProperty) + .Subscribe(_ => { - if (_restoreButton is not null) - _restoreButton.IsEnabled = x; + UpdateMinimizeButtonState(); }), HostWindow.GetObservable(Window.WindowStateProperty) .Subscribe(x => @@ -49,6 +56,9 @@ namespace Avalonia.Controls.Chrome PseudoClasses.Set(":normal", x == WindowState.Normal); PseudoClasses.Set(":maximized", x == WindowState.Maximized); PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + UpdateRestoreButtonState(); + UpdateMinimizeButtonState(); + UpdateFullScreenButtonState(); }), }; } @@ -116,8 +126,8 @@ namespace Avalonia.Controls.Chrome OnRestore(); args.Handled = true; }; - restoreButton.IsEnabled = HostWindow?.CanResize ?? true; _restoreButton = restoreButton; + UpdateRestoreButtonState(); } if (e.NameScope.Find void CanResize(bool value); + /// + /// Enables or disables minimizing the window. + /// + void SetCanMinimize(bool value); + + /// + /// Enables or disables maximizing the window. + /// + void SetCanMaximize(bool value); + /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. @@ -124,7 +134,6 @@ namespace Avalonia.Platform /// /// Minimum width of the window. /// - /// void SetMinMaxSize(Size minSize, Size maxSize); /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 6fb203b035..1f5d12c536 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -181,9 +181,24 @@ namespace Avalonia.Controls 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 /// @@ -236,6 +251,8 @@ namespace Avalonia.Controls 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); @@ -407,6 +424,32 @@ namespace Avalonia.Controls 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. /// @@ -1184,7 +1227,7 @@ namespace Avalonia.Controls PlatformImpl?.SetSystemDecorations(typedNewValue); } - if (change.Property == OwnerProperty) + else if (change.Property == OwnerProperty) { var oldParent = change.OldValue as Window; var newParent = change.NewValue as Window; @@ -1197,6 +1240,11 @@ namespace Avalonia.Controls impl.SetParent(_showingAsDialog ? newParent?.PlatformImpl! : (newParent?.PlatformImpl ?? null)); } } + + else if (change.Property == CanResizeProperty) + { + CoerceValue(CanMaximizeProperty); + } } protected override AutomationPeer OnCreateAutomationPeer() @@ -1217,5 +1265,8 @@ namespace Avalonia.Controls } return null; } + + private static bool CoerceCanMaximize(AvaloniaObject target, bool value) + => value && target is not Window { CanResize: false }; } } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 84c1d7db19..61924f53ab 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -133,6 +133,14 @@ namespace Avalonia.DesignerSupport.Remote { } + public void SetCanMinimize(bool value) + { + } + + public void SetCanMaximize(bool value) + { + } + public void SetTopmost(bool value) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a0e5f3c87e..e83c29c9af 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -152,6 +152,14 @@ namespace Avalonia.DesignerSupport.Remote { } + public void SetCanMinimize(bool value) + { + } + + public void SetCanMaximize(bool value) + { + } + public void SetTopmost(bool value) { } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index a278bda67e..08aaae669f 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -18,6 +18,7 @@ namespace Avalonia.Native private DoubleClickHelper _doubleClickHelper; private readonly ITopLevelNativeMenuExporter _nativeMenuExporter; private bool _canResize = true; + private bool _canMaximize = true; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(factory) { @@ -77,6 +78,17 @@ namespace Avalonia.Native _native.SetCanResize(value.AsComBool()); } + public void SetCanMinimize(bool value) + { + _native.SetCanMinimize(value.AsComBool()); + } + + public void SetCanMaximize(bool value) + { + _canMaximize = value; + _native.SetCanMaximize(value.AsComBool()); + } + public void SetSystemDecorations(Controls.SystemDecorations enabled) { _native.SetDecorations((Interop.SystemDecorations)enabled); @@ -138,10 +150,17 @@ namespace Avalonia.Native { if (_doubleClickHelper.IsDoubleClick(e.Timestamp, e.Position)) { - if (_canResize) + switch (WindowState) { - WindowState = WindowState is WindowState.Maximized or WindowState.FullScreen ? - WindowState.Normal : WindowState.Maximized; + case WindowState.Maximized or WindowState.FullScreen + when _canResize: + WindowState = WindowState.Normal; + break; + + case WindowState.Normal + when _canMaximize: + WindowState = WindowState.Maximized; + break; } } else diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index bc2d6ddd1a..97afb8667e 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -772,6 +772,8 @@ interface IAvnWindow : IAvnWindowBase { HRESULT SetEnabled(bool enable); HRESULT SetCanResize(bool value); + HRESULT SetCanMinimize(bool value); + HRESULT SetCanMaximize(bool value); HRESULT SetDecorations(SystemDecorations value); HRESULT SetTitle(char* utf8Title); HRESULT SetTitleBarColor(AvnColor color); diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index bcaeac8012..d9f9343925 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -112,7 +112,7 @@ - diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0fecf6a47d..eee61a707f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -316,22 +316,28 @@ namespace Avalonia.X11 || _systemDecorations == SystemDecorations.None) decorations = 0; - if (!_canResize || !IsEnabled) + var isDisabled = !IsEnabled; + + if (!_canResize || isDisabled) { - functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); - decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); + functions &= ~MotifFunctions.Resize; + decorations &= ~MotifDecorations.ResizeH; } - if (!IsEnabled) - { - functions &= ~(MotifFunctions.Resize | MotifFunctions.Minimize); - UpdateSizeHints(null, true); + if (!_canMinimize || isDisabled) + { + functions &= ~MotifFunctions.Minimize; + decorations &= ~MotifDecorations.Minimize; } - else + + if (!_canMaximize || isDisabled) { - UpdateSizeHints(null); + functions &= ~MotifFunctions.Maximize; + decorations &= ~MotifDecorations.Maximize; } + UpdateSizeHints(null, isDisabled); + var hints = new MotifWmHints { flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)), @@ -857,6 +863,8 @@ namespace Avalonia.X11 private SystemDecorations _systemDecorations = SystemDecorations.Full; private bool _canResize = true; + private bool _canMinimize = true; + private bool _canMaximize = true; private const int MaxWindowDimension = 100000; private (Size minSize, Size maxSize) _scaledMinMaxSize = @@ -1162,6 +1170,18 @@ namespace Avalonia.X11 UpdateSizeHints(null); } + public void SetCanMinimize(bool value) + { + _canMinimize = value; + UpdateMotifHints(); + } + + public void SetCanMaximize(bool value) + { + _canMaximize = value; + UpdateMotifHints(); + } + public void SetCursor(ICursorImpl? cursor) { if (cursor == null) diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 64ce9945d4..2cd3df7c54 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -165,6 +165,14 @@ namespace Avalonia.Headless } + public void SetCanMinimize(bool value) + { + } + + public void SetCanMaximize(bool value) + { + } + public Func? Closing { get; set; } private class FramebufferProxy : ILockedFramebuffer diff --git a/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs b/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs index 509be65784..a039d568c6 100644 --- a/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs +++ b/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs @@ -12,6 +12,8 @@ namespace Avalonia.Win32 { ShowInTaskbar = false, IsResizable = false, + IsMinimizable = false, + IsMaximizable = false, Decorations = SystemDecorations.None }; } diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 95c8178adc..8a8450443f 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -111,6 +111,8 @@ namespace Avalonia.Win32 { ShowInTaskbar = false, IsResizable = false, + IsMinimizable = false, + IsMaximizable = false, Decorations = SystemDecorations.None, }; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 784ad7c2aa..54e464720b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -136,6 +136,8 @@ namespace Avalonia.Win32 { ShowInTaskbar = false, IsResizable = true, + IsMinimizable = true, + IsMaximizable = true, Decorations = SystemDecorations.Full }; @@ -858,6 +860,24 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } + public void SetCanMinimize(bool value) + { + var newWindowProperties = _windowProperties; + + newWindowProperties.IsMinimizable = value; + + UpdateWindowProperties(newWindowProperties); + } + + public void SetCanMaximize(bool value) + { + var newWindowProperties = _windowProperties; + + newWindowProperties.IsMaximizable = value; + + UpdateWindowProperties(newWindowProperties); + } + public void SetSystemDecorations(SystemDecorations value) { var newWindowProperties = _windowProperties; @@ -1425,15 +1445,19 @@ namespace Avalonia.Win32 style |= WindowStyles.WS_VISIBLE; if (newProperties.IsResizable || newProperties.WindowState == WindowState.Maximized) - { style |= WindowStyles.WS_THICKFRAME; - style |= WindowStyles.WS_MAXIMIZEBOX; - } else - { style &= ~WindowStyles.WS_THICKFRAME; + + if (newProperties.IsMinimizable) + style |= WindowStyles.WS_MINIMIZEBOX; + else + style &= ~WindowStyles.WS_MINIMIZEBOX; + + if (newProperties.IsMaximizable || (newProperties.WindowState == WindowState.Maximized && newProperties.IsResizable)) + style |= WindowStyles.WS_MAXIMIZEBOX; + else style &= ~WindowStyles.WS_MAXIMIZEBOX; - } const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU | WindowStyles.WS_BORDER; @@ -1656,6 +1680,8 @@ namespace Avalonia.Win32 { public bool ShowInTaskbar; public bool IsResizable; + public bool IsMinimizable; + public bool IsMaximizable; public SystemDecorations Decorations; public bool IsFullScreen; public WindowState WindowState; diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index a7f2692a57..e769f350af 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -672,6 +672,23 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void CanMaximize_Should_Be_False_If_CanResize_Is_False() + { + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + + using var app = UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object))); + + var window = new Window(); + + Assert.True(window.CanMaximize); + + window.CanResize = false; + + Assert.False(window.CanMaximize); + } + public class SizingTests : ScopedTestBase { [Fact] diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index d4a6380b5d..b6cbf38adf 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -311,6 +311,37 @@ namespace Avalonia.IntegrationTests.Appium } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Window_Minimize_Button_Enabled_Matches_CanMinimize(bool canMinimize) + { + using (OpenWindow(canMinimize: canMinimize)) + { + var secondaryWindow = Session.GetWindowById("SecondaryWindow"); + var windowChrome = secondaryWindow.GetSystemChromeButtons(); + + Assert.NotNull(windowChrome.Minimize); + Assert.Equal(canMinimize, windowChrome.Minimize!.Enabled); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Window_Maximize_Button_Enabled_Matches_CanMaximize(bool canMaximize) + { + using (OpenWindow(canMaximize: canMaximize)) + { + var secondaryWindow = Session.GetWindowById("SecondaryWindow"); + var windowChrome = secondaryWindow.GetSystemChromeButtons(); + + var maximizeButton = windowChrome.FullScreen ?? windowChrome.Maximize; + Assert.NotNull(maximizeButton); + Assert.Equal(canMaximize, maximizeButton.Enabled); + } + } + [Fact] public void Changing_SystemDecorations_Should_Not_Change_Frame_Size_And_Position() { @@ -452,13 +483,17 @@ namespace Avalonia.IntegrationTests.Appium WindowStartupLocation location = WindowStartupLocation.Manual, WindowState state = Controls.WindowState.Normal, bool canResize = true, - bool extendClientArea = false) + bool extendClientArea = false, + bool canMinimize = true, + bool canMaximize = true) { var sizeTextBox = Session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = Session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = Session.FindElementByAccessibilityId("ShowWindowLocation"); var stateComboBox = Session.FindElementByAccessibilityId("ShowWindowState"); var canResizeCheckBox = Session.FindElementByAccessibilityId("ShowWindowCanResize"); + var canMinimizeCheckBox = Session.FindElementByAccessibilityId("ShowWindowCanMinimize"); + var canMaximizeCheckBox = Session.FindElementByAccessibilityId("ShowWindowCanMaximize"); var showButton = Session.FindElementByAccessibilityId("ShowWindow"); var extendClientAreaCheckBox = Session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint"); @@ -486,6 +521,12 @@ namespace Avalonia.IntegrationTests.Appium if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); + if (canMinimizeCheckBox.GetIsChecked() != canMinimize) + canMinimizeCheckBox.Click(); + + if (canMaximizeCheckBox.GetIsChecked() != canMaximize) + canMaximizeCheckBox.Click(); + if (extendClientAreaCheckBox.GetIsChecked() != extendClientArea) extendClientAreaCheckBox.Click();