Browse Source

Merge branch 'master' into UpdateSkia

pull/3896/head
Benedikt Stebner 6 years ago
committed by GitHub
parent
commit
ee68185be1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      native/Avalonia.Native/inc/avalonia-native.h
  2. 4
      native/Avalonia.Native/src/OSX/window.h
  3. 309
      native/Avalonia.Native/src/OSX/window.mm
  4. 5
      samples/ControlCatalog/MainView.xaml
  5. 2
      samples/ControlCatalog/MainWindow.xaml
  6. 27
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  7. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  8. 5
      src/Avalonia.Controls/WindowState.cs
  9. 2
      src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs
  10. 2
      src/Avalonia.Native/WindowImpl.cs
  11. 148
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  12. 14
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  13. 1
      src/Avalonia.X11/X11Atoms.cs
  14. 32
      src/Avalonia.X11/X11Window.cs
  15. 10
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  16. 9
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  17. 2
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  18. 55
      src/Windows/Avalonia.Win32/Interop/TaskBarList.cs
  19. 24
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  20. 11
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  21. 6
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  22. 13
      src/Windows/Avalonia.Win32/Win32TypeExtensions.cs
  23. 192
      src/Windows/Avalonia.Win32/WindowImpl.cs

3
native/Avalonia.Native/inc/avalonia-native.h

@ -135,6 +135,7 @@ enum AvnWindowState
Normal, Normal,
Minimized, Minimized,
Maximized, Maximized,
FullScreen,
}; };
enum AvnStandardCursorType enum AvnStandardCursorType
@ -246,7 +247,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
{ {
virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
virtual HRESULT SetCanResize(bool value) = 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 SetTitle (void* utf8Title) = 0;
virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0;

4
native/Avalonia.Native/src/OSX/window.h

@ -35,6 +35,10 @@ struct INSWindowHolder
struct IWindowStateChanged struct IWindowStateChanged
{ {
virtual void WindowStateChanged () = 0; virtual void WindowStateChanged () = 0;
virtual void StartStateTransition () = 0;
virtual void EndStateTransition () = 0;
virtual SystemDecorations Decorations () = 0;
virtual AvnWindowState WindowState () = 0;
}; };
#endif /* window_h */ #endif /* window_h */

309
native/Avalonia.Native/src/OSX/window.mm

@ -391,7 +391,7 @@ protected:
void UpdateStyle() void UpdateStyle()
{ {
[Window setStyleMask:GetStyle()]; [Window setStyleMask: GetStyle()];
} }
public: public:
@ -404,10 +404,13 @@ public:
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{ {
private: private:
bool _canResize = true; bool _canResize;
SystemDecorations _hasDecorations = SystemDecorationsFull; bool _fullScreenActive;
CGRect _lastUndecoratedFrame; SystemDecorations _decorations;
AvnWindowState _lastWindowState; AvnWindowState _lastWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP() BEGIN_INTERFACE_MAP()
@ -421,6 +424,11 @@ private:
ComPtr<IAvnWindowEvents> WindowEvents; ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{ {
_fullScreenActive = false;
_canResize = true;
_decorations = SystemDecorationsFull;
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal; _lastWindowState = Normal;
WindowEvents = events; WindowEvents = events;
[Window setCanBecomeKeyAndMain]; [Window setCanBecomeKeyAndMain];
@ -428,6 +436,20 @@ private:
[Window setTabbingMode:NSWindowTabbingModeDisallowed]; [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 virtual HRESULT Show () override
{ {
@autoreleasepool @autoreleasepool
@ -439,6 +461,8 @@ private:
WindowBaseImpl::Show(); WindowBaseImpl::Show();
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState); return SetWindowState(_lastWindowState);
} }
} }
@ -459,41 +483,69 @@ private:
[cparent->Window addChildWindow:Window ordered:NSWindowAbove]; [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
WindowBaseImpl::Show(); WindowBaseImpl::Show();
HideOrShowTrafficLights();
return S_OK; 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 void WindowStateChanged () override
{ {
AvnWindowState state; if(!_inSetWindowState && !_transitioningWindowState)
GetWindowState(&state); {
WindowEvents->WindowStateChanged(state); AvnWindowState state;
GetWindowState(&state);
if(_lastWindowState != state)
{
_lastWindowState = state;
WindowEvents->WindowStateChanged(state);
}
}
} }
bool UndecoratedIsMaximized () bool UndecoratedIsMaximized ()
{ {
return CGRectEqualToRect([Window frame], [Window screen].visibleFrame); auto windowSize = [Window frame];
auto available = [Window screen].visibleFrame;
return CGRectEqualToRect(windowSize, available);
} }
bool IsZoomed () bool IsZoomed ()
{ {
return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized(); return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
} }
void DoZoom() void DoZoom()
{ {
switch (_hasDecorations) switch (_decorations)
{ {
case SystemDecorationsNone: case SystemDecorationsNone:
if (!UndecoratedIsMaximized()) case SystemDecorationsBorderOnly:
{ [Window setFrame:[Window screen].visibleFrame display:true];
_lastUndecoratedFrame = [Window frame];
}
[Window zoom:Window];
break; break;
case SystemDecorationsBorderOnly:
case SystemDecorationsFull: case SystemDecorationsFull:
[Window performZoom:Window]; [Window performZoom:Window];
break; break;
@ -510,25 +562,52 @@ private:
} }
} }
virtual HRESULT SetHasDecorations(SystemDecorations value) override virtual HRESULT SetDecorations(SystemDecorations value) override
{ {
@autoreleasepool @autoreleasepool
{ {
_hasDecorations = value; auto currentWindowState = _lastWindowState;
_decorations = value;
if(_fullScreenActive)
{
return S_OK;
}
auto currentFrame = [Window frame];
UpdateStyle(); UpdateStyle();
HideOrShowTrafficLights();
switch (_hasDecorations) switch (_decorations)
{ {
case SystemDecorationsNone: case SystemDecorationsNone:
[Window setHasShadow:NO]; [Window setHasShadow:NO];
[Window setTitleVisibility:NSWindowTitleHidden]; [Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES]; [Window setTitlebarAppearsTransparent:YES];
if(currentWindowState == Maximized)
{
if(!UndecoratedIsMaximized())
{
DoZoom();
}
}
break; break;
case SystemDecorationsBorderOnly: case SystemDecorationsBorderOnly:
[Window setHasShadow:YES]; [Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleHidden]; [Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES]; [Window setTitlebarAppearsTransparent:YES];
if(currentWindowState == Maximized)
{
if(!UndecoratedIsMaximized())
{
DoZoom();
}
}
break; break;
case SystemDecorationsFull: case SystemDecorationsFull:
@ -536,6 +615,13 @@ private:
[Window setTitleVisibility:NSWindowTitleVisible]; [Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO]; [Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
if(currentWindowState == Maximized)
{
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
break; break;
} }
@ -592,13 +678,19 @@ private:
return E_POINTER; return E_POINTER;
} }
if(([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen)
{
*ret = FullScreen;
return S_OK;
}
if([Window isMiniaturized]) if([Window isMiniaturized])
{ {
*ret = Minimized; *ret = Minimized;
return S_OK; return S_OK;
} }
if([Window isZoomed]) if(IsZoomed())
{ {
*ret = Maximized; *ret = Maximized;
return S_OK; 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 virtual HRESULT SetWindowState (AvnWindowState state) override
{ {
@autoreleasepool @autoreleasepool
{ {
if(_lastWindowState == state)
{
return S_OK;
}
_inSetWindowState = true;
auto currentState = _lastWindowState;
_lastWindowState = state; _lastWindowState = state;
if(currentState == Normal)
{
_preZoomSize = [Window frame];
}
if(_shown) if(_shown)
{ {
switch (state) { switch (state) {
case Maximized: case Maximized:
if(currentState == FullScreen)
{
ExitFullScreenMode();
}
lastPositionSet.X = 0; lastPositionSet.X = 0;
lastPositionSet.Y = 0; lastPositionSet.Y = 0;
@ -635,40 +768,66 @@ private:
break; break;
case Minimized: case Minimized:
[Window miniaturize:Window]; if(currentState == FullScreen)
{
ExitFullScreenMode();
}
else
{
[Window miniaturize:Window];
}
break;
case FullScreen:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
EnterFullScreenMode();
break; break;
default: case Normal:
if([Window isMiniaturized]) if([Window isMiniaturized])
{ {
[Window deminiaturize:Window]; [Window deminiaturize:Window];
} }
if(currentState == FullScreen)
{
ExitFullScreenMode();
}
if(IsZoomed()) if(IsZoomed())
{ {
DoZoom(); if(_decorations == SystemDecorationsFull)
{
DoZoom();
}
else
{
[Window setFrame:_preZoomSize display:true];
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
} }
break; break;
} }
} }
_inSetWindowState = false;
return S_OK; return S_OK;
} }
} }
virtual void OnResized () override virtual void OnResized () override
{ {
if(_shown) if(_shown && !_inSetWindowState && !_transitioningWindowState)
{ {
auto windowState = [Window isMiniaturized] ? Minimized WindowStateChanged();
: (IsZoomed() ? Maximized : Normal);
if (windowState != _lastWindowState)
{
_lastWindowState = windowState;
WindowEvents->WindowStateChanged(windowState);
}
} }
} }
@ -677,22 +836,23 @@ protected:
{ {
unsigned long s = NSWindowStyleMaskBorderless; unsigned long s = NSWindowStyleMaskBorderless;
switch (_hasDecorations) switch (_decorations)
{ {
case SystemDecorationsNone: case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
break; break;
case SystemDecorationsBorderOnly: case SystemDecorationsBorderOnly:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
break; break;
case SystemDecorationsFull: case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless; s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless;
if(_canResize) if(_canResize)
{ {
s = s | NSWindowStyleMaskResizable; s = s | NSWindowStyleMaskResizable;
} }
break; 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 - (void)pollModalSession:(nonnull NSModalSession)session
{ {
auto response = [NSApp runModalSession:session]; auto response = [NSApp runModalSession:session];
@ -1399,7 +1573,66 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)windowDidResize:(NSNotification *)notification - (void)windowDidResize:(NSNotification *)notification
{ {
_parent->OnResized(); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowWillExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_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<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
parent->WindowStateChanged();
}
} }
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame

5
samples/ControlCatalog/MainView.xaml

@ -59,8 +59,8 @@
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem> <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem> <TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag> <TabControl.Tag>
<StackPanel Width="115" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom"> <StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
<ComboBox x:Name="Decorations" SelectedIndex="0" Margin="0,0,0,8"> <ComboBox x:Name="Decorations" SelectedIndex="0">
<ComboBoxItem>No Decorations</ComboBoxItem> <ComboBoxItem>No Decorations</ComboBoxItem>
<ComboBoxItem>Border Only</ComboBoxItem> <ComboBoxItem>Border Only</ComboBoxItem>
<ComboBoxItem>Full Decorations</ComboBoxItem> <ComboBoxItem>Full Decorations</ComboBoxItem>
@ -69,6 +69,7 @@
<ComboBoxItem>Light</ComboBoxItem> <ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem> <ComboBoxItem>Dark</ComboBoxItem>
</ComboBox> </ComboBox>
<ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
</StackPanel> </StackPanel>
</TabControl.Tag> </TabControl.Tag>
</TabControl> </TabControl>

2
samples/ControlCatalog/MainWindow.xaml

@ -7,7 +7,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views" xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow"> x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
<NativeMenu.Menu> <NativeMenu.Menu>
<NativeMenu> <NativeMenu>

27
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -1,4 +1,5 @@
using System.Reactive; using System.Reactive;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Dialogs; using Avalonia.Dialogs;
@ -11,6 +12,8 @@ namespace ControlCatalog.ViewModels
private IManagedNotificationManager _notificationManager; private IManagedNotificationManager _notificationManager;
private bool _isMenuItemChecked = true; private bool _isMenuItemChecked = true;
private WindowState _windowState;
private WindowState[] _windowStates;
public MainWindowViewModel(IManagedNotificationManager notificationManager) public MainWindowViewModel(IManagedNotificationManager notificationManager)
{ {
@ -45,10 +48,32 @@ namespace ControlCatalog.ViewModels
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
}); });
ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() => ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() =>
{ {
IsMenuItemChecked = !IsMenuItemChecked; 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 public IManagedNotificationManager NotificationManager

2
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -392,7 +392,7 @@ namespace Avalonia.Controls.Platform
{ {
var control = e.Source as ILogical; var control = e.Source as ILogical;
if (!Menu.IsLogicalParentOf(control)) if (!Menu.IsLogicalAncestorOf(control))
{ {
Menu.Close(); Menu.Close();
} }

5
src/Avalonia.Controls/WindowState.cs

@ -19,5 +19,10 @@ namespace Avalonia.Controls
/// The window is maximized. /// The window is maximized.
/// </summary> /// </summary>
Maximized, Maximized,
/// <summary>
/// The window is fullscreen.
/// </summary>
FullScreen,
} }
} }

2
src/Avalonia.Dialogs/ManagedFileChooser.xaml.cs

@ -34,7 +34,7 @@ namespace Avalonia.Dialogs
return; return;
} }
var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control);
if (e.ClickCount == 2 || isQuickLink) if (e.ClickCount == 2 || isQuickLink)
{ {
if (model.ItemType == ManagedFileChooserItemType.File) if (model.ItemType == ManagedFileChooserItemType.File)

2
src/Avalonia.Native/WindowImpl.cs

@ -67,7 +67,7 @@ namespace Avalonia.Native
public void SetSystemDecorations(Controls.SystemDecorations enabled) public void SetSystemDecorations(Controls.SystemDecorations enabled)
{ {
_native.HasDecorations = (Interop.SystemDecorations)enabled; _native.Decorations = (Interop.SystemDecorations)enabled;
} }
public void SetTitleBarColor (Avalonia.Media.Color color) public void SetTitleBarColor (Avalonia.Media.Color color)

148
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@ -1,11 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Avalonia.LogicalTree namespace Avalonia.LogicalTree
{ {
/// <summary>
/// Provides extension methods for working with the logical tree.
/// </summary>
public static class LogicalExtensions public static class LogicalExtensions
{ {
/// <summary>
/// Enumerates the ancestors of an <see cref="ILogical"/> in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical's ancestors.</returns>
public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical) public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical)
{ {
Contract.Requires<ArgumentNullException>(logical != null); Contract.Requires<ArgumentNullException>(logical != null);
@ -19,6 +26,11 @@ namespace Avalonia.LogicalTree
} }
} }
/// <summary>
/// Enumerates an <see cref="ILogical"/> and its ancestors in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical and its ancestors.</returns>
public static IEnumerable<ILogical> GetSelfAndLogicalAncestors(this ILogical logical) public static IEnumerable<ILogical> GetSelfAndLogicalAncestors(this ILogical logical)
{ {
yield return logical; yield return logical;
@ -29,11 +41,50 @@ namespace Avalonia.LogicalTree
} }
} }
/// <summary>
/// Finds first ancestor of given type.
/// </summary>
/// <typeparam name="T">Ancestor type.</typeparam>
/// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First ancestor of given type.</returns>
public static T FindLogicalAncestorOfType<T>(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;
}
/// <summary>
/// Enumerates the children of an <see cref="ILogical"/> in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical children.</returns>
public static IEnumerable<ILogical> GetLogicalChildren(this ILogical logical) public static IEnumerable<ILogical> GetLogicalChildren(this ILogical logical)
{ {
return logical.LogicalChildren; return logical.LogicalChildren;
} }
/// <summary>
/// Enumerates the descendants of an <see cref="ILogical"/> in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical's ancestors.</returns>
public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical) public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical)
{ {
foreach (ILogical child in logical.LogicalChildren) foreach (ILogical child in logical.LogicalChildren)
@ -47,6 +98,11 @@ namespace Avalonia.LogicalTree
} }
} }
/// <summary>
/// Enumerates an <see cref="ILogical"/> and its descendants in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical and its ancestors.</returns>
public static IEnumerable<ILogical> GetSelfAndLogicalDescendants(this ILogical logical) public static IEnumerable<ILogical> GetSelfAndLogicalDescendants(this ILogical logical)
{ {
yield return logical; yield return logical;
@ -57,16 +113,56 @@ namespace Avalonia.LogicalTree
} }
} }
/// <summary>
/// Finds first descendant of given type.
/// </summary>
/// <typeparam name="T">Descendant type.</typeparam>
/// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First descendant of given type.</returns>
public static T FindLogicalDescendantOfType<T>(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<T>(logical);
}
/// <summary>
/// Gets the logical parent of an <see cref="ILogical"/>.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The parent, or null if the logical is unparented.</returns>
public static ILogical GetLogicalParent(this ILogical logical) public static ILogical GetLogicalParent(this ILogical logical)
{ {
return logical.LogicalParent; return logical.LogicalParent;
} }
/// <summary>
/// Gets the logical parent of an <see cref="ILogical"/>.
/// </summary>
/// <typeparam name="T">The type of the logical parent.</typeparam>
/// <param name="logical">The logical.</param>
/// <returns>
/// The parent, or null if the logical is unparented or its parent is not of type <typeparamref name="T"/>.
/// </returns>
public static T GetLogicalParent<T>(this ILogical logical) where T : class public static T GetLogicalParent<T>(this ILogical logical) where T : class
{ {
return logical.LogicalParent as T; return logical.LogicalParent as T;
} }
/// <summary>
/// Enumerates the siblings of an <see cref="ILogical"/> in the logical tree.
/// </summary>
/// <param name="logical">The logical.</param>
/// <returns>The logical siblings.</returns>
public static IEnumerable<ILogical> GetLogicalSiblings(this ILogical logical) public static IEnumerable<ILogical> GetLogicalSiblings(this ILogical logical)
{ {
ILogical parent = logical.LogicalParent; ILogical parent = logical.LogicalParent;
@ -80,9 +176,55 @@ namespace Avalonia.LogicalTree
} }
} }
public static bool IsLogicalParentOf(this ILogical logical, ILogical target) /// <summary>
/// Tests whether an <see cref="ILogical"/> is an ancestor of another logical.
/// </summary>
/// <param name="logical">The logical.</param>
/// <param name="target">The potential descendant.</param>
/// <returns>
/// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>;
/// otherwise false.
/// </returns>
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<T>(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<T>(child);
if (!(childResult is null))
{
return childResult;
}
}
return null;
} }
} }
} }

14
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -377,7 +377,19 @@ namespace Avalonia.VisualTree
/// </returns> /// </returns>
public static bool IsVisualAncestorOf(this IVisual visual, IVisual target) 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<IVisual> SortByZIndex(this IEnumerable<IVisual> elements) public static IEnumerable<IVisual> SortByZIndex(this IEnumerable<IVisual> elements)

1
src/Avalonia.X11/X11Atoms.cs

@ -156,6 +156,7 @@ namespace Avalonia.X11
public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE;
public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ; public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ;
public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT; public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT;
public readonly IntPtr _NET_WM_STATE_FULLSCREEN;
public readonly IntPtr _XEMBED; public readonly IntPtr _XEMBED;
public readonly IntPtr _XEMBED_INFO; public readonly IntPtr _XEMBED_INFO;
public readonly IntPtr _MOTIF_WM_HINTS; public readonly IntPtr _MOTIF_WM_HINTS;

32
src/Avalonia.X11/X11Window.cs

@ -220,16 +220,11 @@ namespace Avalonia.X11
var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border | var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border |
MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH; MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH;
if (_popup || _systemDecorations == SystemDecorations.None) if (_popup
{ || _systemDecorations == SystemDecorations.None)
decorations = 0; decorations = 0;
}
else if (_systemDecorations == SystemDecorations.BorderOnly)
{
decorations = MotifDecorations.Border;
}
if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) if (!_canResize)
{ {
functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize);
decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH);
@ -252,7 +247,7 @@ namespace Avalonia.X11
var min = _minMaxSize.minSize; var min = _minMaxSize.minSize;
var max = _minMaxSize.maxSize; var max = _minMaxSize.maxSize;
if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly) if (!_canResize)
max = min = _realSize; max = min = _realSize;
if (preResize.HasValue) if (preResize.HasValue)
@ -552,12 +547,21 @@ namespace Avalonia.X11
else if (value == WindowState.Maximized) else if (value == WindowState.Maximized)
{ {
ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); 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, ChangeWMAtoms(true, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); _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 else
{ {
ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); 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, ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
} }
@ -585,6 +589,12 @@ namespace Avalonia.X11
break; break;
} }
if(pitems[c] == _x11.Atoms._NET_WM_STATE_FULLSCREEN)
{
state = WindowState.FullScreen;
break;
}
if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ || if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ ||
pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT) pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT)
{ {
@ -810,7 +820,7 @@ namespace Avalonia.X11
public void SetSystemDecorations(SystemDecorations enabled) public void SetSystemDecorations(SystemDecorations enabled)
{ {
_systemDecorations = enabled; _systemDecorations = enabled == SystemDecorations.Full ? SystemDecorations.Full : SystemDecorations.None;
UpdateMotifHints(); UpdateMotifHints();
UpdateSizeHints(null); UpdateSizeHints(null);
} }
@ -1052,7 +1062,7 @@ namespace Avalonia.X11
void ChangeWMAtoms(bool enable, params IntPtr[] atoms) void ChangeWMAtoms(bool enable, params IntPtr[] atoms)
{ {
if (atoms.Length < 1 || atoms.Length > 4) if (atoms.Length != 1 && atoms.Length != 2)
throw new ArgumentException(); throw new ArgumentException();
if (!_mapped) if (!_mapped)

10
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -18,7 +18,7 @@ namespace Avalonia.Skia
private GRContext GrContext { get; } private GRContext GrContext { get; }
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu) public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu, long maxResourceBytes = 100663296)
{ {
if (customSkiaGpu != null) if (customSkiaGpu != null)
{ {
@ -26,6 +26,10 @@ namespace Avalonia.Skia
GrContext = _customSkiaGpu.GrContext; GrContext = _customSkiaGpu.GrContext;
GrContext.GetResourceCacheLimits(out var maxResources, out _);
GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes);
return; return;
} }
@ -39,6 +43,10 @@ namespace Avalonia.Skia
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc))) : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{ {
GrContext = GRContext.Create(GRBackend.OpenGL, iface); GrContext = GRContext.Create(GRBackend.OpenGL, iface);
GrContext.GetResourceCacheLimits(out var maxResources, out _);
GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes);
} }
display.ClearContext(); display.ClearContext();
} }

9
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -8,9 +8,18 @@ namespace Avalonia
/// </summary> /// </summary>
public class SkiaOptions public class SkiaOptions
{ {
public SkiaOptions()
{
MaxGpuResourceSizeBytes = 100663296; // Value taken from skia.
}
/// <summary> /// <summary>
/// Custom gpu factory to use. Can be used to customize behavior of Skia renderer. /// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
/// </summary> /// </summary>
public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; } public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; }
/// <summary>
/// The maximum number of bytes for video memory to store textures and resources.
/// </summary>
public long MaxGpuResourceSizeBytes { get; set; }
} }
} }

2
src/Skia/Avalonia.Skia/SkiaPlatform.cs

@ -18,7 +18,7 @@ namespace Avalonia.Skia
public static void Initialize(SkiaOptions options) public static void Initialize(SkiaOptions options)
{ {
var customGpu = options.CustomGpuFactory?.Invoke(); var customGpu = options.CustomGpuFactory?.Invoke();
var renderInterface = new PlatformRenderInterface(customGpu); var renderInterface = new PlatformRenderInterface(customGpu, options.MaxGpuResourceSizeBytes);
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface) .Bind<IPlatformRenderInterface>().ToConstant(renderInterface)

55
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;
/// <summary>
/// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc
/// </summary>
/// <param name="fullscreen">Fullscreen state.</param>
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<HrInit>((*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<MarkFullscreenWindow>((*ptr)->MarkFullscreenWindow);
}
s_markFullscreenWindowDelegate(s_taskBarList, hwnd, fullscreen);
}
}
}
}

24
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -460,6 +460,7 @@ namespace Avalonia.Win32.Interop
WS_SIZEFRAME = 0x40000, WS_SIZEFRAME = 0x40000,
WS_SYSMENU = 0x80000, WS_SYSMENU = 0x80000,
WS_TABSTOP = 0x10000, WS_TABSTOP = 0x10000,
WS_THICKFRAME = 0x40000,
WS_VISIBLE = 0x10000000, WS_VISIBLE = 0x10000000,
WS_VSCROLL = 0x200000, WS_VSCROLL = 0x200000,
WS_EX_DLGMODALFRAME = 0x00000001, WS_EX_DLGMODALFRAME = 0x00000001,
@ -1146,7 +1147,10 @@ namespace Avalonia.Win32.Interop
internal static extern int CoCreateInstance(ref Guid clsid, internal static extern int CoCreateInstance(ref Guid clsid,
IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter); 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)] [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); 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 SaveFileDialog = Guid.Parse("C0B4E2F3-BA21-4773-8DBA-335EC946EB8B");
public static readonly Guid IFileDialog = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8"); 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 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)] [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
@ -1874,6 +1880,22 @@ namespace Avalonia.Win32.Interop
[MarshalAs(UnmanagedType.LPWStr)] [MarshalAs(UnmanagedType.LPWStr)]
public string pszSpec; 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] [Flags]

11
src/Windows/Avalonia.Win32/ScreenImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Win32
{ {
public class ScreenImpl : IScreenImpl public class ScreenImpl : IScreenImpl
{ {
public int ScreenCount public int ScreenCount
{ {
get => GetSystemMetrics(SystemMetric.SM_CMONITORS); get => GetSystemMetrics(SystemMetric.SM_CMONITORS);
} }
@ -33,7 +33,7 @@ namespace Avalonia.Win32
var shcore = LoadLibrary("shcore.dll"); var shcore = LoadLibrary("shcore.dll");
var method = GetProcAddress(shcore, nameof(GetDpiForMonitor)); var method = GetProcAddress(shcore, nameof(GetDpiForMonitor));
if (method != IntPtr.Zero) if (method != IntPtr.Zero)
{ {
GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _); GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _);
dpi = (double)x; dpi = (double)x;
} }
@ -51,11 +51,8 @@ namespace Avalonia.Win32
RECT bounds = monitorInfo.rcMonitor; RECT bounds = monitorInfo.rcMonitor;
RECT workingArea = monitorInfo.rcWork; RECT workingArea = monitorInfo.rcWork;
PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left, PixelRect avaloniaBounds = bounds.ToPixelRect();
bounds.bottom - bounds.top); PixelRect avaloniaWorkArea = workingArea.ToPixelRect();
PixelRect avaloniaWorkArea =
new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
workingArea.bottom - workingArea.top);
screens[index] = screens[index] =
new WinScreen(dpi / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, new WinScreen(dpi / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
monitor); monitor);

6
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 clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
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; var frm = (UnmanagedMethods.IFileDialog)unk;
var openDialog = dialog as OpenFileDialog; var openDialog = dialog as OpenFileDialog;
@ -105,9 +105,9 @@ namespace Avalonia.Win32
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; 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; var frm = (UnmanagedMethods.IFileDialog)unk;
uint options; uint options;
frm.GetOptions(out options); frm.GetOptions(out options);

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

192
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -37,10 +37,14 @@ namespace Avalonia.Win32
{ WindowEdge.West, HitTestValues.HTLEFT } { WindowEdge.West, HitTestValues.HTLEFT }
}; };
private SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive;
#if USE_MANAGED_DRAG #if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag; private readonly ManagedWindowResizeDragHelper _managedDrag;
#endif #endif
private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE);
private readonly List<WindowImpl> _disabledBy; private readonly List<WindowImpl> _disabledBy;
private readonly TouchDevice _touchDevice; private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice; private readonly MouseDevice _mouseDevice;
@ -82,7 +86,9 @@ namespace Avalonia.Win32
_windowProperties = new WindowProperties _windowProperties = new WindowProperties
{ {
ShowInTaskbar = false, IsResizable = true, Decorations = SystemDecorations.Full ShowInTaskbar = false,
IsResizable = true,
Decorations = SystemDecorations.Full
}; };
_rendererLock = new ManagedDeferredRendererLock(); _rendererLock = new ManagedDeferredRendererLock();
@ -538,27 +544,98 @@ namespace Avalonia.Win32
} }
} }
/// <summary>
/// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc
/// Method must only be called from inside UpdateWindowProperties.
/// </summary>
/// <param name="fullscreen"></param>
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) private void ShowWindow(WindowState state)
{ {
ShowWindowCommand command; ShowWindowCommand command;
var newWindowProperties = _windowProperties;
switch (state) switch (state)
{ {
case WindowState.Minimized: case WindowState.Minimized:
newWindowProperties.IsFullScreen = false;
command = ShowWindowCommand.Minimize; command = ShowWindowCommand.Minimize;
break; break;
case WindowState.Maximized: case WindowState.Maximized:
newWindowProperties.IsFullScreen = false;
command = ShowWindowCommand.Maximize; command = ShowWindowCommand.Maximize;
break; break;
case WindowState.Normal: case WindowState.Normal:
newWindowProperties.IsFullScreen = false;
command = ShowWindowCommand.Restore; command = ShowWindowCommand.Restore;
break; break;
case WindowState.FullScreen:
newWindowProperties.IsFullScreen = true;
UpdateWindowProperties(newWindowProperties);
return;
default: default:
throw new ArgumentException("Invalid WindowState."); throw new ArgumentException("Invalid WindowState.");
} }
UpdateWindowProperties(newWindowProperties);
UnmanagedMethods.ShowWindow(_hwnd, command); UnmanagedMethods.ShowWindow(_hwnd, command);
if (state == WindowState.Maximized) if (state == WindowState.Maximized)
@ -590,22 +667,69 @@ namespace Avalonia.Win32
SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); 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() private void UpdateEnabled()
{ {
EnableWindow(_hwnd, _disabledBy.Count == 0); EnableWindow(_hwnd, _disabledBy.Count == 0);
} }
private void UpdateWindowProperties(WindowProperties newProperties) private void UpdateWindowProperties(WindowProperties newProperties, bool forceChanges = false)
{ {
var oldProperties = _windowProperties; var oldProperties = _windowProperties;
@ -613,7 +737,7 @@ namespace Avalonia.Win32
// according to the new values already. // according to the new values already.
_windowProperties = newProperties; _windowProperties = newProperties;
if (oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) if ((oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) || forceChanges)
{ {
var exStyle = GetExtendedStyle(); var exStyle = GetExtendedStyle();
@ -632,7 +756,7 @@ namespace Avalonia.Win32
// Otherwise it will still show in the taskbar. // Otherwise it will still show in the taskbar.
} }
if (oldProperties.IsResizable != newProperties.IsResizable) if ((oldProperties.IsResizable != newProperties.IsResizable) || forceChanges)
{ {
var style = GetStyle(); var style = GetStyle();
@ -648,7 +772,12 @@ namespace Avalonia.Win32
SetStyle(style); SetStyle(style);
} }
if (oldProperties.Decorations != newProperties.Decorations) if (oldProperties.IsFullScreen != newProperties.IsFullScreen)
{
SetFullScreen(newProperties.IsFullScreen);
}
if ((oldProperties.Decorations != newProperties.Decorations) || forceChanges)
{ {
var style = GetStyle(); var style = GetStyle();
@ -663,30 +792,33 @@ namespace Avalonia.Win32
style &= ~fullDecorationFlags; 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); GetClientRect(_hwnd, out var oldClientRect);
var oldClientRectOrigin = new POINT(); var oldClientRectOrigin = new POINT();
ClientToScreen(_hwnd, ref oldClientRectOrigin); ClientToScreen(_hwnd, ref oldClientRectOrigin);
oldClientRect.Offset(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) SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height,
{ SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE |
AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle()); 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; 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 private struct WindowProperties
{ {
public bool ShowInTaskbar; public bool ShowInTaskbar;
public bool IsResizable; public bool IsResizable;
public SystemDecorations Decorations; public SystemDecorations Decorations;
public bool IsFullScreen;
} }
} }
} }

Loading…
Cancel
Save