diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index b10db08adc..38d99db5c9 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -135,6 +135,7 @@ enum AvnWindowState Normal, Minimized, Maximized, + FullScreen, }; enum AvnStandardCursorType @@ -246,7 +247,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; virtual HRESULT SetCanResize(bool value) = 0; - virtual HRESULT SetHasDecorations(SystemDecorations value) = 0; + virtual HRESULT SetDecorations(SystemDecorations value) = 0; virtual HRESULT SetTitle (void* utf8Title) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 8092db3663..ec8fe9e6ee 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -35,6 +35,10 @@ struct INSWindowHolder struct IWindowStateChanged { virtual void WindowStateChanged () = 0; + virtual void StartStateTransition () = 0; + virtual void EndStateTransition () = 0; + virtual SystemDecorations Decorations () = 0; + virtual AvnWindowState WindowState () = 0; }; #endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 68899df9f0..091219fc72 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -391,7 +391,7 @@ protected: void UpdateStyle() { - [Window setStyleMask:GetStyle()]; + [Window setStyleMask: GetStyle()]; } public: @@ -404,10 +404,13 @@ public: class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged { private: - bool _canResize = true; - SystemDecorations _hasDecorations = SystemDecorationsFull; - CGRect _lastUndecoratedFrame; + bool _canResize; + bool _fullScreenActive; + SystemDecorations _decorations; AvnWindowState _lastWindowState; + bool _inSetWindowState; + NSRect _preZoomSize; + bool _transitioningWindowState; FORWARD_IUNKNOWN() BEGIN_INTERFACE_MAP() @@ -421,6 +424,11 @@ private: ComPtr WindowEvents; WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) { + _fullScreenActive = false; + _canResize = true; + _decorations = SystemDecorationsFull; + _transitioningWindowState = false; + _inSetWindowState = false; _lastWindowState = Normal; WindowEvents = events; [Window setCanBecomeKeyAndMain]; @@ -428,6 +436,20 @@ private: [Window setTabbingMode:NSWindowTabbingModeDisallowed]; } + void HideOrShowTrafficLights () + { + for (id subview in Window.contentView.superview.subviews) { + if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { + NSView *titlebarView = [subview subviews][0]; + for (id button in titlebarView.subviews) { + if ([button isKindOfClass:[NSButton class]]) { + [button setHidden: (_decorations != SystemDecorationsFull)]; + } + } + } + } + } + virtual HRESULT Show () override { @autoreleasepool @@ -439,6 +461,8 @@ private: WindowBaseImpl::Show(); + HideOrShowTrafficLights(); + return SetWindowState(_lastWindowState); } } @@ -459,41 +483,69 @@ private: [cparent->Window addChildWindow:Window ordered:NSWindowAbove]; WindowBaseImpl::Show(); + HideOrShowTrafficLights(); + return S_OK; } } + void StartStateTransition () override + { + _transitioningWindowState = true; + } + + void EndStateTransition () override + { + _transitioningWindowState = false; + } + + SystemDecorations Decorations () override + { + return _decorations; + } + + AvnWindowState WindowState () override + { + return _lastWindowState; + } + void WindowStateChanged () override { - AvnWindowState state; - GetWindowState(&state); - WindowEvents->WindowStateChanged(state); + if(!_inSetWindowState && !_transitioningWindowState) + { + AvnWindowState state; + GetWindowState(&state); + + if(_lastWindowState != state) + { + _lastWindowState = state; + WindowEvents->WindowStateChanged(state); + } + } } bool UndecoratedIsMaximized () { - return CGRectEqualToRect([Window frame], [Window screen].visibleFrame); + auto windowSize = [Window frame]; + auto available = [Window screen].visibleFrame; + return CGRectEqualToRect(windowSize, available); } bool IsZoomed () { - return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized(); + return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized(); } void DoZoom() { - switch (_hasDecorations) + switch (_decorations) { case SystemDecorationsNone: - if (!UndecoratedIsMaximized()) - { - _lastUndecoratedFrame = [Window frame]; - } - - [Window zoom:Window]; + case SystemDecorationsBorderOnly: + [Window setFrame:[Window screen].visibleFrame display:true]; break; - case SystemDecorationsBorderOnly: + case SystemDecorationsFull: [Window performZoom:Window]; break; @@ -510,25 +562,52 @@ private: } } - virtual HRESULT SetHasDecorations(SystemDecorations value) override + virtual HRESULT SetDecorations(SystemDecorations value) override { @autoreleasepool { - _hasDecorations = value; + auto currentWindowState = _lastWindowState; + _decorations = value; + + if(_fullScreenActive) + { + return S_OK; + } + + auto currentFrame = [Window frame]; + UpdateStyle(); + + HideOrShowTrafficLights(); - switch (_hasDecorations) + switch (_decorations) { case SystemDecorationsNone: [Window setHasShadow:NO]; [Window setTitleVisibility:NSWindowTitleHidden]; [Window setTitlebarAppearsTransparent:YES]; + + if(currentWindowState == Maximized) + { + if(!UndecoratedIsMaximized()) + { + DoZoom(); + } + } break; case SystemDecorationsBorderOnly: [Window setHasShadow:YES]; [Window setTitleVisibility:NSWindowTitleHidden]; [Window setTitlebarAppearsTransparent:YES]; + + if(currentWindowState == Maximized) + { + if(!UndecoratedIsMaximized()) + { + DoZoom(); + } + } break; case SystemDecorationsFull: @@ -536,6 +615,13 @@ private: [Window setTitleVisibility:NSWindowTitleVisible]; [Window setTitlebarAppearsTransparent:NO]; [Window setTitle:_lastTitle]; + + if(currentWindowState == Maximized) + { + auto newFrame = [Window contentRectForFrameRect:[Window frame]].size; + + [View setFrameSize:newFrame]; + } break; } @@ -592,13 +678,19 @@ private: return E_POINTER; } + if(([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen) + { + *ret = FullScreen; + return S_OK; + } + if([Window isMiniaturized]) { *ret = Minimized; return S_OK; } - if([Window isZoomed]) + if(IsZoomed()) { *ret = Maximized; return S_OK; @@ -610,16 +702,57 @@ private: } } + void EnterFullScreenMode () + { + _fullScreenActive = true; + + [Window setHasShadow:YES]; + [Window setTitleVisibility:NSWindowTitleVisible]; + [Window setTitlebarAppearsTransparent:NO]; + [Window setTitle:_lastTitle]; + + [Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable]; + + [Window toggleFullScreen:nullptr]; + } + + void ExitFullScreenMode () + { + [Window toggleFullScreen:nullptr]; + + _fullScreenActive = false; + + SetDecorations(_decorations); + } + virtual HRESULT SetWindowState (AvnWindowState state) override { @autoreleasepool { + if(_lastWindowState == state) + { + return S_OK; + } + + _inSetWindowState = true; + + auto currentState = _lastWindowState; _lastWindowState = state; + if(currentState == Normal) + { + _preZoomSize = [Window frame]; + } + if(_shown) { switch (state) { case Maximized: + if(currentState == FullScreen) + { + ExitFullScreenMode(); + } + lastPositionSet.X = 0; lastPositionSet.Y = 0; @@ -635,40 +768,66 @@ private: break; case Minimized: - [Window miniaturize:Window]; + if(currentState == FullScreen) + { + ExitFullScreenMode(); + } + else + { + [Window miniaturize:Window]; + } + break; + + case FullScreen: + if([Window isMiniaturized]) + { + [Window deminiaturize:Window]; + } + + EnterFullScreenMode(); break; - default: + case Normal: if([Window isMiniaturized]) { [Window deminiaturize:Window]; } + if(currentState == FullScreen) + { + ExitFullScreenMode(); + } + if(IsZoomed()) { - DoZoom(); + if(_decorations == SystemDecorationsFull) + { + DoZoom(); + } + else + { + [Window setFrame:_preZoomSize display:true]; + auto newFrame = [Window contentRectForFrameRect:[Window frame]].size; + + [View setFrameSize:newFrame]; + } + } break; } } + _inSetWindowState = false; + return S_OK; } } virtual void OnResized () override { - if(_shown) + if(_shown && !_inSetWindowState && !_transitioningWindowState) { - auto windowState = [Window isMiniaturized] ? Minimized - : (IsZoomed() ? Maximized : Normal); - - if (windowState != _lastWindowState) - { - _lastWindowState = windowState; - - WindowEvents->WindowStateChanged(windowState); - } + WindowStateChanged(); } } @@ -677,22 +836,23 @@ protected: { unsigned long s = NSWindowStyleMaskBorderless; - switch (_hasDecorations) + switch (_decorations) { case SystemDecorationsNone: + s = s | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable; break; case SystemDecorationsBorderOnly: - s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; + s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable; break; case SystemDecorationsFull: s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; + if(_canResize) { s = s | NSWindowStyleMaskResizable; } - break; } @@ -1171,6 +1331,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } +- (void)performClose:(id)sender +{ + if([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) + { + if(![[self delegate] windowShouldClose:self]) return; + } + else if([self respondsToSelector:@selector(windowShouldClose:)]) + { + if(![self windowShouldClose:self]) return; + } + + [self close]; +} + - (void)pollModalSession:(nonnull NSModalSession)session { auto response = [NSApp runModalSession:session]; @@ -1399,7 +1573,66 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)windowDidResize:(NSNotification *)notification { - _parent->OnResized(); + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->WindowStateChanged(); + } +} + +- (void)windowWillExitFullScreen:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->StartStateTransition(); + } +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->EndStateTransition(); + + if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized) + { + NSRect screenRect = [[self screen] visibleFrame]; + [self setFrame:screenRect display:YES]; + } + + if(parent->WindowState() == Minimized) + { + [self miniaturize:nullptr]; + } + + parent->WindowStateChanged(); + } +} + +- (void)windowWillEnterFullScreen:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->StartStateTransition(); + } +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->EndStateTransition(); + parent->WindowStateChanged(); + } } - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index f3f70719e3..e02308b5c6 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -59,8 +59,8 @@ - - + + No Decorations Border Only Full Decorations @@ -69,6 +69,7 @@ Light Dark + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index bea751ad4c..935db20757 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow"> + x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"> diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index b6aa3e92cd..0257b4ce66 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using System.Reactive; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Dialogs; @@ -11,6 +12,8 @@ namespace ControlCatalog.ViewModels private IManagedNotificationManager _notificationManager; private bool _isMenuItemChecked = true; + private WindowState _windowState; + private WindowState[] _windowStates; public MainWindowViewModel(IManagedNotificationManager notificationManager) { @@ -45,10 +48,32 @@ namespace ControlCatalog.ViewModels (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); }); - ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() => + ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() => { IsMenuItemChecked = !IsMenuItemChecked; }); + + WindowState = WindowState.Normal; + + WindowStates = new WindowState[] + { + WindowState.Minimized, + WindowState.Normal, + WindowState.Maximized, + WindowState.FullScreen, + }; + } + + public WindowState WindowState + { + get { return _windowState; } + set { this.RaiseAndSetIfChanged(ref _windowState, value); } + } + + public WindowState[] WindowStates + { + get { return _windowStates; } + set { this.RaiseAndSetIfChanged(ref _windowStates, value); } } public IManagedNotificationManager NotificationManager diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 3715bc52a4..8ad622ba4a 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -392,7 +392,7 @@ namespace Avalonia.Controls.Platform { var control = e.Source as ILogical; - if (!Menu.IsLogicalParentOf(control)) + if (!Menu.IsLogicalAncestorOf(control)) { Menu.Close(); } diff --git a/src/Avalonia.Controls/WindowState.cs b/src/Avalonia.Controls/WindowState.cs index 4ed30e726e..777b52dc11 100644 --- a/src/Avalonia.Controls/WindowState.cs +++ b/src/Avalonia.Controls/WindowState.cs @@ -19,5 +19,10 @@ namespace Avalonia.Controls /// The window is maximized. /// Maximized, + + /// + /// The window is fullscreen. + /// + FullScreen, } } diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs b/src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs index b967b40c0d..616f260397 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs @@ -34,7 +34,7 @@ namespace Avalonia.Dialogs return; } - var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); + var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control); if (e.ClickCount == 2 || isQuickLink) { if (model.ItemType == ManagedFileChooserItemType.File) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 6d6b2c5296..ec010815f4 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Native public void SetSystemDecorations(Controls.SystemDecorations enabled) { - _native.HasDecorations = (Interop.SystemDecorations)enabled; + _native.Decorations = (Interop.SystemDecorations)enabled; } public void SetTitleBarColor (Avalonia.Media.Color color) diff --git a/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs b/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs index 1003375978..458ab0fce2 100644 --- a/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs +++ b/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs @@ -1,11 +1,18 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Avalonia.LogicalTree { + /// + /// Provides extension methods for working with the logical tree. + /// public static class LogicalExtensions { + /// + /// Enumerates the ancestors of an in the logical tree. + /// + /// The logical. + /// The logical's ancestors. public static IEnumerable GetLogicalAncestors(this ILogical logical) { Contract.Requires(logical != null); @@ -19,6 +26,11 @@ namespace Avalonia.LogicalTree } } + /// + /// Enumerates an and its ancestors in the logical tree. + /// + /// The logical. + /// The logical and its ancestors. public static IEnumerable GetSelfAndLogicalAncestors(this ILogical logical) { yield return logical; @@ -29,11 +41,50 @@ namespace Avalonia.LogicalTree } } + /// + /// Finds first ancestor of given type. + /// + /// Ancestor type. + /// The logical. + /// If given logical should be included in search. + /// First ancestor of given type. + public static T FindLogicalAncestorOfType(this ILogical logical, bool includeSelf = false) where T : class + { + if (logical is null) + { + return null; + } + + ILogical parent = includeSelf ? logical : logical.LogicalParent; + + while (parent != null) + { + if (parent is T result) + { + return result; + } + + parent = parent.LogicalParent; + } + + return null; + } + + /// + /// Enumerates the children of an in the logical tree. + /// + /// The logical. + /// The logical children. public static IEnumerable GetLogicalChildren(this ILogical logical) { return logical.LogicalChildren; } + /// + /// Enumerates the descendants of an in the logical tree. + /// + /// The logical. + /// The logical's ancestors. public static IEnumerable GetLogicalDescendants(this ILogical logical) { foreach (ILogical child in logical.LogicalChildren) @@ -47,6 +98,11 @@ namespace Avalonia.LogicalTree } } + /// + /// Enumerates an and its descendants in the logical tree. + /// + /// The logical. + /// The logical and its ancestors. public static IEnumerable GetSelfAndLogicalDescendants(this ILogical logical) { yield return logical; @@ -57,16 +113,56 @@ namespace Avalonia.LogicalTree } } + /// + /// Finds first descendant of given type. + /// + /// Descendant type. + /// The logical. + /// If given logical should be included in search. + /// First descendant of given type. + public static T FindLogicalDescendantOfType(this ILogical logical, bool includeSelf = false) where T : class + { + if (logical is null) + { + return null; + } + + if (includeSelf && logical is T result) + { + return result; + } + + return FindDescendantOfTypeCore(logical); + } + + /// + /// Gets the logical parent of an . + /// + /// The logical. + /// The parent, or null if the logical is unparented. public static ILogical GetLogicalParent(this ILogical logical) { return logical.LogicalParent; } + /// + /// Gets the logical parent of an . + /// + /// The type of the logical parent. + /// The logical. + /// + /// The parent, or null if the logical is unparented or its parent is not of type . + /// public static T GetLogicalParent(this ILogical logical) where T : class { return logical.LogicalParent as T; } + /// + /// Enumerates the siblings of an in the logical tree. + /// + /// The logical. + /// The logical siblings. public static IEnumerable GetLogicalSiblings(this ILogical logical) { ILogical parent = logical.LogicalParent; @@ -80,9 +176,55 @@ namespace Avalonia.LogicalTree } } - public static bool IsLogicalParentOf(this ILogical logical, ILogical target) + /// + /// Tests whether an is an ancestor of another logical. + /// + /// The logical. + /// The potential descendant. + /// + /// True if is an ancestor of ; + /// otherwise false. + /// + public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) { - return target.GetLogicalAncestors().Any(x => x == logical); + ILogical current = target?.LogicalParent; + + while (current != null) + { + if (current == logical) + { + return true; + } + + current = current.LogicalParent; + } + + return false; + } + + private static T FindDescendantOfTypeCore(ILogical logical) where T : class + { + var logicalChildren = logical.LogicalChildren; + var logicalChildrenCount = logicalChildren.Count; + + for (var i = 0; i < logicalChildrenCount; i++) + { + ILogical child = logicalChildren[i]; + + if (child is T result) + { + return result; + } + + var childResult = FindDescendantOfTypeCore(child); + + if (!(childResult is null)) + { + return childResult; + } + } + + return null; } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index b4bf4c799a..0f6001516d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph /// public class Scene : IDisposable { - private Dictionary _index; + private readonly Dictionary _index; /// /// Initializes a new instance of the class. @@ -83,7 +83,7 @@ namespace Avalonia.Rendering.SceneGraph /// The cloned scene. public Scene CloneScene() { - var index = new Dictionary(); + var index = new Dictionary(_index.Count); var root = Clone((VisualNode)Root, null, index); var result = new Scene(root, index, Layers.Clone(), Generation + 1) @@ -162,9 +162,19 @@ namespace Avalonia.Rendering.SceneGraph index.Add(result.Visual, result); - foreach (var child in source.Children) + var children = source.Children; + var childrenCount = children.Count; + + if (childrenCount > 0) { - result.AddChild(Clone((VisualNode)child, result, index)); + result.TryPreallocateChildren(childrenCount); + + for (var i = 0; i < childrenCount; i++) + { + var child = children[i]; + + result.AddChild(Clone((VisualNode)child, result, index)); + } } return result; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs index 5960b4f560..25f7383a1a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs @@ -11,16 +11,28 @@ namespace Avalonia.Rendering.SceneGraph public class SceneLayers : IEnumerable { private readonly IVisual _root; - private readonly List _inner = new List(); - private readonly Dictionary _index = new Dictionary(); + private readonly List _inner; + private readonly Dictionary _index; /// /// Initializes a new instance of the class. /// /// The scene's root visual. - public SceneLayers(IVisual root) + public SceneLayers(IVisual root) : this(root, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The scene's root visual. + /// Initial layer capacity. + public SceneLayers(IVisual root, int capacity) { _root = root; + + _inner = new List(capacity); + _index = new Dictionary(capacity); } /// @@ -84,7 +96,7 @@ namespace Avalonia.Rendering.SceneGraph /// The cloned layers. public SceneLayers Clone() { - var result = new SceneLayers(_root); + var result = new SceneLayers(_root, Count); foreach (var src in _inner) { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 82444a0c29..6f566ff6d6 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reactive.Disposables; +using Avalonia.Collections; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; @@ -349,6 +349,11 @@ namespace Avalonia.Rendering.SceneGraph context.Transform = transformRestore; } + internal void TryPreallocateChildren(int count) + { + EnsureChildrenCreated(count); + } + private Rect CalculateBounds() { var result = new Rect(); @@ -362,11 +367,11 @@ namespace Avalonia.Rendering.SceneGraph return result; } - private void EnsureChildrenCreated() + private void EnsureChildrenCreated(int capacity = 0) { if (_children == null) { - _children = new List(); + _children = new List(capacity); } } @@ -383,7 +388,15 @@ namespace Avalonia.Rendering.SceneGraph } else if (_drawOperationsCloned) { - _drawOperations = new List>(_drawOperations.Select(op => op.Clone())); + var oldDrawOperations = _drawOperations; + + _drawOperations = new List>(oldDrawOperations.Count); + + foreach (var drawOperation in oldDrawOperations) + { + _drawOperations.Add(drawOperation.Clone()); + } + _drawOperationsRefCounter.Dispose(); _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations)); _drawOperationsCloned = false; @@ -399,9 +412,9 @@ namespace Avalonia.Rendering.SceneGraph /// Disposable for given draw operations. private static IDisposable CreateDisposeDrawOperations(List> drawOperations) { - return Disposable.Create(() => + return Disposable.Create(drawOperations, operations => { - foreach (var operation in drawOperations) + foreach (var operation in operations) { operation.Dispose(); } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 4b3e757e7a..8c4004efdc 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -377,7 +377,19 @@ namespace Avalonia.VisualTree /// public static bool IsVisualAncestorOf(this IVisual visual, IVisual target) { - return target.GetVisualAncestors().Any(x => x == visual); + IVisual current = target?.VisualParent; + + while (current != null) + { + if (current == visual) + { + return true; + } + + current = current.VisualParent; + } + + return false; } public static IEnumerable SortByZIndex(this IEnumerable elements) diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index db74a32b99..523b65c115 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -156,6 +156,7 @@ namespace Avalonia.X11 public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ; public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT; + public readonly IntPtr _NET_WM_STATE_FULLSCREEN; public readonly IntPtr _XEMBED; public readonly IntPtr _XEMBED_INFO; public readonly IntPtr _MOTIF_WM_HINTS; diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 60b739f43a..0c349ce140 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -220,16 +220,11 @@ namespace Avalonia.X11 var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border | MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH; - if (_popup || _systemDecorations == SystemDecorations.None) - { + if (_popup + || _systemDecorations == SystemDecorations.None) decorations = 0; - } - else if (_systemDecorations == SystemDecorations.BorderOnly) - { - decorations = MotifDecorations.Border; - } - if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) + if (!_canResize) { functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); @@ -252,7 +247,7 @@ namespace Avalonia.X11 var min = _minMaxSize.minSize; var max = _minMaxSize.maxSize; - if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) + if (!_canResize) max = min = _realSize; if (preResize.HasValue) @@ -554,12 +549,21 @@ namespace Avalonia.X11 else if (value == WindowState.Maximized) { ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); + ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_FULLSCREEN); ChangeWMAtoms(true, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); } + else if (value == WindowState.FullScreen) + { + ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); + ChangeWMAtoms(true, _x11.Atoms._NET_WM_STATE_FULLSCREEN); + ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } else { ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); + ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_FULLSCREEN); ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); } @@ -587,6 +591,12 @@ namespace Avalonia.X11 break; } + if(pitems[c] == _x11.Atoms._NET_WM_STATE_FULLSCREEN) + { + state = WindowState.FullScreen; + break; + } + if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ || pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT) { @@ -812,7 +822,7 @@ namespace Avalonia.X11 public void SetSystemDecorations(SystemDecorations enabled) { - _systemDecorations = enabled; + _systemDecorations = enabled == SystemDecorations.Full ? SystemDecorations.Full : SystemDecorations.None; UpdateMotifHints(); UpdateSizeHints(null); } @@ -1054,7 +1064,7 @@ namespace Avalonia.X11 void ChangeWMAtoms(bool enable, params IntPtr[] atoms) { - if (atoms.Length < 1 || atoms.Length > 4) + if (atoms.Length != 1 && atoms.Length != 2) throw new ArgumentException(); if (!_mapped) diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 46872f903e..d277267d5d 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -18,7 +18,7 @@ namespace Avalonia.Skia private GRContext GrContext { get; } - public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu) + public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu, long maxResourceBytes = 100663296) { if (customSkiaGpu != null) { @@ -26,6 +26,10 @@ namespace Avalonia.Skia GrContext = _customSkiaGpu.GrContext; + GrContext.GetResourceCacheLimits(out var maxResources, out _); + + GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes); + return; } @@ -39,6 +43,10 @@ namespace Avalonia.Skia : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc))) { GrContext = GRContext.Create(GRBackend.OpenGL, iface); + + GrContext.GetResourceCacheLimits(out var maxResources, out _); + + GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes); } display.ClearContext(); } diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index f4b8fe3c1d..2d5e5990e6 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -8,9 +8,18 @@ namespace Avalonia /// public class SkiaOptions { + public SkiaOptions() + { + MaxGpuResourceSizeBytes = 100663296; // Value taken from skia. + } /// /// Custom gpu factory to use. Can be used to customize behavior of Skia renderer. /// public Func CustomGpuFactory { get; set; } + + /// + /// The maximum number of bytes for video memory to store textures and resources. + /// + public long MaxGpuResourceSizeBytes { get; set; } } } diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs index b4b340d24d..9a5725e06f 100644 --- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs +++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs @@ -18,7 +18,7 @@ namespace Avalonia.Skia public static void Initialize(SkiaOptions options) { var customGpu = options.CustomGpuFactory?.Invoke(); - var renderInterface = new PlatformRenderInterface(customGpu); + var renderInterface = new PlatformRenderInterface(customGpu, options.MaxGpuResourceSizeBytes); AvaloniaLocator.CurrentMutable .Bind().ToConstant(renderInterface) diff --git a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs new file mode 100644 index 0000000000..1b01ebbe7f --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32.Interop +{ + internal class TaskBarList + { + private static IntPtr s_taskBarList; + private static HrInit s_hrInitDelegate; + private static MarkFullscreenWindow s_markFullscreenWindowDelegate; + + /// + /// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc + /// + /// Fullscreen state. + public static unsafe void MarkFullscreen(IntPtr hwnd, bool fullscreen) + { + if (s_taskBarList == IntPtr.Zero) + { + Guid clsid = ShellIds.TaskBarList; + Guid iid = ShellIds.ITaskBarList2; + + int result = CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out s_taskBarList); + + if (s_taskBarList != IntPtr.Zero) + { + var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer(); + + if (s_hrInitDelegate is null) + { + s_hrInitDelegate = Marshal.GetDelegateForFunctionPointer((*ptr)->HrInit); + } + + if (s_hrInitDelegate(s_taskBarList) != HRESULT.S_OK) + { + s_taskBarList = IntPtr.Zero; + } + } + } + + if (s_taskBarList != IntPtr.Zero) + { + var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer(); + + if (s_markFullscreenWindowDelegate is null) + { + s_markFullscreenWindowDelegate = Marshal.GetDelegateForFunctionPointer((*ptr)->MarkFullscreenWindow); + } + + s_markFullscreenWindowDelegate(s_taskBarList, hwnd, fullscreen); + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index cbfa1abfb7..5601ccbafe 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -460,6 +460,7 @@ namespace Avalonia.Win32.Interop WS_SIZEFRAME = 0x40000, WS_SYSMENU = 0x80000, WS_TABSTOP = 0x10000, + WS_THICKFRAME = 0x40000, WS_VISIBLE = 0x10000000, WS_VSCROLL = 0x200000, WS_EX_DLGMODALFRAME = 0x00000001, @@ -1146,7 +1147,10 @@ namespace Avalonia.Win32.Interop internal static extern int CoCreateInstance(ref Guid clsid, IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter); - + [DllImport("ole32.dll", PreserveSig = true)] + internal static extern int CoCreateInstance(ref Guid clsid, + IntPtr ignore1, int ignore2, ref Guid iid, [Out] out IntPtr pUnkOuter); + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); @@ -1642,6 +1646,8 @@ namespace Avalonia.Win32.Interop public static readonly Guid SaveFileDialog = Guid.Parse("C0B4E2F3-BA21-4773-8DBA-335EC946EB8B"); public static readonly Guid IFileDialog = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8"); public static readonly Guid IShellItem = Guid.Parse("43826D1E-E718-42EE-BC55-A1E261C37BFE"); + public static readonly Guid TaskBarList = Guid.Parse("56FDF344-FD6D-11D0-958A-006097C9A090"); + public static readonly Guid ITaskBarList2 = Guid.Parse("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf"); } [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] @@ -1874,6 +1880,22 @@ namespace Avalonia.Win32.Interop [MarshalAs(UnmanagedType.LPWStr)] public string pszSpec; } + + public delegate void MarkFullscreenWindow(IntPtr This, IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fullscreen); + public delegate HRESULT HrInit(IntPtr This); + + public struct ITaskBarList2VTable + { + public IntPtr IUnknown1; + public IntPtr IUnknown2; + public IntPtr IUnknown3; + public IntPtr HrInit; + public IntPtr AddTab; + public IntPtr DeleteTab; + public IntPtr ActivateTab; + public IntPtr SetActiveAlt; + public IntPtr MarkFullscreenWindow; + } } [Flags] diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 963042b249..442794f0f0 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Win32 { public class ScreenImpl : IScreenImpl { - public int ScreenCount + public int ScreenCount { get => GetSystemMetrics(SystemMetric.SM_CMONITORS); } @@ -33,7 +33,7 @@ namespace Avalonia.Win32 var shcore = LoadLibrary("shcore.dll"); var method = GetProcAddress(shcore, nameof(GetDpiForMonitor)); if (method != IntPtr.Zero) - { + { GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _); dpi = (double)x; } @@ -51,11 +51,8 @@ namespace Avalonia.Win32 RECT bounds = monitorInfo.rcMonitor; RECT workingArea = monitorInfo.rcWork; - PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left, - bounds.bottom - bounds.top); - PixelRect avaloniaWorkArea = - new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, - workingArea.bottom - workingArea.top); + PixelRect avaloniaBounds = bounds.ToPixelRect(); + PixelRect avaloniaWorkArea = workingArea.ToPixelRect(); screens[index] = new WinScreen(dpi / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, monitor); diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index 8bdd4b7bfa..c6164e0868 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32 Guid clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; Guid iid = UnmanagedMethods.ShellIds.IFileDialog; - UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk); + UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk); var frm = (UnmanagedMethods.IFileDialog)unk; var openDialog = dialog as OpenFileDialog; @@ -105,9 +105,9 @@ namespace Avalonia.Win32 var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; - Guid iid = UnmanagedMethods.ShellIds.IFileDialog; + Guid iid = UnmanagedMethods.ShellIds.IFileDialog; - UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk); + UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk); var frm = (UnmanagedMethods.IFileDialog)unk; uint options; frm.GetOptions(out options); diff --git a/src/Windows/Avalonia.Win32/Win32TypeExtensions.cs b/src/Windows/Avalonia.Win32/Win32TypeExtensions.cs new file mode 100644 index 0000000000..8193611f6d --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32TypeExtensions.cs @@ -0,0 +1,13 @@ +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32 +{ + internal static class Win32TypeExtensions + { + public static PixelRect ToPixelRect(this RECT rect) + { + return new PixelRect(rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top); + } + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e193c72ef7..d82e9f64f1 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -37,10 +37,14 @@ namespace Avalonia.Win32 { WindowEdge.West, HitTestValues.HTLEFT } }; + private SavedWindowInfo _savedWindowInfo; + private bool _isFullScreenActive; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif + private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE); private readonly List _disabledBy; private readonly TouchDevice _touchDevice; private readonly MouseDevice _mouseDevice; @@ -82,7 +86,9 @@ namespace Avalonia.Win32 _windowProperties = new WindowProperties { - ShowInTaskbar = false, IsResizable = true, Decorations = SystemDecorations.Full + ShowInTaskbar = false, + IsResizable = true, + Decorations = SystemDecorations.Full }; _rendererLock = new ManagedDeferredRendererLock(); @@ -538,27 +544,98 @@ namespace Avalonia.Win32 } } + /// + /// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc + /// Method must only be called from inside UpdateWindowProperties. + /// + /// + private void SetFullScreen(bool fullscreen) + { + if (fullscreen) + { + GetWindowRect(_hwnd, out var windowRect); + _savedWindowInfo.WindowRect = windowRect; + + var current = GetStyle(); + var currentEx = GetExtendedStyle(); + + _savedWindowInfo.Style = current; + _savedWindowInfo.ExStyle = currentEx; + + // Set new window style and size. + SetStyle(current & ~(WindowStyles.WS_CAPTION | WindowStyles.WS_THICKFRAME), false); + SetExtendedStyle(currentEx & ~(WindowStyles.WS_EX_DLGMODALFRAME | WindowStyles.WS_EX_WINDOWEDGE | WindowStyles.WS_EX_CLIENTEDGE | WindowStyles.WS_EX_STATICEDGE), false); + + // On expand, if we're given a window_rect, grow to it, otherwise do + // not resize. + MONITORINFO monitor_info = MONITORINFO.Create(); + GetMonitorInfo(MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST), ref monitor_info); + + var window_rect = monitor_info.rcMonitor.ToPixelRect(); + + SetWindowPos(_hwnd, IntPtr.Zero, window_rect.X, window_rect.Y, + window_rect.Width, window_rect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + + _isFullScreenActive = true; + } + else + { + // Reset original window style and size. The multiple window size/moves + // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be + // repainted. Better-looking methods welcome. + _isFullScreenActive = false; + + var windowStates = GetWindowStateStyles(); + SetStyle((_savedWindowInfo.Style & ~WindowStateMask) | windowStates, false); + SetExtendedStyle(_savedWindowInfo.ExStyle, false); + + // On restore, resize to the previous saved rect size. + var new_rect = _savedWindowInfo.WindowRect.ToPixelRect(); + + SetWindowPos(_hwnd, IntPtr.Zero, new_rect.X, new_rect.Y, new_rect.Width, + new_rect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + + UpdateWindowProperties(_windowProperties, true); + } + + TaskBarList.MarkFullscreen(_hwnd, fullscreen); + } + private void ShowWindow(WindowState state) { ShowWindowCommand command; + var newWindowProperties = _windowProperties; + switch (state) { case WindowState.Minimized: + newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Minimize; break; case WindowState.Maximized: + newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Maximize; break; case WindowState.Normal: + newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Restore; break; + case WindowState.FullScreen: + newWindowProperties.IsFullScreen = true; + UpdateWindowProperties(newWindowProperties); + return; + default: throw new ArgumentException("Invalid WindowState."); } + UpdateWindowProperties(newWindowProperties); + UnmanagedMethods.ShowWindow(_hwnd, command); if (state == WindowState.Maximized) @@ -590,22 +667,69 @@ namespace Avalonia.Win32 SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); } } + } + + private WindowStyles GetWindowStateStyles () + { + return GetStyle() & WindowStateMask; } - private WindowStyles GetStyle() => (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + private WindowStyles GetStyle() + { + if (_isFullScreenActive) + { + return _savedWindowInfo.Style; + } + else + { + return (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + } + } - private WindowStyles GetExtendedStyle() => (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE); + private WindowStyles GetExtendedStyle() + { + if (_isFullScreenActive) + { + return _savedWindowInfo.ExStyle; + } + else + { + return (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE); + } + } - private void SetStyle(WindowStyles style) => SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); + private void SetStyle(WindowStyles style, bool save = true) + { + if (save) + { + _savedWindowInfo.Style = style; + } - private void SetExtendedStyle(WindowStyles style) => SetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE, (uint)style); + if (!_isFullScreenActive) + { + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); + } + } + + private void SetExtendedStyle(WindowStyles style, bool save = true) + { + if (save) + { + _savedWindowInfo.ExStyle = style; + } + + if (!_isFullScreenActive) + { + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE, (uint)style); + } + } private void UpdateEnabled() { EnableWindow(_hwnd, _disabledBy.Count == 0); } - private void UpdateWindowProperties(WindowProperties newProperties) + private void UpdateWindowProperties(WindowProperties newProperties, bool forceChanges = false) { var oldProperties = _windowProperties; @@ -613,7 +737,7 @@ namespace Avalonia.Win32 // according to the new values already. _windowProperties = newProperties; - if (oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) + if ((oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) || forceChanges) { var exStyle = GetExtendedStyle(); @@ -632,7 +756,7 @@ namespace Avalonia.Win32 // Otherwise it will still show in the taskbar. } - if (oldProperties.IsResizable != newProperties.IsResizable) + if ((oldProperties.IsResizable != newProperties.IsResizable) || forceChanges) { var style = GetStyle(); @@ -648,7 +772,12 @@ namespace Avalonia.Win32 SetStyle(style); } - if (oldProperties.Decorations != newProperties.Decorations) + if (oldProperties.IsFullScreen != newProperties.IsFullScreen) + { + SetFullScreen(newProperties.IsFullScreen); + } + + if ((oldProperties.Decorations != newProperties.Decorations) || forceChanges) { var style = GetStyle(); @@ -663,30 +792,33 @@ namespace Avalonia.Win32 style &= ~fullDecorationFlags; } - var margins = new MARGINS + SetStyle(style); + + if (!_isFullScreenActive) { - cyBottomHeight = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0 - }; + var margins = new MARGINS + { + cyBottomHeight = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0 + }; - DwmExtendFrameIntoClientArea(_hwnd, ref margins); + DwmExtendFrameIntoClientArea(_hwnd, ref margins); - GetClientRect(_hwnd, out var oldClientRect); - var oldClientRectOrigin = new POINT(); - ClientToScreen(_hwnd, ref oldClientRectOrigin); - oldClientRect.Offset(oldClientRectOrigin); + GetClientRect(_hwnd, out var oldClientRect); + var oldClientRectOrigin = new POINT(); + ClientToScreen(_hwnd, ref oldClientRectOrigin); + oldClientRect.Offset(oldClientRectOrigin); - SetStyle(style); + var newRect = oldClientRect; - var newRect = oldClientRect; + if (newProperties.Decorations == SystemDecorations.Full) + { + AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle()); + } - if (newProperties.Decorations == SystemDecorations.Full) - { - AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle()); + SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | + SetWindowPosFlags.SWP_FRAMECHANGED); } - - SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, - SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | - SetWindowPosFlags.SWP_FRAMECHANGED); } } @@ -713,11 +845,19 @@ namespace Avalonia.Win32 IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + private struct SavedWindowInfo + { + public WindowStyles Style { get; set; } + public WindowStyles ExStyle { get; set; } + public RECT WindowRect { get; set; } + }; + private struct WindowProperties { public bool ShowInTaskbar; public bool IsResizable; public SystemDecorations Decorations; + public bool IsFullScreen; } } }