Browse Source

Merge branch 'master' into feature/ui-automation

ui-automation-test
Steven Kirk 5 years ago
parent
commit
0388a7b920
  1. 1
      Documentation/build.md
  2. 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  3. 1
      native/Avalonia.Native/src/OSX/common.h
  4. 11
      native/Avalonia.Native/src/OSX/main.mm
  5. 33
      native/Avalonia.Native/src/OSX/trayicon.h
  6. 85
      native/Avalonia.Native/src/OSX/trayicon.mm
  7. 1
      native/Avalonia.Native/src/OSX/window.h
  8. 50
      native/Avalonia.Native/src/OSX/window.mm
  9. 25
      samples/ControlCatalog/App.xaml
  10. 9
      samples/ControlCatalog/App.xaml.cs
  11. 1
      samples/ControlCatalog/MainView.xaml
  12. 1
      samples/ControlCatalog/MainWindow.xaml
  13. 2
      samples/ControlCatalog/MainWindow.xaml.cs
  14. 10
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  15. 1
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  16. 26
      samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
  17. 5
      src/Avalonia.Base/Logging/LogArea.cs
  18. 1
      src/Avalonia.Base/Metadata/TemplateContent.cs
  19. 18
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  20. 2
      src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
  21. 29
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  22. 26
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  23. 4
      src/Avalonia.Controls/ApiCompatBaseline.txt
  24. 2
      src/Avalonia.Controls/Border.cs
  25. 7
      src/Avalonia.Controls/Button.cs
  26. 98
      src/Avalonia.Controls/ItemsSourceView.cs
  27. 13
      src/Avalonia.Controls/NativeMenu.Export.cs
  28. 2
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  29. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  30. 17
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  31. 35
      src/Avalonia.Controls/Platform/ITrayIconImpl.cs
  32. 5
      src/Avalonia.Controls/Platform/IWindowingPlatform.cs
  33. 16
      src/Avalonia.Controls/Platform/PlatformManager.cs
  34. 19
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  35. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  36. 4
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  37. 3
      src/Avalonia.Controls/ProgressBar.cs
  38. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  39. 6
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  40. 9
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  41. 20
      src/Avalonia.Controls/Templates/TemplateResult.cs
  42. 2
      src/Avalonia.Controls/TextBox.cs
  43. 187
      src/Avalonia.Controls/TrayIcon.cs
  44. 7
      src/Avalonia.Controls/WindowTransparencyLevel.cs
  45. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  46. 12
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  47. 8
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  48. 4
      src/Avalonia.Dialogs/ManagedFileChooser.cs
  49. 2
      src/Avalonia.Dialogs/ManagedFileChooserSources.cs
  50. 7
      src/Avalonia.FreeDesktop/DBusHelper.cs
  51. 75
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  52. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  53. 8
      src/Avalonia.Input/Gestures.cs
  54. 2
      src/Avalonia.Input/MouseDevice.cs
  55. 63
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  56. 5
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  57. 63
      src/Avalonia.Native/TrayIconImpl.cs
  58. 9
      src/Avalonia.Native/avn.idl
  59. 10
      src/Avalonia.Themes.Default/OverlayPopupHost.xaml
  60. 10
      src/Avalonia.Themes.Default/PopupRoot.xaml
  61. 6
      src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml
  62. 6
      src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml
  63. 8
      src/Avalonia.Visuals/Media/DrawingImage.cs
  64. 2
      src/Avalonia.Visuals/Media/FormattedText.cs
  65. 4
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  66. 7
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  67. 20
      src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
  68. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  69. 12
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  70. 14
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs
  71. 45
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  72. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  73. 2
      src/Avalonia.Visuals/Vector.cs
  74. 6
      src/Avalonia.X11/X11CursorFactory.cs
  75. 2
      src/Avalonia.X11/X11IconLoader.cs
  76. 2
      src/Avalonia.X11/X11Info.cs
  77. 13
      src/Avalonia.X11/X11Platform.cs
  78. 367
      src/Avalonia.X11/X11TrayIconImpl.cs
  79. 2
      src/Avalonia.X11/X11Window.cs
  80. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  81. 7
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  82. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  83. 12
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  84. 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  85. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  86. 2
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  87. 6
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  88. 10
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  89. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  90. 63
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  91. 265
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  92. 2
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  93. 54
      src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs
  94. 18
      src/Windows/Avalonia.Win32/Win32Platform.cs
  95. 15
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs
  96. 76
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  97. 10
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
  98. 11
      src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs
  99. 123
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  100. 11
      src/Windows/Avalonia.Win32/WindowImpl.cs

1
Documentation/build.md

@ -6,6 +6,7 @@ Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on
```
git clone https://github.com/AvaloniaUI/Avalonia.git
cd Avalonia
git submodule update --init
```

6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -22,6 +22,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 522D5958258159C1006F7F7A /* Carbon.framework */; };
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -53,6 +54,8 @@
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
522D5958258159C1006F7F7A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
523484C926EA688F00EA0C2C /* trayicon.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trayicon.mm; sourceTree = "<group>"; };
523484CB26EA68AA00EA0C2C /* trayicon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trayicon.h; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -120,6 +123,8 @@
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
523484C926EA688F00EA0C2C /* trayicon.mm */,
523484CB26EA68AA00EA0C2C /* trayicon.h */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
@ -211,6 +216,7 @@
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,

1
native/Avalonia.Native/src/OSX/common.h

@ -22,6 +22,7 @@ extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);

11
native/Avalonia.Native/src/OSX/main.mm

@ -304,6 +304,17 @@ public:
}
}
virtual HRESULT CreateTrayIcon (IAvnTrayIcon** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreateTrayIcon();
return S_OK;
}
}
virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{
START_COM_CALL;

33
native/Avalonia.Native/src/OSX/trayicon.h

@ -0,0 +1,33 @@
//
// trayicon.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 09/09/2021.
// Copyright © 2021 Avalonia. All rights reserved.
//
#ifndef trayicon_h
#define trayicon_h
#include "common.h"
class AvnTrayIcon : public ComSingleObject<IAvnTrayIcon, &IID_IAvnTrayIcon>
{
private:
NSStatusItem* _native;
public:
FORWARD_IUNKNOWN()
AvnTrayIcon();
~AvnTrayIcon ();
virtual HRESULT SetIcon (void* data, size_t length) override;
virtual HRESULT SetMenu (IAvnMenu* menu) override;
virtual HRESULT SetIsVisible (bool isVisible) override;
};
#endif /* trayicon_h */

85
native/Avalonia.Native/src/OSX/trayicon.mm

@ -0,0 +1,85 @@
#include "common.h"
#include "trayicon.h"
#include "menu.h"
extern IAvnTrayIcon* CreateTrayIcon()
{
@autoreleasepool
{
return new AvnTrayIcon();
}
}
AvnTrayIcon::AvnTrayIcon()
{
_native = [[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength];
}
AvnTrayIcon::~AvnTrayIcon()
{
if(_native != nullptr)
{
[[_native statusBar] removeStatusItem:_native];
_native = nullptr;
}
}
HRESULT AvnTrayIcon::SetIcon (void* data, size_t length)
{
START_COM_CALL;
@autoreleasepool
{
if(data != nullptr)
{
NSData *imageData = [NSData dataWithBytes:data length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];
NSSize originalSize = [image size];
NSSize size;
size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;
auto scaleFactor = size.height / originalSize.height;
size.width = originalSize.width * scaleFactor;
[image setSize: size];
[_native setImage:image];
}
else
{
[_native setImage:nullptr];
}
return S_OK;
}
}
HRESULT AvnTrayIcon::SetMenu (IAvnMenu* menu)
{
START_COM_CALL;
@autoreleasepool
{
auto appMenu = dynamic_cast<AvnAppMenu*>(menu);
if(appMenu != nullptr)
{
[_native setMenu:appMenu->GetNative()];
}
}
return S_OK;
}
HRESULT AvnTrayIcon::SetIsVisible(bool isVisible)
{
START_COM_CALL;
@autoreleasepool
{
[_native setVisible:isVisible];
}
return S_OK;
}

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

@ -12,6 +12,7 @@ class WindowBaseImpl;
-(AvnPixelSize) getPixelSize;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
@interface AutoFitContentView : NSView

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

@ -252,6 +252,8 @@ public:
virtual HRESULT GetFrameSize(AvnSize* ret) override
{
START_COM_CALL;
@autoreleasepool
{
if(ret == nullptr)
@ -1581,7 +1583,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return pt;
}
- (AvnPoint)toAvnPoint:(CGPoint)p
+ (AvnPoint)toAvnPoint:(CGPoint)p
{
AvnPoint result;
@ -1638,7 +1640,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta;
@ -1983,7 +1985,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
{
auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
@ -2419,6 +2421,48 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self frame].size.height - pt.Y;
return pt;
}
- (void)sendEvent:(NSEvent *)event
{
if(_parent != nullptr)
{
switch(event.type)
{
case NSEventTypeLeftMouseDown:
{
auto avnPoint = [AvnView toAvnPoint:[event locationInWindow]];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta;
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, [event timestamp] * 1000, AvnInputModifiersNone, point, delta);
}
break;
case NSEventTypeMouseEntered:
{
_parent->UpdateCursor();
}
break;
case NSEventTypeMouseExited:
{
[[NSCursor arrowCursor] set];
}
break;
default:
break;
}
}
[super sendEvent:event];
}
- (BOOL)isAccessibilityElement
{
[self getAutomationPeer];

25
samples/ControlCatalog/App.xaml

@ -1,5 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ControlCatalog.ViewModels"
x:DataType="vm:ApplicationViewModel"
x:CompileBindings="True"
x:Class="ControlCatalog.App">
<Application.Styles>
<Style Selector="TextBlock.h1">
@ -22,6 +25,26 @@
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<StyleInclude Source="/SideBar.xaml"/>
<StyleInclude Source="/SideBar.xaml" />
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="/Assets/test_icon.ico" ToolTipText="Avalonia Tray Icon ToolTip">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Settings">
<NativeMenu>
<NativeMenuItem Header="Option 1" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Header="Option 2" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItemSeparator />
<NativeMenuItem Header="Option 3" ToggleType="CheckBox" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Icon="/Assets/test_icon.ico" Header="Restore Defaults" Command="{Binding ToggleCommand}" />
</NativeMenu>
</NativeMenuItem>
<NativeMenuItem Header="Exit" Command="{Binding ExitCommand}" />
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.Icons>
</Application>

9
samples/ControlCatalog/App.xaml.cs

@ -1,14 +1,21 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using ControlCatalog.ViewModels;
namespace ControlCatalog
{
public class App : Application
{
public App()
{
DataContext = new ApplicationViewModel();
}
private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
@ -97,7 +104,9 @@ namespace ControlCatalog
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow = new MainWindow();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();

1
samples/ControlCatalog/MainView.xaml

@ -98,6 +98,7 @@
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
<ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
</StackPanel>

1
samples/ControlCatalog/MainWindow.xaml

@ -12,6 +12,7 @@
ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
TransparencyLevelHint="{Binding TransparencyLevel}"
x:Name="MainWindow"
Background="Transparent"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
<NativeMenu.Menu>
<NativeMenu>

2
samples/ControlCatalog/MainWindow.xaml.cs

@ -35,6 +35,8 @@ namespace ControlCatalog
var mainMenu = this.FindControl<Menu>("MainMenu");
mainMenu.AttachedToVisualTree += MenuAttached;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
}
public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";

10
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -11,7 +11,15 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" />
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200"
FontFamily="Comic Sans MS"
Foreground="Blue">
<TextBox.ContextFlyout>
<Flyout>
<TextBlock>Custom context flyout</TextBlock>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric Watermark" x:Name="numericWatermark"/>
<TextBox Width="200"

1
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -14,6 +14,7 @@
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
</StackPanel>
</UserControl>

26
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

@ -0,0 +1,26 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class ApplicationViewModel : ViewModelBase
{
public ApplicationViewModel()
{
ExitCommand = MiniCommand.Create(() =>
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.Shutdown();
}
});
ToggleCommand = MiniCommand.Create(() => { });
}
public MiniCommand ExitCommand { get; }
public MiniCommand ToggleCommand { get; }
}
}

5
src/Avalonia.Base/Logging/LogArea.cs

@ -39,5 +39,10 @@ namespace Avalonia.Logging
/// The log event comes from Win32Platform.
/// </summary>
public const string Win32Platform = nameof(Win32Platform);
/// <summary>
/// The log event comes from X11Platform.
/// </summary>
public const string X11Platform = nameof(X11Platform);
}
}

1
src/Avalonia.Base/Metadata/TemplateContent.cs

@ -8,5 +8,6 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.Property)]
public class TemplateContentAttribute : Attribute
{
public Type TemplateResultType { get; set; }
}
}

18
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2223,6 +2223,7 @@ namespace Avalonia.Controls
if (IsEnabled && DisplayData.NumDisplayedScrollingElements > 0)
{
var handled = false;
var ignoreInvalidate = false;
var scrollHeight = 0d;
// Vertical scroll handling
@ -2252,8 +2253,7 @@ namespace Avalonia.Controls
// Horizontal scroll handling
if (delta.X != 0)
{
var originalHorizontalOffset = HorizontalOffset;
var horizontalOffset = originalHorizontalOffset - delta.X;
var horizontalOffset = HorizontalOffset - delta.X;
var widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth);
if (horizontalOffset < 0)
@ -2265,16 +2265,20 @@ namespace Avalonia.Controls
horizontalOffset = widthNotVisible;
}
if (horizontalOffset != originalHorizontalOffset)
if (UpdateHorizontalOffset(horizontalOffset))
{
HorizontalOffset = horizontalOffset;
// We don't need to invalidate once again after UpdateHorizontalOffset.
ignoreInvalidate = true;
handled = true;
}
}
if (handled)
{
InvalidateRowsMeasure(invalidateIndividualElements: false);
if (!ignoreInvalidate)
{
InvalidateRowsMeasure(invalidateIndividualElements: false);
}
return true;
}
}
@ -2932,7 +2936,7 @@ namespace Avalonia.Controls
return SetCurrentCellCore(columnIndex, slot, commitEdit: true, endRowEdit: true);
}
internal void UpdateHorizontalOffset(double newValue)
internal bool UpdateHorizontalOffset(double newValue)
{
if (HorizontalOffset != newValue)
{
@ -2940,7 +2944,9 @@ namespace Avalonia.Controls
InvalidateColumnHeadersMeasure();
InvalidateRowsMeasure(true);
return true;
}
return false;
}
internal bool UpdateSelectionAndCurrency(int columnIndex, int slot, DataGridSelectionAction action, bool scrollIntoView)

2
src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs

@ -133,7 +133,7 @@ namespace Avalonia.Controls
protected abstract IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem);
internal AvaloniaProperty BindingTarget { get; set; }
protected AvaloniaProperty BindingTarget { get; set; }
internal void SetHeaderFromBinding()
{

29
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -9,6 +9,7 @@ using Avalonia.VisualTree;
using Avalonia.Collections;
using Avalonia.Utilities;
using System;
using System.ComponentModel;
using System.Linq;
using System.Diagnostics;
using Avalonia.Controls.Utils;
@ -653,6 +654,34 @@ namespace Avalonia.Controls
return null;
}
/// <summary>
/// Clears the current sort direction
/// </summary>
public void ClearSort()
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.Control);
}
/// <summary>
/// Switches the current state of sort direction
/// </summary>
public void Sort()
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.None);
}
/// <summary>
/// Changes the sort direction of this column
/// </summary>
/// <param name="direction">New sort direction</param>
public void Sort(ListSortDirection direction)
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(Input.KeyModifiers.None, direction);
}
/// <summary>
/// When overridden in a derived class, causes the column cell being edited to revert to the unedited value.
/// </summary>

26
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -201,21 +201,21 @@ namespace Avalonia.Controls
handled = true;
}
internal void InvokeProcessSort(KeyModifiers keyModifiers)
internal void InvokeProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
{
Debug.Assert(OwningGrid != null);
if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers)))
if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers, forcedDirection)))
{
return;
}
if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers));
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers, forcedDirection));
}
}
//TODO GroupSorting
internal void ProcessSort(KeyModifiers keyModifiers)
internal void ProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
{
// if we can sort:
// - AllowUserToSortColumns and CanSort are true, and
@ -259,7 +259,14 @@ namespace Avalonia.Controls
{
if (sort != null)
{
newSort = sort.SwitchSortDirection();
if (forcedDirection == null || sort.Direction != forcedDirection)
{
newSort = sort.SwitchSortDirection();
}
else
{
newSort = sort;
}
// changing direction should not affect sort order, so we replace this column's
// sort description instead of just adding it to the end of the collection
@ -276,7 +283,10 @@ namespace Avalonia.Controls
}
else if (OwningColumn.CustomSortComparer != null)
{
newSort = DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer);
newSort = forcedDirection != null ?
DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer, forcedDirection.Value) :
DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer);
owningGrid.DataConnection.SortDescriptions.Add(newSort);
}
@ -290,6 +300,10 @@ namespace Avalonia.Controls
}
newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
if (forcedDirection != null && newSort.Direction != forcedDirection)
{
newSort = newSort.SwitchSortDirection();
}
owningGrid.DataConnection.SortDescriptions.Add(newSort);
}

4
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -37,6 +37,7 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
@ -58,4 +59,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
Total Issues: 59
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 61

2
src/Avalonia.Controls/Border.cs

@ -8,7 +8,9 @@ namespace Avalonia.Controls
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public partial class Border : Decorator, IVisualWithRoundRectClip
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Defines the <see cref="Background"/> property.

7
src/Avalonia.Controls/Button.cs

@ -359,6 +359,13 @@ namespace Avalonia.Controls
IsPressed = false;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
IsPressed = false;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

98
src/Avalonia.Controls/ItemsSourceView.cs

@ -32,8 +32,8 @@ namespace Avalonia.Controls
/// </summary>
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
private protected readonly IList _inner;
private INotifyCollectionChanged? _notifyCollectionChanged;
private IList? _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
/// <summary>
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
@ -42,27 +42,22 @@ namespace Avalonia.Controls
public ItemsSourceView(IEnumerable source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
if (source is IList list)
{
_inner = list;
}
else if (source is IEnumerable<object> objectEnumerable)
_inner = source switch
{
_inner = new List<object>(objectEnumerable);
}
else
{
_inner = new List<object>(source.Cast<object>());
}
ListenToCollectionChanges();
ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged _ => throw new ArgumentException(
"Collection implements INotifyCollectionChanged by not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
_ => new List<object>(source.Cast<object>())
};
}
/// <summary>
/// Gets the number of items in the collection.
/// </summary>
public int Count => _inner.Count;
public int Count => Inner.Count;
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
@ -72,6 +67,19 @@ namespace Avalonia.Controls
/// </remarks>
public bool HasKeyIndexMapping => false;
/// <summary>
/// Gets the inner collection.
/// </summary>
public IList Inner
{
get
{
if (_inner is null)
ThrowDisposed();
return _inner!;
}
}
/// <summary>
/// Retrieves the item at the specified index.
/// </summary>
@ -82,15 +90,38 @@ namespace Avalonia.Controls
/// <summary>
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
/// </summary>
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add
{
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
}
_collectionChanged += value;
}
remove
{
_collectionChanged -= value;
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= OnCollectionChanged;
}
}
}
/// <inheritdoc/>
public void Dispose()
{
if (_notifyCollectionChanged != null)
if (_inner is INotifyCollectionChanged incc)
{
_notifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
incc.CollectionChanged -= OnCollectionChanged;
}
_inner = null;
}
/// <summary>
@ -98,9 +129,9 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public object? GetAt(int index) => _inner[index];
public object? GetAt(int index) => Inner[index];
public int IndexOf(object? item) => _inner.IndexOf(item);
public int IndexOf(object? item) => Inner.IndexOf(item);
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
@ -146,7 +177,7 @@ namespace Avalonia.Controls
internal void AddListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
if (Inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.AddListener(incc, listener);
}
@ -154,7 +185,7 @@ namespace Avalonia.Controls
internal void RemoveListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
if (Inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
}
@ -162,22 +193,15 @@ namespace Avalonia.Controls
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
private void ListenToCollectionChanges()
{
if (_inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
_notifyCollectionChanged = incc;
}
_collectionChanged?.Invoke(this, args);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnItemsSourceChanged(e);
}
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
}
public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@ -216,10 +240,10 @@ namespace Avalonia.Controls
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
[return: MaybeNull]
public new T GetAt(int index) => (T)_inner[index];
public new T GetAt(int index) => (T)Inner[index];
public IEnumerator<T> GetEnumerator() => _inner.Cast<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
{

13
src/Avalonia.Controls/NativeMenu.Export.cs

@ -52,15 +52,10 @@ namespace Avalonia.Controls
}
public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu"/*, validate:
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
}*/);
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu");
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu()
@ -79,6 +74,10 @@ namespace Avalonia.Controls
{
GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
else if(args.Sender is INativeMenuExporterProvider provider)
{
provider.NativeMenuExporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
});
}
}

2
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -332,7 +332,9 @@ namespace Avalonia.Controls
/// </summary>
static NumericUpDown()
{
#pragma warning disable CS0612 // Type or member is obsolete
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
#pragma warning restore CS0612 // Type or member is obsolete
NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);

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

@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform
return;
}
if (item.HasSubMenu)
if (item.HasSubMenu && item.IsEffectivelyEnabled)
{
Open(item, true);
}

17
src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs

@ -1,14 +1,25 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Controls.Platform
{
public interface ITopLevelNativeMenuExporter
public interface INativeMenuExporter
{
void SetNativeMenu(NativeMenu? menu);
}
public interface ITopLevelNativeMenuExporter : INativeMenuExporter
{
bool IsNativeMenuExported { get; }
event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu);
}
public interface INativeMenuExporterProvider
{
INativeMenuExporter? NativeMenuExporter { get; }
}
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl

35
src/Avalonia.Controls/Platform/ITrayIconImpl.cs

@ -0,0 +1,35 @@
using System;
using Avalonia.Controls.Platform;
#nullable enable
namespace Avalonia.Platform
{
public interface ITrayIconImpl : IDisposable
{
/// <summary>
/// Sets the icon of this tray icon.
/// </summary>
void SetIcon(IWindowIconImpl? icon);
/// <summary>
/// Sets the icon of this tray icon.
/// </summary>
void SetToolTipText(string? text);
/// <summary>
/// Sets if the tray icon is visible or not.
/// </summary>
void SetIsVisible(bool visible);
/// <summary>
/// Gets the MenuExporter to allow native menus to be exported to the TrayIcon.
/// </summary>
INativeMenuExporter? MenuExporter { get; }
/// <summary>
/// Gets or Sets the Action that is called when the TrayIcon is clicked.
/// </summary>
Action? OnClicked { get; set; }
}
}

5
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

@ -1,8 +1,13 @@
#nullable enable
namespace Avalonia.Platform
{
public interface IWindowingPlatform
{
IWindowImpl CreateWindow();
IWindowImpl CreateEmbeddableWindow();
ITrayIconImpl? CreateTrayIcon();
}
}

16
src/Avalonia.Controls/Platform/PlatformManager.cs

@ -1,8 +1,9 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Media;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Controls.Platform
{
public static partial class PlatformManager
@ -22,6 +23,19 @@ namespace Avalonia.Controls.Platform
{
}
public static ITrayIconImpl? CreateTrayIcon()
{
var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
if (platform == null)
{
throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered.");
}
return s_designerMode ? null : platform.CreateTrayIcon();
}
public static IWindowImpl CreateWindow()
{
var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();

19
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -60,9 +60,6 @@ namespace Avalonia.Controls.Presenters
o => o.Viewport,
(o, v) => o.Viewport = v);
// Arbitrary chosen value, probably need to ask ILogicalScrollable
private const int LogicalScrollItemSize = 50;
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private bool _arranging;
@ -351,7 +348,8 @@ namespace Avalonia.Controls.Presenters
if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width)
{
var scrollable = Child as ILogicalScrollable;
bool isLogical = scrollable?.IsLogicalScrollEnabled == true;
var isLogical = scrollable?.IsLogicalScrollEnabled == true;
var logicalScrollItemSize = new Vector(1, 1);
double x = Offset.X;
double y = Offset.Y;
@ -361,13 +359,18 @@ namespace Avalonia.Controls.Presenters
_activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta);
delta += e.Delta;
if (isLogical && scrollable is object)
{
logicalScrollItemSize = Bounds.Size / scrollable.Viewport;
}
if (Extent.Height > Viewport.Height)
{
double dy;
if (isLogical)
{
var logicalUnits = delta.Y / LogicalScrollItemSize;
delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize);
var logicalUnits = delta.Y / logicalScrollItemSize.Y;
delta = delta.WithY(delta.Y - logicalUnits * logicalScrollItemSize.Y);
dy = logicalUnits * scrollable!.ScrollSize.Height;
}
else
@ -384,8 +387,8 @@ namespace Avalonia.Controls.Presenters
double dx;
if (isLogical)
{
var logicalUnits = delta.X / LogicalScrollItemSize;
delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize);
var logicalUnits = delta.X / logicalScrollItemSize.X;
delta = delta.WithX(delta.X - logicalUnits * logicalScrollItemSize.X);
dx = logicalUnits * scrollable!.ScrollSize.Width;
}
else

4
src/Avalonia.Controls/Primitives/Popup.cs

@ -21,10 +21,12 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Displays a popup window.
/// </summary>
#pragma warning disable CS0612 // Type or member is obsolete
public class Popup : Control, IVisualTreeHost, IPopupHostProvider
#pragma warning restore CS0612 // Type or member is obsolete
{
public static readonly StyledProperty<bool> WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register<PopupRoot, bool>(nameof(WindowManagerAddShadowHint), true);
AvaloniaProperty.Register<PopupRoot, bool>(nameof(WindowManagerAddShadowHint), false);
/// <summary>
/// Defines the <see cref="Child"/> property.

4
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -447,8 +447,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect)
{
// We need a better way for tracking the last pointer position
#pragma warning disable CS0618 // Type or member is obsolete
var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position);
#pragma warning restore CS0618 // Type or member is obsolete
positionerParameters.Offset = offset;
positionerParameters.ConstraintAdjustment = constraintAdjustment;
if (placement == PlacementMode.Pointer)

3
src/Avalonia.Controls/ProgressBar.cs

@ -218,9 +218,12 @@ namespace Avalonia.Controls
TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
#pragma warning disable CS0618 // Type or member is obsolete
// Remove these properties when we switch to fluent as default and removed the old one.
IndeterminateStartingOffset = -dim;
IndeterminateEndingOffset = dim;
#pragma warning restore CS0618 // Type or member is obsolete
var padding = Padding;
var rectangle = new RectangleGeometry(

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -77,8 +77,10 @@ namespace Avalonia.Controls.Remote
_bitmap.PixelSize.Height != _lastFrame.Height)
{
_bitmap?.Dispose();
#pragma warning disable CS0618 // Type or member is obsolete
_bitmap = new WriteableBitmap(new PixelSize(_lastFrame.Width, _lastFrame.Height),
new Vector(96, 96), fmt);
#pragma warning restore CS0618 // Type or member is obsolete
}
using (var l = _bitmap.Lock())
{

6
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -588,14 +588,14 @@ namespace Avalonia.Controls
throw new AvaloniaInternalException("Cannot set ItemsSourceView during layout.");
}
ItemsSourceView?.Dispose();
ItemsSourceView = newValue;
if (oldValue != null)
{
oldValue.CollectionChanged -= OnItemsSourceViewChanged;
}
ItemsSourceView?.Dispose();
ItemsSourceView = newValue;
if (newValue != null)
{
newValue.CollectionChanged += OnItemsSourceViewChanged;

9
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
@ -10,18 +11,16 @@ namespace Avalonia.Controls.Templates
{
}
public class ControlTemplateResult
public class ControlTemplateResult : TemplateResult<IControl>
{
public IControl Control { get; }
public INameScope NameScope { get; }
public ControlTemplateResult(IControl control, INameScope nameScope)
public ControlTemplateResult(IControl control, INameScope nameScope) : base(control, nameScope)
{
Control = control;
NameScope = nameScope;
}
public void Deconstruct(out IControl control, out INameScope scope)
public new void Deconstruct(out IControl control, out INameScope scope)
{
control = Control;
scope = NameScope;

20
src/Avalonia.Controls/Templates/TemplateResult.cs

@ -0,0 +1,20 @@
namespace Avalonia.Controls.Templates
{
public class TemplateResult<T>
{
public T Result { get; }
public INameScope NameScope { get; }
public TemplateResult(T result, INameScope nameScope)
{
Result = result;
NameScope = nameScope;
}
public void Deconstruct(out T result, out INameScope scope)
{
result = Result;
scope = NameScope;
}
}
}

2
src/Avalonia.Controls/TextBox.cs

@ -992,7 +992,9 @@ namespace Avalonia.Controls
{
var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point);
#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete
{
case 1:
SelectionStart = SelectionEnd = index;

187
src/Avalonia.Controls/TrayIcon.cs

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Controls
{
public sealed class TrayIcons : AvaloniaList<TrayIcon>
{
}
public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
{
private readonly ITrayIconImpl? _impl;
private TrayIcon(ITrayIconImpl? impl)
{
if (impl != null)
{
_impl = impl;
_impl.SetIsVisible(IsVisible);
_impl.OnClicked = () => Clicked?.Invoke(this, EventArgs.Empty);
}
}
public TrayIcon() : this(PlatformManager.CreateTrayIcon())
{
}
static TrayIcon()
{
IconsProperty.Changed.Subscribe(args =>
{
if (args.Sender is Application)
{
if (args.OldValue.Value != null)
{
RemoveIcons(args.OldValue.Value);
}
if (args.NewValue.Value != null)
{
args.NewValue.Value.CollectionChanged += Icons_CollectionChanged;
}
}
});
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.Exit += Lifetime_Exit;
}
}
/// <summary>
/// Raised when the TrayIcon is clicked.
/// Note, this is only supported on Win32 and some Linux DEs,
/// on OSX this event is not raised.
/// </summary>
public event EventHandler? Clicked;
/// <summary>
/// Defines the <see cref="TrayIcons"/> attached property.
/// </summary>
public static readonly AttachedProperty<TrayIcons> IconsProperty
= AvaloniaProperty.RegisterAttached<TrayIcon, Application, TrayIcons>("Icons");
/// <summary>
/// Defines the <see cref="Menu"/> property.
/// </summary>
public static readonly StyledProperty<NativeMenu?> MenuProperty
= AvaloniaProperty.Register<TrayIcon, NativeMenu?>(nameof(Menu));
/// <summary>
/// Defines the <see cref="Icon"/> property.
/// </summary>
public static readonly StyledProperty<WindowIcon> IconProperty =
Window.IconProperty.AddOwner<TrayIcon>();
/// <summary>
/// Defines the <see cref="ToolTipText"/> property.
/// </summary>
public static readonly StyledProperty<string?> ToolTipTextProperty =
AvaloniaProperty.Register<TrayIcon, string?>(nameof(ToolTipText));
/// <summary>
/// Defines the <see cref="IsVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsVisibleProperty =
Visual.IsVisibleProperty.AddOwner<TrayIcon>();
public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons);
public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty);
/// <summary>
/// Gets or sets the Menu of the TrayIcon.
/// </summary>
public NativeMenu? Menu
{
get => GetValue(MenuProperty);
set => SetValue(MenuProperty, value);
}
/// <summary>
/// Gets or sets the icon of the TrayIcon.
/// </summary>
public WindowIcon Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
/// <summary>
/// Gets or sets the tooltip text of the TrayIcon.
/// </summary>
public string? ToolTipText
{
get => GetValue(ToolTipTextProperty);
set => SetValue(ToolTipTextProperty, value);
}
/// <summary>
/// Gets or sets the visibility of the TrayIcon.
/// </summary>
public bool IsVisible
{
get => GetValue(IsVisibleProperty);
set => SetValue(IsVisibleProperty, value);
}
public INativeMenuExporter? NativeMenuExporter => _impl?.MenuExporter;
private static void Lifetime_Exit(object sender, ControlledApplicationLifetimeExitEventArgs e)
{
var trayIcons = GetIcons(Application.Current);
RemoveIcons(trayIcons);
}
private static void Icons_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RemoveIcons(e.OldItems.Cast<TrayIcon>());
}
private static void RemoveIcons(IEnumerable<TrayIcon> icons)
{
foreach (var icon in icons)
{
icon.Dispose();
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == IconProperty)
{
_impl?.SetIcon(Icon.PlatformImpl);
}
else if (change.Property == IsVisibleProperty)
{
_impl?.SetIsVisible(change.NewValue.GetValueOrDefault<bool>());
}
else if (change.Property == ToolTipTextProperty)
{
_impl?.SetToolTipText(change.NewValue.GetValueOrDefault<string?>());
}
else if (change.Property == MenuProperty)
{
_impl?.MenuExporter?.SetNativeMenu(change.NewValue.GetValueOrDefault<NativeMenu>());
}
}
/// <summary>
/// Disposes the tray icon (removing it from the tray area).
/// </summary>
public void Dispose() => _impl?.Dispose();
}
}

7
src/Avalonia.Controls/WindowTransparencyLevel.cs

@ -20,6 +20,11 @@
/// <summary>
/// The window background is a blur-behind with a high blur radius. This level may fallback to Blur.
/// </summary>
AcrylicBlur
AcrylicBlur,
/// <summary>
/// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11
/// </summary>
Mica
}
}

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -16,7 +16,9 @@ namespace Avalonia.DesignerSupport.Remote
private static DetachableTransportConnection s_lastWindowTransport;
private static PreviewerWindowImpl s_lastWindow;
public static List<object> PreFlightMessages = new List<object>();
public ITrayIconImpl CreateTrayIcon() => null;
public IWindowImpl CreateWindow() => new WindowStub();
public IWindowImpl CreateEmbeddableWindow()

12
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -160,13 +160,19 @@ namespace Avalonia.Diagnostics.Views
return;
}
var root = Root;
if (root is null)
{
return;
}
switch (e.Modifiers)
{
case RawInputModifiers.Control | RawInputModifiers.Shift:
{
IControl? control = null;
foreach (var popupRoot in GetPopupRoots(Root))
foreach (var popupRoot in GetPopupRoots(root))
{
control = GetHoveredControl(popupRoot);
@ -176,7 +182,7 @@ namespace Avalonia.Diagnostics.Views
}
}
control ??= GetHoveredControl(Root);
control ??= GetHoveredControl(root);
if (control != null)
{
@ -190,7 +196,7 @@ namespace Avalonia.Diagnostics.Views
{
vm.FreezePopups = !vm.FreezePopups;
foreach (var popupRoot in GetPopupRoots(Root))
foreach (var popupRoot in GetPopupRoots(root))
{
if (popupRoot.Parent is Popup popup)
{

8
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
@ -30,19 +31,20 @@ namespace Avalonia.Dialogs
}
else
{
using (Process process = Process.Start(new ProcessStartInfo
using Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
}));
});
}
}
private static void ShellExec(string cmd, bool waitForExit = true)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
var escapedArgs = Regex.Replace(cmd, "(?=[`~!#&*()|;'<>])", "\\")
.Replace("\"", "\\\\\\\"");
using (var process = Process.Start(
new ProcessStartInfo

4
src/Avalonia.Dialogs/ManagedFileChooser.cs

@ -1,13 +1,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs
{
@ -35,7 +33,9 @@ namespace Avalonia.Dialogs
if (_quickLinksRoot != null)
{
var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control);
#pragma warning disable CS0618 // Type or member is obsolete
if (e.ClickCount == 2 || isQuickLink)
#pragma warning restore CS0618 // Type or member is obsolete
{
if (model.ItemType == ManagedFileChooserItemType.File)
{

2
src/Avalonia.Dialogs/ManagedFileChooserSources.cs

@ -67,7 +67,7 @@ namespace Avalonia.Dialogs
{
Directory.GetFiles(x.VolumePath);
}
catch (Exception _)
catch (Exception)
{
return null;
}

7
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -51,8 +51,11 @@ namespace Avalonia.FreeDesktop
public static Connection TryInitialize(string dbusAddress = null)
{
if (Connection != null)
return Connection;
return Connection ?? TryGetConnection(dbusAddress);
}
public static Connection TryGetConnection(string dbusAddress = null)
{
var oldContext = SynchronizationContext.Current;
try
{

75
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -8,6 +8,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop.DBusMenu;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Threading;
using Tmds.DBus;
#pragma warning disable 1998
@ -16,51 +17,78 @@ namespace Avalonia.FreeDesktop
{
public class DBusMenuExporter
{
public static ITopLevelNativeMenuExporter TryCreate(IntPtr xid)
public static ITopLevelNativeMenuExporter TryCreateTopLevelNativeMenu(IntPtr xid)
{
if (DBusHelper.Connection == null)
return null;
return new DBusMenuExporterImpl(DBusHelper.Connection, xid);
}
public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConection)
{
return new DBusMenuExporterImpl(currentConection, path);
}
public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/"
+ Guid.NewGuid().ToString("N");
class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable
private class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable
{
private readonly Connection _dbus;
private readonly uint _xid;
private IRegistrar _registar;
private IRegistrar _registrar;
private bool _disposed;
private uint _revision = 1;
private NativeMenu _menu;
private Dictionary<int, NativeMenuItemBase> _idsToItems = new Dictionary<int, NativeMenuItemBase>();
private Dictionary<NativeMenuItemBase, int> _itemsToIds = new Dictionary<NativeMenuItemBase, int>();
private readonly Dictionary<int, NativeMenuItemBase> _idsToItems = new Dictionary<int, NativeMenuItemBase>();
private readonly Dictionary<NativeMenuItemBase, int> _itemsToIds = new Dictionary<NativeMenuItemBase, int>();
private readonly HashSet<NativeMenu> _menus = new HashSet<NativeMenu>();
private bool _resetQueued;
private int _nextId = 1;
private bool _appMenu = true;
public DBusMenuExporterImpl(Connection dbus, IntPtr xid)
{
_dbus = dbus;
_xid = (uint)xid.ToInt32();
ObjectPath = new ObjectPath("/net/avaloniaui/dbusmenu/"
+ Guid.NewGuid().ToString().Replace("-", ""));
ObjectPath = GenerateDBusMenuObjPath;
SetNativeMenu(new NativeMenu());
Init();
}
public DBusMenuExporterImpl(Connection dbus, ObjectPath path)
{
_dbus = dbus;
_appMenu = false;
ObjectPath = path;
SetNativeMenu(new NativeMenu());
Init();
}
async void Init()
{
try
{
await _dbus.RegisterObjectAsync(this);
_registar = DBusHelper.Connection.CreateProxy<IRegistrar>(
"com.canonical.AppMenu.Registrar",
"/com/canonical/AppMenu/Registrar");
if (!_disposed)
await _registar.RegisterWindowAsync(_xid, ObjectPath);
if (_appMenu)
{
await _dbus.RegisterObjectAsync(this);
_registrar = DBusHelper.Connection.CreateProxy<IRegistrar>(
"com.canonical.AppMenu.Registrar",
"/com/canonical/AppMenu/Registrar");
if (!_disposed)
await _registrar.RegisterWindowAsync(_xid, ObjectPath);
}
else
{
await _dbus.RegisterObjectAsync(this);
}
}
catch (Exception e)
{
Console.Error.WriteLine(e);
Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.X11Platform)
?.Log(this, e.Message);
// It's not really important if this code succeeds,
// and it's not important to know if it succeeds
// since even if we register the window it's not guaranteed that
@ -75,7 +103,7 @@ namespace Avalonia.FreeDesktop
_disposed = true;
_dbus.UnregisterObject(this);
// Fire and forget
_registar?.UnregisterWindowAsync(_xid);
_registrar?.UnregisterWindowAsync(_xid);
}
@ -248,17 +276,24 @@ namespace Avalonia.FreeDesktop
if (item.ToggleType != NativeMenuItemToggleType.None)
return item.IsChecked ? 1 : 0;
}
if (name == "icon-data")
{
if (item.Icon != null)
{
var ms = new MemoryStream();
item.Icon.Save(ms);
return ms.ToArray();
var loader = AvaloniaLocator.Current.GetService<IPlatformIconLoader>();
if (loader != null)
{
var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item);
using var ms = new MemoryStream();
icon.Save(ms);
return ms.ToArray();
}
}
}
if (name == "children-display")
return menu != null ? "submenu" : null;
}

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -51,6 +51,8 @@ namespace Avalonia.Headless
public IWindowImpl CreateEmbeddableWindow() => throw new PlatformNotSupportedException();
public IPopupImpl CreatePopup() => new HeadlessWindowImpl(true);
public ITrayIconImpl CreateTrayIcon() => null;
}
internal static void Initialize()

8
src/Avalonia.Input/Gestures.cs

@ -81,17 +81,21 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source;
if (e.ClickCount <= 1)
#pragma warning disable CS0618 // Type or member is obsolete
var clickCount = e.ClickCount;
#pragma warning restore CS0618 // Type or member is obsolete
if (clickCount <= 1)
{
s_lastPress = new WeakReference<IInteractive>(ev.Source);
}
else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
else if (s_lastPress != null && clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
}
}

2
src/Avalonia.Input/MouseDevice.cs

@ -75,7 +75,9 @@ namespace Avalonia.Input
throw new InvalidOperationException("Control is not attached to visual tree.");
}
#pragma warning disable CS0618 // Type or member is obsolete
var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
#pragma warning restore CS0618 // Type or member is obsolete
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform!.Value;
}

63
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -9,14 +9,15 @@ using Avalonia.Threading;
namespace Avalonia.Native
{
class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
internal class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
{
private IAvaloniaNativeFactory _factory;
private readonly IAvaloniaNativeFactory _factory;
private bool _resetQueued = true;
private bool _exported = false;
private IAvnWindow _nativeWindow;
private bool _exported;
private readonly IAvnWindow _nativeWindow;
private NativeMenu _menu;
private __MicroComIAvnMenuProxy _nativeMenu;
private readonly IAvnTrayIcon _trayIcon;
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{
@ -33,13 +34,21 @@ namespace Avalonia.Native
DoLayoutReset();
}
public AvaloniaNativeMenuExporter(IAvnTrayIcon trayIcon, IAvaloniaNativeFactory factory)
{
_factory = factory;
_trayIcon = trayIcon;
DoLayoutReset();
}
public bool IsNativeMenuExported => _exported;
public event EventHandler OnIsNativeMenuExportedChanged;
public void SetNativeMenu(NativeMenu menu)
{
_menu = menu == null ? new NativeMenu() : menu;
_menu = menu ?? new NativeMenu();
DoLayoutReset(true);
}
@ -82,15 +91,22 @@ namespace Avalonia.Native
if (_nativeWindow is null)
{
var appMenu = NativeMenu.GetMenu(Application.Current);
if (_trayIcon is null)
{
var appMenu = NativeMenu.GetMenu(Application.Current);
if (appMenu == null)
if (appMenu == null)
{
appMenu = CreateDefaultAppMenu();
NativeMenu.SetMenu(Application.Current, appMenu);
}
SetMenu(appMenu);
}
else if (_menu != null)
{
appMenu = CreateDefaultAppMenu();
NativeMenu.SetMenu(Application.Current, appMenu);
SetMenu(_trayIcon, _menu);
}
SetMenu(appMenu);
}
else
{
@ -118,7 +134,7 @@ namespace Avalonia.Native
var appMenuHolder = menuItem?.Parent;
if (menu.Parent is null)
if (menuItem is null)
{
menuItem = new NativeMenuItem();
}
@ -136,7 +152,7 @@ namespace Avalonia.Native
if (_nativeMenu is null)
{
_nativeMenu = (__MicroComIAvnMenuProxy)__MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, appMenuHolder, "");
@ -171,5 +187,26 @@ namespace Avalonia.Native
avnWindow.SetMainMenu(_nativeMenu);
}
}
private void SetMenu(IAvnTrayIcon trayIcon, NativeMenu menu)
{
var setMenu = false;
if (_nativeMenu is null)
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, "");
setMenu = true;
}
_nativeMenu.Update(_factory, menu);
if(setMenu)
{
trayIcon.SetMenu(_nativeMenu);
}
}
}
}

5
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -134,6 +134,11 @@ namespace Avalonia.Native
}
}
public ITrayIconImpl CreateTrayIcon ()
{
return new TrayIconImpl(_factory);
}
public IWindowImpl CreateWindow()
{
return new WindowImpl(_factory, _options, _platformGl);

63
src/Avalonia.Native/TrayIconImpl.cs

@ -0,0 +1,63 @@
using System;
using System.IO;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Native
{
internal class TrayIconImpl : ITrayIconImpl
{
private readonly IAvnTrayIcon _native;
public TrayIconImpl(IAvaloniaNativeFactory factory)
{
_native = factory.CreateTrayIcon();
MenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
}
public Action? OnClicked { get; set; }
public void Dispose()
{
_native.Dispose();
}
public unsafe void SetIcon(IWindowIconImpl? icon)
{
if (icon is null)
{
_native.SetIcon(null, IntPtr.Zero);
}
else
{
using (var ms = new MemoryStream())
{
icon.Save(ms);
var imageData = ms.ToArray();
fixed (void* ptr = imageData)
{
_native.SetIcon(ptr, new IntPtr(imageData.Length));
}
}
}
}
public void SetToolTipText(string? text)
{
// NOP
}
public void SetIsVisible(bool visible)
{
_native.SetIsVisible(visible.AsComBool());
}
public INativeMenuExporter? MenuExporter { get; }
}
}

9
src/Avalonia.Native/avn.idl

@ -479,6 +479,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
HRESULT CreateMenuItem(IAvnMenuItem** ppv);
HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv);
HRESULT CreateTrayIcon(IAvnTrayIcon** ppv);
HRESULT CreateAutomationNode(IAvnAutomationPeer* peer, IAvnAutomationNode** ppv);
}
@ -718,6 +719,14 @@ interface IAvnGlSurfaceRenderingSession : IUnknown
HRESULT GetScaling(double* ret);
}
[uuid(60992d19-38f0-4141-a0a9-76ac303801f3)]
interface IAvnTrayIcon : IUnknown
{
HRESULT SetIcon(void* data, size_t length);
HRESULT SetMenu(IAvnMenu* menu);
HRESULT SetIsVisible(bool isVisible);
}
[uuid(a7724dc1-cf6b-4fa8-9d23-228bf2593edc)]
interface IAvnMenu : IUnknown
{

10
src/Avalonia.Themes.Default/OverlayPopupHost.xaml

@ -1,5 +1,11 @@
<Style xmlns="https://github.com/avaloniaui" Selector="OverlayPopupHost">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="OverlayPopupHost">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

10
src/Avalonia.Themes.Default/PopupRoot.xaml

@ -1,5 +1,11 @@
<Style xmlns="https://github.com/avaloniaui" Selector="PopupRoot">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="PopupRoot">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

6
src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml

@ -1,5 +1,9 @@
<Style xmlns="https://github.com/avaloniaui" Selector="OverlayPopupHost">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

6
src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml

@ -2,7 +2,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="PopupRoot">
<Setter Property="TransparencyLevelHint" Value="Transparent" />
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

8
src/Avalonia.Visuals/Media/DrawingImage.cs

@ -11,6 +11,14 @@ namespace Avalonia.Media
/// </summary>
public class DrawingImage : AvaloniaObject, IImage, IAffectsRender
{
public DrawingImage()
{
}
public DrawingImage(Drawing drawing)
{
Drawing = drawing;
}
/// <summary>
/// Defines the <see cref="Drawing"/> property.
/// </summary>

2
src/Avalonia.Visuals/Media/FormattedText.cs

@ -200,7 +200,7 @@ namespace Avalonia.Media
private void Set<T>(ref T field, T value)
{
if (field != null && field.Equals(value))
if (EqualityComparer<T>.Default.Equals(field, value))
{
return;
}

4
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -279,13 +279,13 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
return (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
return (_currentDraw?.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
}
/// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
var childScene = (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
var childScene = (_currentDraw?.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
if (childScene != null)
{

7
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -289,11 +289,14 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds
? visual is IVisualWithRoundRectClip roundClipVisual
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds)
: default(DrawingContext.PushedState))
#pragma warning restore CS0618 // Type or member is obsolete
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())

20
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@ -11,19 +11,23 @@ namespace Avalonia.Rendering.SceneGraph
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
/// clip push.
/// </summary>
/// <param name="transform">The current transform.</param>
/// <param name="clip">The clip to push.</param>
public ClipNode(Rect clip)
public ClipNode(Matrix transform, Rect clip)
{
Transform = transform;
Clip = clip;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
/// clip push.
/// </summary>
/// <param name="transform">The current transform.</param>
/// <param name="clip">The clip to push.</param>
public ClipNode(RoundedRect clip)
public ClipNode(Matrix transform, RoundedRect clip)
{
Transform = transform;
Clip = clip;
}
@ -43,23 +47,31 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
public RoundedRect? Clip { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="clip">The clip of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(RoundedRect? clip) => Clip == clip;
public bool Equals(Matrix transform, RoundedRect? clip) => Transform == transform && Clip == clip;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
if (Clip.HasValue)
{
context.PushClip(Clip.Value);

7
src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs

@ -17,7 +17,12 @@ namespace Avalonia.Rendering.SceneGraph
public override bool HitTest(Point p)
{
return Custom.HitTest(p * Transform);
if (Transform.HasInverse)
{
return Custom.HitTest(p * Transform.Invert());
}
return false;
}
public override void Render(IDrawingContextImpl context)

12
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -303,9 +303,9 @@ namespace Avalonia.Rendering.SceneGraph
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(clip))
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(clip));
Add(new ClipNode(Transform, clip));
}
else
{
@ -318,9 +318,9 @@ namespace Avalonia.Rendering.SceneGraph
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(clip))
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(clip));
Add(new ClipNode(Transform, clip));
}
else
{
@ -333,9 +333,9 @@ namespace Avalonia.Rendering.SceneGraph
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Item.Equals(clip))
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new GeometryClipNode(clip));
Add(new GeometryClipNode(Transform, clip));
}
else
{

14
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs

@ -11,9 +11,11 @@ namespace Avalonia.Rendering.SceneGraph
/// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
/// geometry clip push.
/// </summary>
/// <param name="transform">The current transform.</param>
/// <param name="clip">The clip to push.</param>
public GeometryClipNode(IGeometryImpl clip)
public GeometryClipNode(Matrix transform, IGeometryImpl clip)
{
Transform = transform;
Clip = clip;
}
@ -33,23 +35,31 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
public IGeometryImpl Clip { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="clip">The clip of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(IGeometryImpl clip) => Clip == clip;
public bool Equals(Matrix transform, IGeometryImpl clip) => Transform == transform && Clip == clip;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
if (Clip != null)
{
context.PushGeometryClip(Clip);

45
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
@ -82,8 +83,46 @@ namespace Avalonia.Rendering.SceneGraph
public override bool HitTest(Point p)
{
// TODO: Implement line hit testing.
return false;
if (!Transform.HasInverse)
return false;
p *= Transform.Invert();
var halfThickness = Pen.Thickness / 2;
var minX = Math.Min(P1.X, P2.X) - halfThickness;
var maxX = Math.Max(P1.X, P2.X) + halfThickness;
var minY = Math.Min(P1.Y, P2.Y) - halfThickness;
var maxY = Math.Max(P1.Y, P2.Y) + halfThickness;
if (p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY)
return false;
var a = P1;
var b = P2;
//If dot1 or dot2 is negative, then the angle between the perpendicular and the segment is obtuse.
//The distance from a point to a straight line is defined as the
//length of the vector formed by the point and the closest point of the segment
Vector ap = p - a;
var dot1 = Vector.Dot(b - a, ap);
if (dot1 < 0)
return ap.Length <= Pen.Thickness / 2;
Vector bp = p - b;
var dot2 = Vector.Dot(a - b, bp);
if (dot2 < 0)
return bp.Length <= halfThickness;
var bXaX = b.X - a.X;
var bYaY = b.Y - a.Y;
var distance = (bXaX * (p.Y - a.Y) - bYaY * (p.X - a.X)) /
(Math.Sqrt(bXaX * bXaX + bYaY * bYaY));
return Math.Abs(distance) <= halfThickness;
}
}
}

4
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -164,10 +164,12 @@ namespace Avalonia.Rendering.SceneGraph
var visual = node.Visual;
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
#pragma warning disable CS0618 // Type or member is obsolete
var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ?
roundRectClip.ClipToBoundsRadius :
default;
#pragma warning restore CS0618 // Type or member is obsolete
var bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;

2
src/Avalonia.Visuals/Vector.cs

@ -175,7 +175,7 @@ namespace Avalonia
MathUtilities.AreClose(_y, other._y);
}
public override bool Equals(object obj) => obj is Vector other && Equals(other);
public override bool Equals(object? obj) => obj is Vector other && Equals(other);
public override int GetHashCode()
{

6
src/Avalonia.X11/X11CursorFactory.cs

@ -23,9 +23,9 @@ namespace Avalonia.X11
private static readonly Dictionary<StandardCursorType, CursorFontShape> s_mapping =
new Dictionary<StandardCursorType, CursorFontShape>
{
{StandardCursorType.Arrow, CursorFontShape.XC_top_left_arrow},
{StandardCursorType.Arrow, CursorFontShape.XC_left_ptr},
{StandardCursorType.Cross, CursorFontShape.XC_cross},
{StandardCursorType.Hand, CursorFontShape.XC_hand1},
{StandardCursorType.Hand, CursorFontShape.XC_hand2},
{StandardCursorType.Help, CursorFontShape.XC_question_arrow},
{StandardCursorType.Ibeam, CursorFontShape.XC_xterm},
{StandardCursorType.No, CursorFontShape.XC_X_cursor},
@ -67,7 +67,7 @@ namespace Avalonia.X11
{
handle = s_mapping.TryGetValue(cursorType, out var shape)
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
: _cursors[CursorFontShape.XC_left_ptr];
}
return new CursorImpl(handle);
}

2
src/Avalonia.X11/X11IconLoader.cs

@ -77,7 +77,9 @@ namespace Avalonia.X11
public void Save(Stream outputStream)
{
using (var wr =
#pragma warning disable CS0618 // Type or member is obsolete
new WriteableBitmap(new PixelSize(_width, _height), new Vector(96, 96), PixelFormat.Bgra8888))
#pragma warning restore CS0618 // Type or member is obsolete
{
using (var fb = wr.Lock())
{

2
src/Avalonia.X11/X11Info.cs

@ -42,7 +42,7 @@ namespace Avalonia.X11
DefaultScreen = XDefaultScreen(display);
BlackPixel = XBlackPixel(display, DefaultScreen);
RootWindow = XRootWindow(display, DefaultScreen);
DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_top_left_arrow);
DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_left_ptr);
DefaultRootWindow = XDefaultRootWindow(display);
Atoms = new X11Atoms(display);

13
src/Avalonia.X11/X11Platform.cs

@ -52,11 +52,14 @@ namespace Avalonia.X11
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
if (Display == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
if (DeferredDisplay == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
OrphanedWindow = XCreateSimpleWindow(Display, XDefaultRootWindow(Display), 0, 0, 1, 1, 0, IntPtr.Zero,
IntPtr.Zero);
if (Display == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
XError.Init();
Info = new X11Info(Display, DeferredDisplay, useXim);
@ -100,6 +103,12 @@ namespace Avalonia.X11
public IntPtr DeferredDisplay { get; set; }
public IntPtr Display { get; set; }
public ITrayIconImpl CreateTrayIcon ()
{
return new X11TrayIconImpl();
}
public IWindowImpl CreateWindow()
{
return new X11Window(this, null);

367
src/Avalonia.X11/X11TrayIconImpl.cs

@ -0,0 +1,367 @@
#nullable enable
using System;
using System.Diagnostics;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Logging;
using Avalonia.Platform;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.X11
{
internal class X11TrayIconImpl : ITrayIconImpl
{
private static int s_trayIconInstanceId;
private readonly ObjectPath _dbusMenuPath;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private readonly Connection? _connection;
private DbusPixmap _icon;
private IStatusNotifierWatcher? _statusNotifierWatcher;
private string? _sysTrayServiceName;
private string? _tooltipText;
private bool _isActive;
private bool _isDisposed;
private readonly bool _ctorFinished;
public INativeMenuExporter? MenuExporter { get; }
public Action? OnClicked { get; set; }
public X11TrayIconImpl()
{
_connection = DBusHelper.TryGetConnection();
if (_connection is null)
{
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)
?.Log(this, "Unable to get a dbus connection for system tray icons.");
return;
}
_dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath;
MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection);
CreateTrayIcon();
_ctorFinished = true;
}
public async void CreateTrayIcon()
{
if (_connection is null)
return;
try
{
_statusNotifierWatcher = _connection.CreateProxy<IStatusNotifierWatcher>(
"org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher");
}
catch
{
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)
?.Log(this,
"DBUS: org.kde.StatusNotifierWatcher service is not available on this system. System Tray Icons will not work without it.");
}
if (_statusNotifierWatcher is null)
return;
var pid = Process.GetCurrentProcess().Id;
var tid = s_trayIconInstanceId++;
_sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}";
_statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath);
await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj);
await _connection.RegisterServiceAsync(_sysTrayServiceName);
await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName);
_statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText);
_statusNotifierItemDbusObj.SetIcon(_icon);
_statusNotifierItemDbusObj.ActivationDelegate += OnClicked;
_isActive = true;
}
public async void DestroyTrayIcon()
{
if (_connection is null)
return;
_connection.UnregisterObject(_statusNotifierItemDbusObj);
await _connection.UnregisterServiceAsync(_sysTrayServiceName);
_isActive = false;
}
public void Dispose()
{
_isDisposed = true;
DestroyTrayIcon();
_connection?.Dispose();
}
public void SetIcon(IWindowIconImpl? icon)
{
if (_isDisposed)
return;
if (!(icon is X11IconData x11icon))
return;
var w = (int)x11icon.Data[0];
var h = (int)x11icon.Data[1];
var pixLength = w * h;
var pixByteArrayCounter = 0;
var pixByteArray = new byte[w * h * 4];
for (var i = 0; i < pixLength; i++)
{
var rawPixel = x11icon.Data[i + 2].ToUInt32();
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8);
pixByteArray[pixByteArrayCounter++] = (byte)(rawPixel & 0xFF);
}
_icon = new DbusPixmap(w, h, pixByteArray);
_statusNotifierItemDbusObj?.SetIcon(_icon);
}
public void SetIsVisible(bool visible)
{
if (_isDisposed || !_ctorFinished)
return;
if (visible & !_isActive)
{
DestroyTrayIcon();
CreateTrayIcon();
}
else if (!visible & _isActive)
{
DestroyTrayIcon();
}
}
public void SetToolTipText(string? text)
{
if (_isDisposed || text is null)
return;
_tooltipText = text;
_statusNotifierItemDbusObj?.SetTitleAndTooltip(_tooltipText);
}
}
/// <summary>
/// DBus Object used for setting system tray icons.
/// </summary>
/// <remarks>
/// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html
/// </remarks>
internal class StatusNotifierItemDbusObj : IStatusNotifierItem
{
private readonly StatusNotifierItemProperties _backingProperties;
public event Action? OnTitleChanged;
public event Action? OnIconChanged;
public event Action? OnAttentionIconChanged;
public event Action? OnOverlayIconChanged;
public event Action? OnTooltipChanged;
public Action<string>? NewStatusAsync { get; set; }
public Action? ActivationDelegate { get; set; }
public ObjectPath ObjectPath { get; }
public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath)
{
ObjectPath = new ObjectPath($"/StatusNotifierItem");
_backingProperties = new StatusNotifierItemProperties
{
Menu = dbusmenuPath, // Needs a dbus menu somehow
ToolTip = new ToolTip("")
};
InvalidateAll();
}
public Task ContextMenuAsync(int x, int y) => Task.CompletedTask;
public Task ActivateAsync(int x, int y)
{
ActivationDelegate?.Invoke();
return Task.CompletedTask;
}
public Task SecondaryActivateAsync(int x, int y) => Task.CompletedTask;
public Task ScrollAsync(int delta, string orientation) => Task.CompletedTask;
public void InvalidateAll()
{
OnTitleChanged?.Invoke();
OnIconChanged?.Invoke();
OnOverlayIconChanged?.Invoke();
OnAttentionIconChanged?.Invoke();
OnTooltipChanged?.Invoke();
}
public Task<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError)
{
OnTitleChanged += handler;
return Task.FromResult(Disposable.Create(() => OnTitleChanged -= handler));
}
public Task<IDisposable> WatchNewIconAsync(Action handler, Action<Exception> onError)
{
OnIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnIconChanged -= handler));
}
public Task<IDisposable> WatchNewAttentionIconAsync(Action handler, Action<Exception> onError)
{
OnAttentionIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnAttentionIconChanged -= handler));
}
public Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError)
{
OnOverlayIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnOverlayIconChanged -= handler));
}
public Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError)
{
OnTooltipChanged += handler;
return Task.FromResult(Disposable.Create(() => OnTooltipChanged -= handler));
}
public Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError)
{
NewStatusAsync += handler;
return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler));
}
public Task<object> GetAsync(string prop) => Task.FromResult(new object());
public Task<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties);
public Task SetAsync(string prop, object val) => Task.CompletedTask;
public Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler) =>
Task.FromResult(Disposable.Empty);
public void SetIcon(DbusPixmap dbusPixmap)
{
_backingProperties.IconPixmap = new[] { dbusPixmap };
InvalidateAll();
}
public void SetTitleAndTooltip(string? text)
{
if (text is null)
return;
_backingProperties.Id = text;
_backingProperties.Category = "ApplicationStatus";
_backingProperties.Status = text;
_backingProperties.Title = text;
_backingProperties.ToolTip = new ToolTip(text);
InvalidateAll();
}
}
[DBusInterface("org.kde.StatusNotifierWatcher")]
internal interface IStatusNotifierWatcher : IDBusObject
{
Task RegisterStatusNotifierItemAsync(string Service);
Task RegisterStatusNotifierHostAsync(string Service);
}
[DBusInterface("org.kde.StatusNotifierItem")]
internal interface IStatusNotifierItem : IDBusObject
{
Task ContextMenuAsync(int x, int y);
Task ActivateAsync(int x, int y);
Task SecondaryActivateAsync(int x, int y);
Task ScrollAsync(int delta, string orientation);
Task<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewAttentionIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError);
Task<object> GetAsync(string prop);
Task<StatusNotifierItemProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[Dictionary]
// This class is used by Tmds.Dbus to ferry properties
// from the SNI spec.
// Don't change this to actual C# properties since
// Tmds.Dbus will get confused.
internal class StatusNotifierItemProperties
{
public string? Category;
public string? Id;
public string? Title;
public string? Status;
public ObjectPath Menu;
public DbusPixmap[]? IconPixmap;
public ToolTip ToolTip;
}
internal struct ToolTip
{
public readonly string First;
public readonly DbusPixmap[] Second;
public readonly string Third;
public readonly string Fourth;
private static readonly DbusPixmap[] s_blank =
{
new DbusPixmap(0, 0, Array.Empty<byte>()), new DbusPixmap(0, 0, Array.Empty<byte>())
};
public ToolTip(string message) : this("", s_blank, message, "")
{
}
public ToolTip(string first, DbusPixmap[] second, string third, string fourth)
{
First = first;
Second = second;
Third = third;
Fourth = fourth;
}
}
internal readonly struct DbusPixmap
{
public readonly int Width;
public readonly int Height;
public readonly byte[] Data;
public DbusPixmap(int width, int height, byte[] data)
{
Width = width;
Height = height;
Data = data;
}
}
}

2
src/Avalonia.X11/X11Window.cs

@ -191,7 +191,7 @@ namespace Avalonia.X11
if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
if (platform.Options.UseDBusMenu)
NativeMenuExporter = DBusMenuExporter.TryCreate(_handle);
NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
NativeControlHost = new X11NativeControlHost(_platform, this);
InitializeIme();
}

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -14,7 +14,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompiler : XamlILCompiler
{
private readonly TransformerConfiguration _configuration;
private readonly IXamlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
@ -22,8 +21,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
_configuration = configuration;
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);

7
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -49,8 +49,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XmlNamespaceInfoProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"),
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")},
DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.IControl"),
DeferredContentExecutorCustomizationTypeParameterDeferredContentAttributePropertyNames = new List<string>
{
"TemplateResultType"
},
DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV2"),
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit f4ac681b91a9dc7a7a095d1050a683de23d86b72
Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3

12
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.Templates
public static class TemplateContent
{
public static ControlTemplateResult Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
@ -20,5 +21,16 @@ namespace Avalonia.Markup.Xaml.Templates
throw new ArgumentException(nameof(templateContent));
}
public static TemplateResult<T> Load<T>(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
return (TemplateResult<T>)direct(null);
if (templateContent is null)
return null;
throw new ArgumentException(nameof(templateContent));
}
}
}

12
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -15,6 +15,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
return DeferredTransformationFactoryV2<IControl>(builder, provider);
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
@ -25,7 +31,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
return new ControlTemplateResult((IControl)obj, scope);
if(typeof(T) == typeof(IControl))
return new ControlTemplateResult((IControl)obj, scope);
return new TemplateResult<T>((T)obj, scope);
};
}

6
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@ -184,6 +184,9 @@ namespace Avalonia.Markup.Parsers
}
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
public class PropertySyntax : ISyntax
{
public string Name { get; set; } = string.Empty;
@ -205,7 +208,7 @@ namespace Avalonia.Markup.Parsers
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
}
public class ChildTraversalSyntax : ISyntax
{
public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax();
@ -231,5 +234,6 @@ namespace Avalonia.Markup.Parsers
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
}
#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
}
}

2
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -591,7 +591,7 @@ namespace Avalonia.Skia
/// Configure paint wrapper for using gradient brush.
/// </summary>
/// <param name="paintWrapper">Paint wrapper.</param>
/// <param name="targetRect">Target bound rect.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="gradientBrush">Gradient brush.</param>
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
{

6
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -140,7 +140,11 @@ namespace Avalonia.Skia
$"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
}
return new GlyphTypefaceImpl(skTypeface);
var isFakeBold = (int)typeface.Weight >= 600 && !skTypeface.IsBold;
var isFakeItalic = typeface.Style == FontStyle.Italic && !skTypeface.IsItalic;
return new GlyphTypefaceImpl(skTypeface, isFakeBold, isFakeItalic);
}
}
}

10
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -10,7 +10,7 @@ namespace Avalonia.Skia
{
private bool _isDisposed;
public GlyphTypefaceImpl(SKTypeface typeface)
public GlyphTypefaceImpl(SKTypeface typeface, bool isFakeBold = false, bool isFakeItalic = false)
{
Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface));
@ -52,6 +52,10 @@ namespace Avalonia.Skia
0;
IsFixedPitch = Typeface.IsFixedPitch;
IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic;
}
public Face Face { get; }
@ -86,6 +90,10 @@ namespace Avalonia.Skia
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public ushort GetGlyph(uint codepoint)

2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -217,6 +217,8 @@ namespace Avalonia.Skia
s_font.Size = (float)glyphRun.FontRenderingEmSize;
s_font.Typeface = typeface;
s_font.Embolden = glyphTypeface.IsFakeBold;
s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0;
SKTextBlob textBlob;

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

@ -1110,6 +1110,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr SetCapture(IntPtr hWnd);
@ -1197,6 +1200,9 @@ namespace Avalonia.Win32.Interop
GCW_ATOM = -32
}
[DllImport("shell32", CharSet = CharSet.Auto)]
public static extern int Shell_NotifyIcon(NIM dwMessage, NOTIFYICONDATA lpData);
[DllImport("user32.dll", EntryPoint = "SetClassLongPtr")]
private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
@ -2296,4 +2302,61 @@ namespace Avalonia.Win32.Interop
public uint VisibleMask;
public uint DamageMask;
}
internal enum NIM : uint
{
ADD = 0x00000000,
MODIFY = 0x00000001,
DELETE = 0x00000002,
SETFOCUS = 0x00000003,
SETVERSION = 0x00000004
}
[Flags]
internal enum NIF : uint
{
MESSAGE = 0x00000001,
ICON = 0x00000002,
TIP = 0x00000004,
STATE = 0x00000008,
INFO = 0x00000010,
GUID = 0x00000020,
REALTIME = 0x00000040,
SHOWTIP = 0x00000080
}
[Flags]
internal enum NIIF : uint
{
NONE = 0x00000000,
INFO = 0x00000001,
WARNING = 0x00000002,
ERROR = 0x00000003,
USER = 0x00000004,
ICON_MASK = 0x0000000F,
NOSOUND = 0x00000010,
LARGE_ICON = 0x00000020,
RESPECT_QUIET_TIME = 0x00000080
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal class NOTIFYICONDATA
{
public int cbSize = Marshal.SizeOf<NOTIFYICONDATA>();
public IntPtr hWnd;
public int uID;
public NIF uFlags;
public int uCallbackMessage;
public IntPtr hIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szTip;
public int dwState = 0;
public int dwStateMask = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szInfo;
public int uTimeoutOrVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string szInfoTitle;
public NIIF dwInfoFlags;
}
}

265
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
#nullable enable
namespace Avalonia.Win32
{
public class TrayIconImpl : ITrayIconImpl
{
private readonly int _uniqueId;
private static int s_nextUniqueId;
private bool _iconAdded;
private IconImpl? _icon;
private string? _tooltipText;
private readonly Win32NativeToManagedMenuExporter _exporter;
private static readonly Dictionary<int, TrayIconImpl> s_trayIcons = new Dictionary<int, TrayIconImpl>();
private bool _disposedValue;
public TrayIconImpl()
{
_exporter = new Win32NativeToManagedMenuExporter();
_uniqueId = ++s_nextUniqueId;
s_trayIcons.Add(_uniqueId, this);
}
public Action? OnClicked { get; set; }
public INativeMenuExporter MenuExporter => _exporter;
internal static void ProcWnd(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (int)CustomWindowsMessage.WM_TRAYMOUSE && s_trayIcons.ContainsKey(wParam.ToInt32()))
{
s_trayIcons[wParam.ToInt32()].WndProc(hWnd, msg, wParam, lParam);
}
}
public void SetIcon(IWindowIconImpl? icon)
{
_icon = icon as IconImpl;
UpdateIcon();
}
public void SetIsVisible(bool visible)
{
UpdateIcon(!visible);
}
public void SetToolTipText(string? text)
{
_tooltipText = text;
UpdateIcon(!_iconAdded);
}
private void UpdateIcon(bool remove = false)
{
var iconData = new NOTIFYICONDATA()
{
hWnd = Win32Platform.Instance.Handle,
uID = _uniqueId,
uFlags = NIF.TIP | NIF.MESSAGE,
uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE,
hIcon = _icon?.HIcon ?? new IconImpl(new System.Drawing.Bitmap(32, 32)).HIcon,
szTip = _tooltipText ?? ""
};
if (!remove)
{
iconData.uFlags |= NIF.ICON;
if (!_iconAdded)
{
Shell_NotifyIcon(NIM.ADD, iconData);
_iconAdded = true;
}
else
{
Shell_NotifyIcon(NIM.MODIFY, iconData);
}
}
else
{
Shell_NotifyIcon(NIM.DELETE, iconData);
_iconAdded = false;
}
}
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (uint)CustomWindowsMessage.WM_TRAYMOUSE)
{
// Determine the type of message and call the matching event handlers
switch (lParam.ToInt32())
{
case (int)WindowsMessage.WM_LBUTTONUP:
OnClicked?.Invoke();
break;
case (int)WindowsMessage.WM_RBUTTONUP:
OnRightClicked();
break;
}
return IntPtr.Zero;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private void OnRightClicked()
{
var _trayMenu = new TrayPopupRoot()
{
SystemDecorations = SystemDecorations.None,
SizeToContent = SizeToContent.WidthAndHeight,
Background = null,
TransparencyLevelHint = WindowTransparencyLevel.Transparent,
Content = new TrayIconMenuFlyoutPresenter()
{
Items = _exporter.GetMenu()
}
};
GetCursorPos(out POINT pt);
_trayMenu.Position = new PixelPoint(pt.X, pt.Y);
_trayMenu.Show();
}
/// <summary>
/// Custom Win32 window messages for the NotifyIcon
/// </summary>
private enum CustomWindowsMessage : uint
{
WM_TRAYICON = WindowsMessage.WM_APP + 1024,
WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024
}
private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter, IStyleable
{
Type IStyleable.StyleKey => typeof(MenuFlyoutPresenter);
public override void Close()
{
// DefaultMenuInteractionHandler calls this
var host = this.FindLogicalAncestorOfType<TrayPopupRoot>();
if (host != null)
{
SelectedIndex = -1;
host.Close();
}
}
}
private class TrayPopupRoot : Window
{
private readonly ManagedPopupPositioner _positioner;
public TrayPopupRoot()
{
_positioner = new ManagedPopupPositioner(new TrayIconManagedPopupPositionerPopupImplHelper(MoveResize));
Topmost = true;
Deactivated += TrayPopupRoot_Deactivated;
ShowInTaskbar = false;
ShowActivated = true;
}
private void TrayPopupRoot_Deactivated(object sender, EventArgs e)
{
Close();
}
private void MoveResize(PixelPoint position, Size size, double scaling)
{
PlatformImpl!.Move(position);
PlatformImpl!.Resize(size, PlatformResizeReason.Layout);
}
protected override void ArrangeCore(Rect finalRect)
{
base.ArrangeCore(finalRect);
_positioner.Update(new PopupPositionerParameters
{
Anchor = PopupAnchor.TopLeft,
Gravity = PopupGravity.BottomRight,
AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)),
Size = finalRect.Size,
ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY,
});
}
private class TrayIconManagedPopupPositionerPopupImplHelper : IManagedPopupPositionerPopup
{
private readonly Action<PixelPoint, Size, double> _moveResize;
private readonly Window _hiddenWindow;
public TrayIconManagedPopupPositionerPopupImplHelper(Action<PixelPoint, Size, double> moveResize)
{
_moveResize = moveResize;
_hiddenWindow = new Window();
}
public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens =>
_hiddenWindow.Screens.All.Select(s => new ManagedPopupPositionerScreenInfo(
s.Bounds.ToRect(1), s.Bounds.ToRect(1))).ToList();
public Rect ParentClientAreaScreenGeometry
{
get
{
var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft;
var size = _hiddenWindow.Screens.Primary.Bounds.Size;
return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity);
}
}
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity);
}
public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity;
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
UpdateIcon(true);
_disposedValue = true;
}
}
~TrayIconImpl()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

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

@ -27,7 +27,7 @@ namespace Avalonia.Win32
if (egl != null &&
opts?.UseWindowsUIComposition == true)
{
WinUICompositorConnection.TryCreateAndRegister(egl);
WinUICompositorConnection.TryCreateAndRegister(egl, opts.CompositionBackdropCornerRadius);
}
return egl;

54
src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

@ -0,0 +1,54 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
#nullable enable
namespace Avalonia.Win32
{
internal class Win32NativeToManagedMenuExporter : INativeMenuExporter
{
private NativeMenu? _nativeMenu;
public void SetNativeMenu(NativeMenu? nativeMenu)
{
_nativeMenu = nativeMenu;
}
private IEnumerable<MenuItem> Populate(NativeMenu nativeMenu)
{
foreach (var menuItem in nativeMenu.Items)
{
if (menuItem is NativeMenuItemSeparator)
{
yield return new MenuItem { Header = "-" };
}
else if (menuItem is NativeMenuItem item)
{
var newItem = new MenuItem { Header = item.Header, Icon = item.Icon, Command = item.Command, CommandParameter = item.CommandParameter };
if (item.Menu != null)
{
newItem.Items = Populate(item.Menu);
}
else if (item.HasClickHandlers && item is INativeMenuItemExporterEventsImplBridge bridge)
{
newItem.Click += (_, __) => bridge.RaiseClicked();
}
yield return newItem;
}
}
}
public IEnumerable<MenuItem>? GetMenu()
{
if (_nativeMenu != null)
{
return Populate(_nativeMenu);
}
return null;
}
}
}

18
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -89,6 +89,13 @@ namespace Avalonia
/// This is recommended if you need to use AcrylicBlur or acrylic in your applications.
/// </remarks>
public bool UseWindowsUIComposition { get; set; } = true;
/// <summary>
/// When <see cref="UseWindowsUIComposition"/> enabled, create rounded corner blur brushes
/// If set to null the brushes will be created using default settings (sharp corners)
/// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app
/// </summary>
public float? CompositionBackdropCornerRadius { get; set; }
}
}
@ -108,6 +115,10 @@ namespace Avalonia.Win32
CreateMessageWindow();
}
internal static Win32Platform Instance => s_instance;
internal IntPtr Handle => _hwnd;
/// <summary>
/// Gets the actual WindowsVersion. Same as the info returned from RtlGetVersion.
/// </summary>
@ -261,6 +272,8 @@ namespace Avalonia.Win32
}
}
}
TrayIconImpl.ProcWnd(hWnd, msg, wParam, lParam);
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
@ -293,6 +306,11 @@ namespace Avalonia.Win32
}
}
public ITrayIconImpl CreateTrayIcon ()
{
return new TrayIconImpl();
}
public IWindowImpl CreateWindow()
{
return new WindowImpl();

15
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs

@ -13,6 +13,8 @@ namespace Avalonia.Win32.WinRT.Composition
{
private EglContext _syncContext;
private readonly object _pumpLock;
private readonly IVisual _micaVisual;
private readonly ICompositionRoundedRectangleGeometry _roundedRectangleGeometry;
private readonly IVisual _blurVisual;
private ICompositionTarget _compositionTarget;
private IVisual _contentVisual;
@ -28,11 +30,14 @@ namespace Avalonia.Win32.WinRT.Composition
object pumpLock,
ICompositionTarget compositionTarget,
ICompositionDrawingSurfaceInterop surfaceInterop,
IVisual contentVisual, IVisual blurVisual)
IVisual contentVisual, IVisual blurVisual, IVisual micaVisual,
ICompositionRoundedRectangleGeometry roundedRectangleGeometry)
{
_compositor = compositor.CloneReference();
_syncContext = syncContext;
_pumpLock = pumpLock;
_micaVisual = micaVisual;
_roundedRectangleGeometry = roundedRectangleGeometry;
_blurVisual = blurVisual.CloneReference();
_compositionTarget = compositionTarget.CloneReference();
_contentVisual = contentVisual.CloneReference();
@ -48,6 +53,7 @@ namespace Avalonia.Win32.WinRT.Composition
{
_surfaceInterop.Resize(new UnmanagedMethods.POINT { X = size.Width, Y = size.Height });
_contentVisual.SetSize(new Vector2(size.Width, size.Height));
_roundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height));
_size = size;
}
}
@ -72,10 +78,13 @@ namespace Avalonia.Win32.WinRT.Composition
_surfaceInterop.EndDraw();
}
public void SetBlur(bool enable)
public void SetBlur(BlurEffect blurEffect)
{
using (_syncContext.EnsureLocked())
_blurVisual.SetIsVisible(enable ? 1 : 0);
{
_blurVisual.SetIsVisible(blurEffect == BlurEffect.Acrylic ? 1 : 0);
_micaVisual?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0);
}
}
public IDisposable BeginTransaction()

76
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;
@ -16,7 +17,9 @@ namespace Avalonia.Win32.WinRT.Composition
{
class WinUICompositorConnection : IRenderTimer
{
private readonly float? _backdropCornerRadius;
private readonly EglContext _syncContext;
private readonly ICompositionBrush _micaBrush;
private ICompositor _compositor;
private ICompositor2 _compositor2;
private ICompositor5 _compositor5;
@ -28,10 +31,11 @@ namespace Avalonia.Win32.WinRT.Composition
private ICompositionBrush _blurBrush;
private object _pumpLock = new object();
public WinUICompositorConnection(EglPlatformOpenGlInterface gl, object pumpLock)
public WinUICompositorConnection(EglPlatformOpenGlInterface gl, object pumpLock, float? backdropCornerRadius)
{
_gl = gl;
_pumpLock = pumpLock;
_backdropCornerRadius = backdropCornerRadius;
_syncContext = _gl.PrimaryEglContext;
_angle = (AngleWin32EglDisplay)_gl.Display;
_compositor = NativeWinRTMethods.CreateInstance<ICompositor>("Windows.UI.Composition.Compositor");
@ -42,13 +46,13 @@ namespace Avalonia.Win32.WinRT.Composition
using var device = MicroComRuntime.CreateProxyFor<IUnknown>(_angle.GetDirect3DDevice(), true);
_device = _compositorInterop.CreateGraphicsDevice(device);
_blurBrush = CreateBlurBrush();
_blurBrush = CreateAcrylicBlurBackdropBrush();
_micaBrush = CreateMicaBackdropBrush();
}
public EglPlatformOpenGlInterface Egl => _gl;
static bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface angle)
static bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface angle, float? backdropCornerRadius)
{
var tcs = new TaskCompletionSource<bool>();
var pumpLock = new object();
@ -63,7 +67,7 @@ namespace Avalonia.Win32.WinRT.Composition
dwSize = Marshal.SizeOf<NativeWinRTMethods.DispatcherQueueOptions>(),
threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT
});
connect = new WinUICompositorConnection(angle, pumpLock);
connect = new WinUICompositorConnection(angle, pumpLock, backdropCornerRadius);
AvaloniaLocator.CurrentMutable.BindToSelf(connect);
AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(connect);
tcs.SetResult(true);
@ -130,7 +134,8 @@ namespace Avalonia.Win32.WinRT.Composition
}
}
public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle)
public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle,
float? backdropCornerRadius)
{
const int majorRequired = 10;
const int buildRequired = 17134;
@ -143,7 +148,7 @@ namespace Avalonia.Win32.WinRT.Composition
{
try
{
TryCreateAndRegisterCore(angle);
TryCreateAndRegisterCore(angle, backdropCornerRadius);
return;
}
catch (Exception e)
@ -188,16 +193,37 @@ namespace Avalonia.Win32.WinRT.Composition
target.SetRoot(containerVisual);
using var blur = CreateBlurVisual();
using var blur = CreateBlurVisual(_blurBrush);
IVisual mica = null;
if (_micaBrush != null)
{
mica = CreateBlurVisual(_micaBrush);
containerChildren.InsertAtTop(mica);
}
var compositionRoundedRectangleGeometry = ClipVisual(blur, mica);
containerChildren.InsertAtTop(blur);
containerChildren.InsertAtTop(visual);
return new WinUICompositedWindow(_syncContext, _compositor, _pumpLock, target, surfaceInterop, visual, blur);
return new WinUICompositedWindow(_syncContext, _compositor, _pumpLock, target, surfaceInterop, visual,
blur, mica, compositionRoundedRectangleGeometry);
}
private ICompositionBrush CreateMicaBackdropBrush()
{
if (Win32Platform.WindowsVersion.Build < 22000)
return null;
using var compositorWithBlurredWallpaperBackdropBrush =
_compositor.QueryInterface<ICompositorWithBlurredWallpaperBackdropBrush>();
using var blurredWallpaperBackdropBrush =
compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush();
using var micaBackdropBrush = blurredWallpaperBackdropBrush?.QueryInterface<ICompositionBrush>();
return micaBackdropBrush.CloneReference();
}
private unsafe ICompositionBrush CreateBlurBrush()
private unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush()
{
using var backDropParameterFactory = NativeWinRTMethods.CreateActivationFactory<ICompositionEffectSourceParameterFactory>(
"Windows.UI.Composition.CompositionEffectSourceParameter");
@ -207,6 +233,7 @@ namespace Avalonia.Win32.WinRT.Composition
using var backDropParameterAsSource = backDropParameter.QueryInterface<IGraphicsEffectSource>();
var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource);
using var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect);
using var compositionEffectBrush = blurEffectFactory.CreateBrush();
using var backdrop = _compositor2.CreateBackdropBrush();
using var backdropBrush = backdrop.QueryInterface<ICompositionBrush>();
@ -214,18 +241,39 @@ namespace Avalonia.Win32.WinRT.Composition
var saturateEffect = new SaturationEffect(blurEffect);
using var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect);
using var sat = satEffectFactory.CreateBrush();
sat.SetSourceParameter(backdropString.Handle, backdropBrush);
return sat.QueryInterface<ICompositionBrush>();
compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush);
return compositionEffectBrush.QueryInterface<ICompositionBrush>();
}
private ICompositionRoundedRectangleGeometry ClipVisual(params IVisual[] containerVisuals)
{
if (!_backdropCornerRadius.HasValue)
return null;
using var roundedRectangleGeometry = _compositor5.CreateRoundedRectangleGeometry();
roundedRectangleGeometry.SetCornerRadius(new Vector2(_backdropCornerRadius.Value, _backdropCornerRadius.Value));
using var compositor6 = _compositor.QueryInterface<ICompositor6>();
using var compositionGeometry = roundedRectangleGeometry
.QueryInterface<ICompositionGeometry>();
using var geometricClipWithGeometry =
compositor6.CreateGeometricClipWithGeometry(compositionGeometry);
foreach (var visual in containerVisuals)
{
visual?.SetClip(geometricClipWithGeometry.QueryInterface<ICompositionClip>());
}
private unsafe IVisual CreateBlurVisual()
return roundedRectangleGeometry.CloneReference();
}
private unsafe IVisual CreateBlurVisual(ICompositionBrush compositionBrush)
{
using var spriteVisual = _compositor.CreateSpriteVisual();
using var visual = spriteVisual.QueryInterface<IVisual>();
using var visual2 = spriteVisual.QueryInterface<IVisual2>();
spriteVisual.SetBrush(_blurBrush);
spriteVisual.SetBrush(compositionBrush);
visual.SetIsVisible(0);
visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f));

10
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs

@ -15,7 +15,7 @@ namespace Avalonia.Win32.WinRT.Composition
private EglPlatformOpenGlInterface _egl;
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private IRef<WinUICompositedWindow> _window;
private bool _enableBlur;
private BlurEffect _blurEffect;
public WinUiCompositedWindowSurface(WinUICompositorConnection connection, IEglWindowGlPlatformSurfaceInfo info) : base()
{
@ -31,7 +31,7 @@ namespace Avalonia.Win32.WinRT.Composition
if (_window?.Item == null)
{
_window = RefCountable.Create(_connection.CreateWindow(_info.Handle));
_window.Item.SetBlur(_enableBlur);
_window.Item.SetBlur(_blurEffect);
}
return new CompositionRenderTarget(_egl, _window, _info);
@ -100,10 +100,10 @@ namespace Avalonia.Win32.WinRT.Composition
}
}
public void SetBlur(bool enable)
public void SetBlur(BlurEffect blurEffect)
{
_enableBlur = enable;
_window?.Item?.SetBlur(enable);
_blurEffect = blurEffect;
_window?.Item?.SetBlur(blurEffect);
}
public void Dispose()

11
src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs

@ -1,7 +1,14 @@
namespace Avalonia.Win32.WinRT
{
public interface IBlurHost
public enum BlurEffect
{
void SetBlur(bool enable);
None,
Acrylic,
Mica
}
internal interface IBlurHost
{
void SetBlur(BlurEffect enable);
}
}

123
src/Windows/Avalonia.Win32/WinRT/winrt.idl

@ -358,6 +358,12 @@ interface ICompositor2 : IInspectable
[overload("CreateStepEasingFunction")] HRESULT CreateStepEasingFunctionWithStepCount([in] INT32 stepCount, [out] [retval] void** result);
}
[uuid(0D8FB190-F122-5B8D-9FDD-543B0D8EB7F3)]
interface ICompositorWithBlurredWallpaperBackdropBrush : IInspectable
{
HRESULT TryCreateBlurredWallpaperBackdropBrush([out] [retval] ICompositionBackdropBrush** result);
}
[uuid(08E05581-1AD1-4F97-9757-402D76E4233B)]
interface ISpriteVisual : IInspectable
{
@ -520,6 +526,13 @@ enum CompositionCompositeMode
MinBlend,
}
[contract(Windows.Foundation.UniversalApiContract, 2.0)]
[exclusiveto(Windows.UI.Composition.CompositionClip)]
[uuid(1CCD2A52-CFC7-4ACE-9983-146BB8EB6A3C)]
interface ICompositionClip : IInspectable
{
}
[uuid(117E202D-A859-4C89-873B-C2AA566788E3)]
interface IVisual : IInspectable
{
@ -531,8 +544,8 @@ interface IVisual : IInspectable
[propput] HRESULT BorderMode([in] CompositionBorderMode value);
[propget] HRESULT CenterPoint([out] [retval] Vector3* value);
[propput] HRESULT CenterPoint([in] Vector3 value);
[propget] HRESULT Clip([out] [retval]void** value);
[propput] HRESULT Clip([in] void* value);
[propget] HRESULT Clip([out] [retval] ICompositionClip** value);
[propput] HRESULT Clip([in] ICompositionClip* value);
[propget] HRESULT CompositeMode([out] [retval] CompositionCompositeMode* value);
[propput] HRESULT CompositeMode([in] CompositionCompositeMode value);
[propget] HRESULT IsVisible([out] [retval] boolean* value);
@ -692,6 +705,99 @@ interface ICompositionScopedBatch : IInspectable
[eventremove] HRESULT RemoveCompleted([in] int token);
}
[contract(Windows.Foundation.UniversalApiContract, 6.0)]
[exclusiveto(Windows.UI.Composition.CompositionRoundedRectangleGeometry)]
[uuid(8770C822-1D50-4B8B-B013-7C9A0E46935F)]
interface ICompositionRoundedRectangleGeometry : IInspectable
{
[propget] HRESULT CornerRadius([out] [retval] Vector2* value);
[propput] HRESULT CornerRadius([in] Vector2 value);
[propget] HRESULT Offset([out] [retval] Vector2* value);
[propput] HRESULT Offset([in] Vector2 value);
[propget] HRESULT Size([out] [retval] Vector2* value);
[propput] HRESULT Size([in] Vector2 value);
}
[contract(Windows.Foundation.UniversalApiContract, 6.0)]
[exclusiveto(Windows.UI.Composition.CompositionGeometry)]
[uuid(E985217C-6A17-4207-ABD8-5FD3DD612A9D)]
interface ICompositionGeometry : IInspectable
{
[propget] HRESULT TrimEnd([out] [retval] FLOAT* value);
[propput] HRESULT TrimEnd([in] FLOAT value);
[propget] HRESULT TrimOffset([out] [retval] FLOAT* value);
[propput] HRESULT TrimOffset([in] FLOAT value);
[propget] HRESULT TrimStart([out] [retval] FLOAT* value);
[propput] HRESULT TrimStart([in] FLOAT value);
}
[uuid(401B61BB-0007-4363-B1F3-6BCC003FB83E)]
interface ICompositionSpriteShape : IInspectable
{
[propget] HRESULT GetFillBrush([out] [retval] ICompositionBrush** value);
[propput] HRESULT SetFillBrush([in] ICompositionBrush* value);
[propget] HRESULT Geometry([out] [retval] ICompositionGeometry** value);
[propput] HRESULT Geometry([in] ICompositionGeometry* value);
[propget] HRESULT IsStrokeNonScaling([out] [retval] boolean* value);
[propput] HRESULT IsStrokeNonScaling([in] boolean value);
[propget] HRESULT StrokeBrush([out] [retval] ICompositionBrush** value);
[propput] HRESULT StrokeBrush([in] ICompositionBrush* value);
[propget] HRESULT StrokeDashArray();
[propget] HRESULT StrokeDashCap();
[propput] HRESULT StrokeDashCap();
[propget] HRESULT StrokeDashOffset();
[propput] HRESULT StrokeDashOffset();
[propget] HRESULT StrokeEndCap();
[propput] HRESULT StrokeEndCap();
[propget] HRESULT StrokeLineJoin();
[propput] HRESULT StrokeLineJoin();
[propget] HRESULT StrokeMiterLimit();
[propput] HRESULT StrokeMiterLimit();
[propget] HRESULT StrokeStartCap();
[propput] HRESULT StrokeStartCap();
[propget] HRESULT StrokeThickness();
[propput] HRESULT StrokeThickness();
}
[contract(Windows.Foundation.UniversalApiContract, 6.0)]
[exclusiveto(Windows.UI.Composition.CompositionShape)]
[uuid(B47CE2F7-9A88-42C4-9E87-2E500CA8688C)]
interface ICompositionShape : IInspectable
{
[propget] HRESULT CenterPoint([out] [retval] Vector2* value);
[propput] HRESULT CenterPoint([in] Vector2 value);
}
[uuid(42d4219a-be1b-5091-8f1e-90270840fc2d)]
interface IVectorOfCompositionShape : IInspectable
{
HRESULT GetAt();
[propget] HRESULT GetSize();
HRESULT GetView();
HRESULT IndexOf();
HRESULT SetAt();
HRESULT InsertAt();
HRESULT RemoveAt();
HRESULT Append([in] ICompositionShape* value);
HRESULT RemoveAtEnd();
HRESULT Clear();
}
[contract(Windows.Foundation.UniversalApiContract, 7.0)]
[exclusiveto(Windows.UI.Composition.CompositionGeometricClip)]
[uuid(C840B581-81C9-4444-A2C1-CCAECE3A50E5)]
interface ICompositionGeometricClip : IInspectable
{
[propget] HRESULT Geometry([out] [retval] ICompositionGeometry** value);
[propput] HRESULT Geometry([in] ICompositionGeometry* value);
}
[uuid(F2BD13C3-BA7E-4B0F-9126-FFB7536B8176)]
interface IShapeVisual : IInspectable
{
[propget] HRESULT Shapes([out] [retval] IUnknown** value);
}
[uuid(48EA31AD-7FCD-4076-A79C-90CC4B852C9B)]
interface ICompositor5 : IInspectable
{
@ -709,10 +815,19 @@ interface ICompositor5 : IInspectable
[overload("CreatePathGeometry")] HRESULT CreatePathGeometryWithPath([in] void* path, [out] [retval] void** result);
HRESULT CreatePathKeyFrameAnimation([out] [retval] void** result);
HRESULT CreateRectangleGeometry([out] [retval] void** result);
HRESULT CreateRoundedRectangleGeometry([out] [retval] void** result);
HRESULT CreateShapeVisual([out] [retval] void** result);
HRESULT CreateRoundedRectangleGeometry([out] [retval] ICompositionRoundedRectangleGeometry** result);
HRESULT CreateShapeVisual([out] [retval] IShapeVisual** result);
[overload("CreateSpriteShape")] HRESULT CreateSpriteShape([out] [retval] void** result);
[overload("CreateSpriteShape")] HRESULT CreateSpriteShapeWithGeometry([in] void* geometry, [out] [retval] void** result);
HRESULT CreateViewBox([out] [retval] void** result);
HRESULT RequestCommitAsync([out] [retval] IAsyncAction** operation);
}
[contract(Windows.Foundation.UniversalApiContract, 7.0)]
[exclusiveto(Windows.UI.Composition.Compositor)]
[uuid(7A38B2BD-CEC8-4EEB-830F-D8D07AEDEBC3)]
interface ICompositor6 : IInspectable
{
[overload("CreateGeometricClip")] HRESULT CreateGeometricClip([out] [retval] ICompositionGeometricClip** result);
[overload("CreateGeometricClip")] HRESULT CreateGeometricClipWithGeometry([in] ICompositionGeometry* geometry, [out] [retval] ICompositionGeometricClip** result);
}

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

@ -376,7 +376,13 @@ namespace Avalonia.Win32
{
if (_isUsingComposition)
{
_blurHost?.SetBlur(transparencyLevel >= WindowTransparencyLevel.Blur);
_blurHost?.SetBlur(transparencyLevel switch
{
WindowTransparencyLevel.Mica => BlurEffect.Mica,
WindowTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic,
WindowTransparencyLevel.Blur => BlurEffect.Acrylic,
_ => BlurEffect.None
});
return transparencyLevel;
}
@ -510,7 +516,7 @@ namespace Avalonia.Win32
public void Activate()
{
SetActiveWindow(_hwnd);
SetForegroundWindow(_hwnd);
}
public IPopupImpl CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this);
@ -1003,6 +1009,7 @@ namespace Avalonia.Win32
if (!Design.IsDesignMode && activate)
{
SetFocus(_hwnd);
SetForegroundWindow(_hwnd);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save