Browse Source

Add Window.CanMinimize/CanMaximize (#18117)

* Add CanMinimize and CanMaximize to Window

* Win32 impl for CanMinimize/CanMaximize

* Add CanResize/CanMinimize/CanMaximize samples to control catalog

* X11 impl for CanMinimize/CanMaximize

* macOS impl for CanMinimize/CanMaximize

* Win32: don't allow restore when the window isn't resizable

* Additional documentation for CanMinimize/CanMaximize

* Add CanMinimize/CanMaximize logic to CaptionButtons

* Use START_COM_ARP_CALL

* Added CanMinimize/CanMaximize integration tests

* Fixed CanMaximize tests on macOS
pull/19461/head
Julien Lebosquain 6 months ago
committed by GitHub
parent
commit
1f8701a2fc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      native/Avalonia.Native/src/OSX/WindowImpl.h
  2. 27
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  3. 3
      samples/ControlCatalog/MainWindow.xaml
  4. 49
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  5. 64
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  6. 6
      samples/IntegrationTestApp/Pages/WindowPage.axaml
  7. 5
      samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
  8. 55
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  9. 11
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  10. 53
      src/Avalonia.Controls/Window.cs
  11. 8
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  12. 8
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  13. 25
      src/Avalonia.Native/WindowImpl.cs
  14. 2
      src/Avalonia.Native/avn.idl
  15. 2
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  16. 38
      src/Avalonia.X11/X11Window.cs
  17. 8
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  18. 2
      src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs
  19. 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  20. 36
      src/Windows/Avalonia.Win32/WindowImpl.cs
  21. 17
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  22. 43
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

8
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;

27
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)

3
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}"

49
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -8,18 +8,55 @@
x:DataType="viewModels:MainWindowViewModel"
x:CompileBindings="True">
<StackPanel>
<StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Desktop=true}">
<TextBlock Classes="h2" Text="Desktop properties" Margin="4" />
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" />
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" />
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" />
<StackPanel
Spacing="10"
Margin="25"
IsEnabled="{OnFormFactor false, Desktop=true}">
<TextBlock Classes="h2"
Text="Desktop properties"
Margin="4" />
<CheckBox Content="Extend Client Area to Decorations"
IsChecked="{Binding ExtendClientAreaEnabled}" />
<DockPanel IsEnabled="{Binding ExtendClientAreaEnabled}">
<CheckBox Content="Title Bar"
IsChecked="{Binding SystemTitleBarEnabled}"
DockPanel.Dock="Left" />
<Slider Minimum="-1"
Maximum="200"
Value="{Binding TitleBarHeight}"
IsEnabled="{Binding SystemTitleBarEnabled}"
Margin="8,-10" />
</DockPanel>
<CheckBox Content="Prefer System Chrome"
IsChecked="{Binding PreferSystemChromeEnabled}"
IsEnabled="{Binding ExtendClientAreaEnabled}" />
<CheckBox Content="Can Resize"
IsChecked="{Binding CanResize}" />
<CheckBox Content="Can Minimize"
IsChecked="{Binding CanMinimize}" />
<CheckBox Content="Can Maximize"
IsChecked="{Binding CanMaximize}"
IsEnabled="{Binding CanResize}" />
</StackPanel>
<StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Mobile=true}">
<TextBlock Classes="h2" Text="Mobile properties" Margin="4" />
<CheckBox Content="Is System Bar Visible" IsChecked="{Binding IsSystemBarVisible}" />
<CheckBox Content="Display Edge To Edge" IsChecked="{Binding DisplayEdgeToEdge}" />
<TextBlock Text="{Binding SafeAreaPadding, StringFormat='Safe Area Padding: {0}'}" />
</StackPanel>
</StackPanel>
</UserControl>

64
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);
}
}
}

6
samples/IntegrationTestApp/Pages/WindowPage.axaml

@ -30,14 +30,16 @@
</ComboBox>
<CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<CheckBox Name="ShowWindowCanMinimize" IsChecked="True">Can Minimize</CheckBox>
<CheckBox Name="ShowWindowCanMaximize" IsChecked="True">Can Maximize</CheckBox>
</StackPanel>
<StackPanel Grid.Column="2">
<Button Name="ShowWindow" Click="ShowWindow_Click">Show Window</Button>
<Button Name="SendToBack" Click="SendToBack_Click">Send to Back</Button>
<Button Name="EnterFullscreen" Click="EnterFullscreen_Click">Enter Fullscreen</Button>
<Button Name="ExitFullscreen" Click="ExitFullscreen_Click">Exit Fullscreen</Button>
<Button Name="RestoreAll" Click="RestoreAll_Click">Restore All</Button>
<Button Name="ShowTopmostWindow" Click="ShowTopmostWindow_Click">Show Topmost Window</Button>
</StackPanel>
<StackPanel Grid.Column="2">
<Button Name="ShowTransparentWindow" Click="ShowTransparentWindow_Click">Transparent Window</Button>
<Button Name="ShowTransparentPopup" Click="ShowTransparentPopup_Click">Transparent Popup</Button>
</StackPanel>

5
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)

55
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;
/// <summary>
@ -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<Button>(PART_MinimizeButton) is { } minimizeButton)
@ -127,6 +137,8 @@ namespace Avalonia.Controls.Chrome
OnMinimize();
args.Handled = true;
};
_minimizeButton = minimizeButton;
UpdateMinimizeButtonState();
}
if (e.NameScope.Find<Button>(PART_FullScreenButton) is { } fullScreenButton)
@ -136,7 +148,40 @@ namespace Avalonia.Controls.Chrome
OnToggleFullScreen();
args.Handled = true;
};
_fullScreenButton = fullScreenButton;
UpdateFullScreenButtonState();
}
}
private void UpdateRestoreButtonState()
{
if (_restoreButton is null)
return;
_restoreButton.IsEnabled = HostWindow?.WindowState switch
{
WindowState.Maximized or WindowState.FullScreen => HostWindow.CanResize,
WindowState.Normal => HostWindow.CanMaximize,
_ => true
};
}
private void UpdateMinimizeButtonState()
{
if (_minimizeButton is null)
return;
_minimizeButton.IsEnabled = HostWindow?.CanMinimize ?? true;
}
private void UpdateFullScreenButtonState()
{
if (_fullScreenButton is null)
return;
_fullScreenButton.IsEnabled = HostWindow?.WindowState == WindowState.FullScreen ?
HostWindow.CanResize :
HostWindow?.CanMaximize ?? true;
}
}
}

11
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -64,6 +64,16 @@ namespace Avalonia.Platform
/// </summary>
void CanResize(bool value);
/// <summary>
/// Enables or disables minimizing the window.
/// </summary>
void SetCanMinimize(bool value);
/// <summary>
/// Enables or disables maximizing the window.
/// </summary>
void SetCanMaximize(bool value);
/// <summary>
/// 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
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>

53
src/Avalonia.Controls/Window.cs

@ -181,9 +181,24 @@ namespace Avalonia.Controls
public static readonly StyledProperty<WindowStartupLocation> WindowStartupLocationProperty =
AvaloniaProperty.Register<Window, WindowStartupLocation>(nameof(WindowStartupLocation));
/// <summary>
/// Defines the <see cref="CanResize"/> property.
/// </summary>
public static readonly StyledProperty<bool> CanResizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
/// <summary>
/// Defines the <see cref="CanMinimize"/> property.
/// </summary>
public static readonly StyledProperty<bool> CanMinimizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanMinimize), true);
/// <summary>
/// Defines the <see cref="CanMaximize"/> property.
/// </summary>
public static readonly StyledProperty<bool> CanMaximizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanMaximize), true, coerce: CoerceCanMaximize);
/// <summary>
/// Routed event that can be used for global tracking of window destruction
/// </summary>
@ -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);
}
/// <summary>
/// Enables or disables minimizing the window.
/// </summary>
/// <remarks>
/// This property might be ignored by some window managers on Linux.
/// </remarks>
public bool CanMinimize
{
get => GetValue(CanMinimizeProperty);
set => SetValue(CanMinimizeProperty, value);
}
/// <summary>
/// Enables or disables maximizing the window.
/// </summary>
/// <remarks>
/// <para>When <see cref="CanResize"/> is false, this property is always false.</para>
/// <para>On macOS, setting this property to false also disables the full screen mode.</para>
/// <para>This property might be ignored by some window managers on Linux.</para>
/// </remarks>
public bool CanMaximize
{
get => GetValue(CanMaximizeProperty);
set => SetValue(CanMaximizeProperty, value);
}
/// <summary>
/// Gets or sets the icon of the window.
/// </summary>
@ -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 };
}
}

8
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)
{
}

8
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)
{
}

25
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

2
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);

2
src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml

@ -112,7 +112,7 @@
<Style Selector="^:fullscreen /template/ Button#PART_MinimizeButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^ /template/ Button#PART_RestoreButton:disabled">
<Style Selector="^ /template/ Button:disabled">
<Setter Property="Opacity" Value="0.2"/>
</Style>
</ControlTheme>

38
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)

8
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<WindowCloseReason, bool>? Closing { get; set; }
private class FramebufferProxy : ILockedFramebuffer

2
src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs

@ -12,6 +12,8 @@ namespace Avalonia.Win32
{
ShowInTaskbar = false,
IsResizable = false,
IsMinimizable = false,
IsMaximizable = false,
Decorations = SystemDecorations.None
};
}

2
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -111,6 +111,8 @@ namespace Avalonia.Win32
{
ShowInTaskbar = false,
IsResizable = false,
IsMinimizable = false,
IsMaximizable = false,
Decorations = SystemDecorations.None,
};

36
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;

17
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]

43
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();

Loading…
Cancel
Save