Browse Source

Merge branch 'fixes/textProcessingBugs' of https://github.com/Gillibald/Avalonia into fixes/textProcessingBugs

pull/8094/head
Benedikt Stebner 4 years ago
parent
commit
b36804fc5a
  1. 2
      Avalonia.sln
  2. 2
      build/CoreLibraries.props
  3. 2
      build/ImageSharp.props
  4. 5
      native/Avalonia.Native/inc/rendertarget.h
  5. 17
      native/Avalonia.Native/src/OSX/AutoFitContentView.h
  6. 106
      native/Avalonia.Native/src/OSX/AutoFitContentView.mm
  7. 78
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  8. 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  9. 11
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  10. 27
      native/Avalonia.Native/src/OSX/AvnView.h
  11. 712
      native/Avalonia.Native/src/OSX/AvnView.mm
  12. 441
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  13. 17
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  14. 18
      native/Avalonia.Native/src/OSX/IWindowStateChanged.h
  15. 9
      native/Avalonia.Native/src/OSX/PopupImpl.h
  16. 68
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  17. 24
      native/Avalonia.Native/src/OSX/ResizeScope.h
  18. 18
      native/Avalonia.Native/src/OSX/ResizeScope.mm
  19. 2
      native/Avalonia.Native/src/OSX/SystemDialogs.mm
  20. 130
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  21. 589
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  22. 96
      native/Avalonia.Native/src/OSX/WindowImpl.h
  23. 552
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  24. 17
      native/Avalonia.Native/src/OSX/WindowInterfaces.h
  25. 25
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  26. 12
      native/Avalonia.Native/src/OSX/app.mm
  27. 4
      native/Avalonia.Native/src/OSX/automation.h
  28. 3
      native/Avalonia.Native/src/OSX/automation.mm
  29. 3
      native/Avalonia.Native/src/OSX/common.h
  30. 1
      native/Avalonia.Native/src/OSX/cursor.mm
  31. 7
      native/Avalonia.Native/src/OSX/main.mm
  32. 1
      native/Avalonia.Native/src/OSX/menu.h
  33. 6
      native/Avalonia.Native/src/OSX/menu.mm
  34. 4
      native/Avalonia.Native/src/OSX/rendertarget.mm
  35. 77
      native/Avalonia.Native/src/OSX/window.h
  36. 2590
      native/Avalonia.Native/src/OSX/window.mm
  37. 1
      samples/BindingDemo/BindingDemo.csproj
  38. 1
      samples/ControlCatalog.NetCore/Program.cs
  39. 2
      samples/ControlCatalog/ControlCatalog.csproj
  40. 10
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  41. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  42. 2
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  43. 3
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  44. 1
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  45. 3
      samples/Previewer/Previewer.csproj
  46. 1
      samples/RenderDemo/RenderDemo.csproj
  47. 1
      samples/Sandbox/Sandbox.csproj
  48. 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  49. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  50. 1
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  51. 6
      src/Avalonia.Base/Collections/AvaloniaList.cs
  52. 19
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  53. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  54. 18
      src/Avalonia.Base/Input/InputElement.cs
  55. 2
      src/Avalonia.Base/Layout/UniformGridLayout.cs
  56. 51
      src/Avalonia.Base/Logging/ILogSink.cs
  57. 38
      src/Avalonia.Base/Logging/TraceLogSink.cs
  58. 4
      src/Avalonia.Base/Media/ConicGradientBrush.cs
  59. 8
      src/Avalonia.Base/StyledPropertyBase.cs
  60. 5
      src/Avalonia.Base/Styling/IStyleInstance.cs
  61. 10
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  62. 31
      src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs
  63. 9
      src/Avalonia.Base/Styling/Setter.cs
  64. 35
      src/Avalonia.Base/Styling/StyleInstance.cs
  65. 3
      src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
  66. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  67. 33
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  68. 75
      src/Avalonia.Controls/Canvas.cs
  69. 2
      src/Avalonia.Controls/ContextMenu.cs
  70. 4
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  71. 2
      src/Avalonia.Controls/DockPanel.cs
  72. 2
      src/Avalonia.Controls/MaskedTextBox.cs
  73. 22
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  74. 31
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  75. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  76. 56
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  77. 42
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  78. 152
      src/Avalonia.Controls/Primitives/Popup.cs
  79. 39
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  80. 6
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  81. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  82. 6
      src/Avalonia.Controls/Primitives/Track.cs
  83. 4
      src/Avalonia.Controls/Slider.cs
  84. 2
      src/Avalonia.Controls/SplitView.cs
  85. 31
      src/Avalonia.Controls/TextBox.cs
  86. 53
      src/Avalonia.Controls/Viewbox.cs
  87. 4
      src/Avalonia.Controls/Window.cs
  88. 1
      src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj
  89. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  90. 13
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
  91. 6
      src/Avalonia.FreeDesktop/NativeMethods.cs
  92. 7
      src/Avalonia.Native/WindowImpl.cs
  93. 1
      src/Avalonia.Native/avn.idl
  94. 18
      src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml
  95. 22
      src/Avalonia.Themes.Default/Controls/PopupRoot.xaml
  96. 1
      src/Avalonia.Themes.Default/Controls/TextBox.xaml
  97. 3
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  98. 16
      src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml
  99. 14
      src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml
  100. 1
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

2
Avalonia.sln

@ -99,7 +99,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props build\Moq.props = build\Moq.props
@ -118,6 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\System.Memory.props = build\System.Memory.props build\System.Memory.props = build\System.Memory.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props build\XUnit.props = build\XUnit.props
build\ImageSharp.props = build\ImageSharp.props
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"

2
build/CoreLibraries.props

@ -3,8 +3,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Base/Avalonia.Base.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Base/Avalonia.Base.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />

2
build/Magick.NET-Q16-AnyCPU.props → build/ImageSharp.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.9.0.2" /> <PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
native/Avalonia.Native/inc/rendertarget.h

@ -1,3 +1,8 @@
#pragma once
#include "com.h"
#include "comimpl.h"
#include "avalonia-native.h"
@protocol IRenderTarget @protocol IRenderTarget
-(void) setNewLayer: (CALayer*) layer; -(void) setNewLayer: (CALayer*) layer;

17
native/Avalonia.Native/src/OSX/AutoFitContentView.h

@ -0,0 +1,17 @@
//
// Created by Dan Walmsley on 05/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#pragma once
#import <Foundation/Foundation.h>
#include "avalonia-native.h"
@interface AutoFitContentView : NSView
-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
-(void) ShowTitleBar: (bool) show;
-(void) SetTitleBarHeightHint: (double) height;
-(void) ShowBlur: (bool) show;
@end

106
native/Avalonia.Native/src/OSX/AutoFitContentView.mm

@ -0,0 +1,106 @@
//
// Created by Dan Walmsley on 05/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#include "AvnView.h"
#include "AutoFitContentView.h"
#include "WindowInterfaces.h"
#include "WindowProtocol.h"
@implementation AutoFitContentView
{
NSVisualEffectView* _titleBarMaterial;
NSBox* _titleBarUnderline;
NSView* _content;
NSVisualEffectView* _blurBehind;
double _titleBarHeightHint;
bool _settingSize;
}
-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
{
_titleBarHeightHint = -1;
_content = content;
_settingSize = false;
[self setAutoresizesSubviews:true];
[self setWantsLayer:true];
_titleBarMaterial = [NSVisualEffectView new];
[_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
[_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
[_titleBarMaterial setWantsLayer:true];
_titleBarMaterial.hidden = true;
_titleBarUnderline = [NSBox new];
_titleBarUnderline.boxType = NSBoxSeparator;
_titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
_titleBarUnderline.hidden = true;
[self addSubview:_titleBarMaterial];
[self addSubview:_titleBarUnderline];
_blurBehind = [NSVisualEffectView new];
[_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
[_blurBehind setMaterial:NSVisualEffectMaterialLight];
[_blurBehind setWantsLayer:true];
_blurBehind.hidden = true;
[_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self addSubview:_blurBehind];
[self addSubview:_content];
[self setWantsLayer:true];
return self;
}
-(void) ShowBlur:(bool)show
{
_blurBehind.hidden = !show;
}
-(void) ShowTitleBar: (bool) show
{
_titleBarMaterial.hidden = !show;
_titleBarUnderline.hidden = !show;
}
-(void) SetTitleBarHeightHint: (double) height
{
_titleBarHeightHint = height;
[self setFrameSize:self.frame.size];
}
-(void)setFrameSize:(NSSize)newSize
{
if(_settingSize)
{
return;
}
_settingSize = true;
[super setFrameSize:newSize];
auto window = static_cast<id <AvnWindowProtocol>>([self window]);
// TODO get actual titlebar size
double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
NSRect tbar;
tbar.origin.x = 0;
tbar.origin.y = newSize.height - height;
tbar.size.width = newSize.width;
tbar.size.height = height;
[_titleBarMaterial setFrame:tbar];
tbar.size.height = height < 1 ? 0 : 1;
[_titleBarUnderline setFrame:tbar];
_settingSize = false;
}
@end

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

@ -7,6 +7,24 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391E45702740FE9DD69695 /* ResizeScope.mm */; };
1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 183919BF108EB72A029F7671 /* WindowImpl.mm */; };
183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391DB45C7D892E61BF388C /* WindowInterfaces.h */; };
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */; };
183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */; };
1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391BBB7782C296D424071F /* INSWindowHolder.h */; };
1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */; };
183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391CD090AA776E7E841AC9 /* WindowImpl.h */; };
18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839166350F32661F3ABD70F /* AutoFitContentView.mm */; };
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839155B28B20FFB672D29C6 /* AvnWindow.mm */; };
18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183910513F396141938832B5 /* PopupImpl.h */; };
18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839171D898F9BFC1373631A /* ResizeScope.h */; };
18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */ = {isa = PBXBuildFile; fileRef = 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */; };
18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839132D0E2454D911F1D1F9 /* AvnView.mm */; };
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391BB698579F40F1783F31 /* PopupImpl.mm */; };
18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */; };
18391ED5F611FF62C45F196D /* AvnView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391D1669284AD2EC9E866A /* AvnView.h */; };
18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839122E037567BDD1D09DEB /* WindowProtocol.h */; };
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; }; 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; }; 1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; }; 1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
@ -28,13 +46,30 @@
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; }; AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; }; AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; }; AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
183910513F396141938832B5 /* PopupImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupImpl.h; sourceTree = "<group>"; };
1839122E037567BDD1D09DEB /* WindowProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowProtocol.h; sourceTree = "<group>"; };
1839132D0E2454D911F1D1F9 /* AvnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnView.mm; sourceTree = "<group>"; };
183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IWindowStateChanged.h; sourceTree = "<group>"; };
1839155B28B20FFB672D29C6 /* AvnWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnWindow.mm; sourceTree = "<group>"; };
183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowBaseImpl.h; sourceTree = "<group>"; };
18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoFitContentView.h; sourceTree = "<group>"; };
1839166350F32661F3ABD70F /* AutoFitContentView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AutoFitContentView.mm; sourceTree = "<group>"; };
18391676ECF0E983F4964357 /* WindowBaseImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowBaseImpl.mm; sourceTree = "<group>"; };
1839171D898F9BFC1373631A /* ResizeScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResizeScope.h; sourceTree = "<group>"; };
18391884C7476DA4E53A492D /* AvnPanelWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnPanelWindow.mm; sourceTree = "<group>"; };
183919BF108EB72A029F7671 /* WindowImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowImpl.mm; sourceTree = "<group>"; };
18391BB698579F40F1783F31 /* PopupImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PopupImpl.mm; sourceTree = "<group>"; };
18391BBB7782C296D424071F /* INSWindowHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INSWindowHolder.h; sourceTree = "<group>"; };
18391CD090AA776E7E841AC9 /* WindowImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowImpl.h; sourceTree = "<group>"; };
18391D1669284AD2EC9E866A /* AvnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvnView.h; sourceTree = "<group>"; };
18391DB45C7D892E61BF388C /* WindowInterfaces.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowInterfaces.h; sourceTree = "<group>"; };
18391E45702740FE9DD69695 /* ResizeScope.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ResizeScope.mm; sourceTree = "<group>"; };
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; }; 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; }; 1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; };
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; }; 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; };
@ -48,7 +83,6 @@
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; }; 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; }; 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; }; 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
@ -62,7 +96,6 @@
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; }; AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; };
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; }; AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; }; AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
@ -118,8 +151,6 @@
AB661C212148288600291242 /* common.h */, AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */, 379860FE214DA0C000CD0246 /* KeyTransform.h */,
37E2330E21583241000CB7E2 /* KeyTransform.mm */, 37E2330E21583241000CB7E2 /* KeyTransform.mm */,
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */, AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */, 37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */, 520624B222973F4100C4DCEF /* menu.mm */,
@ -130,6 +161,24 @@
37C09D8721580FE4006A6758 /* SystemDialogs.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */, AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */, AB661C1C2148230E00291242 /* Frameworks */,
18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */,
18391BBB7782C296D424071F /* INSWindowHolder.h */,
183919BF108EB72A029F7671 /* WindowImpl.mm */,
18391CD090AA776E7E841AC9 /* WindowImpl.h */,
183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */,
18391E45702740FE9DD69695 /* ResizeScope.mm */,
1839171D898F9BFC1373631A /* ResizeScope.h */,
1839132D0E2454D911F1D1F9 /* AvnView.mm */,
18391D1669284AD2EC9E866A /* AvnView.h */,
1839166350F32661F3ABD70F /* AutoFitContentView.mm */,
18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */,
18391884C7476DA4E53A492D /* AvnPanelWindow.mm */,
1839122E037567BDD1D09DEB /* WindowProtocol.h */,
1839155B28B20FFB672D29C6 /* AvnWindow.mm */,
18391DB45C7D892E61BF388C /* WindowInterfaces.h */,
18391BB698579F40F1783F31 /* PopupImpl.mm */,
183910513F396141938832B5 /* PopupImpl.h */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -150,6 +199,16 @@
files = ( files = (
37155CE4233C00EB0034DCE9 /* menu.h in Headers */, 37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */, BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */,
183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */,
1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */,
183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */,
18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */,
18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */,
18391ED5F611FF62C45F196D /* AvnView.h in Headers */,
18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */,
18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */,
183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */,
18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -228,7 +287,14 @@
1A465D10246AB61600C5858B /* dnd.mm in Sources */, 1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */, 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */,
1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */,
18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */,
18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */,
18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */,
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@ -56,10 +56,14 @@
</MacroExpansion> </MacroExpansion>
<CommandLineArguments> <CommandLineArguments>
<CommandLineArgument <CommandLineArgument
argument = "bin/Debug/netcoreapp3.1/ControlCatalog.NetCore.dll" argument = "bin/Debug/net6.0/ControlCatalog.NetCore.dll"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

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

@ -0,0 +1,11 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#pragma once
#define IS_NSPANEL
#include "AvnWindow.mm"

27
native/Avalonia.Native/src/OSX/AvnView.h

@ -0,0 +1,27 @@
//
// Created by Dan Walmsley on 05/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#pragma once
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include "common.h"
#include "WindowImpl.h"
#include "KeyTransform.h"
@class AvnAccessibilityElement;
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end

712
native/Avalonia.Native/src/OSX/AvnView.mm

@ -0,0 +1,712 @@
//
// Created by Dan Walmsley on 05/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <AppKit/AppKit.h>
#include "AvnView.h"
#include "automation.h"
#import "WindowInterfaces.h"
@implementation AvnView
{
ComPtr<WindowBaseImpl> _parent;
NSTrackingArea* _area;
bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
AvnInputModifiers _modifierState;
NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget;
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
}
- (void)onClosed
{
@synchronized (self)
{
_parent = nullptr;
}
}
- (NSEvent*) lastMouseDownEvent
{
return _lastMouseDownEvent;
}
- (void) updateRenderTarget
{
[_renderTarget resize:_lastPixelSize withScale:static_cast<float>([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
}
-(AvnView*) initWithParent: (WindowBaseImpl*) parent
{
self = [super init];
_renderTarget = parent->renderTarget;
[self setWantsLayer:YES];
[self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
_parent = parent;
_area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
[self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
_modifierState = AvnInputModifiersNone;
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)wantsUpdateLayer
{
return YES;
}
- (void)setLayer:(CALayer *)layer
{
[_renderTarget setNewLayer: layer];
[super setLayer: layer];
}
- (BOOL)isOpaque
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return true;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
return true;
}
- (BOOL)canBecomeKeyView
{
return true;
}
-(void)setFrameSize:(NSSize)newSize
{
[super setFrameSize:newSize];
if(_area != nullptr)
{
[self removeTrackingArea:_area];
_area = nullptr;
}
if (_parent == nullptr)
{
return;
}
NSRect rect = NSZeroRect;
rect.size = newSize;
NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag;
_area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr];
[self addTrackingArea:_area];
_parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
{
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
}
}
- (void)updateLayer
{
AvnInsidePotentialDeadlock deadlock;
if (_parent == nullptr)
{
return;
}
_parent->BaseEvents->RunRenderPriorityJobs();
if (_parent == nullptr)
{
return;
}
_parent->BaseEvents->Paint();
}
- (void)drawRect:(NSRect)dirtyRect
{
return;
}
-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
{
@autoreleasepool {
[_renderTarget setSwFrame:fb];
dispose->Release();
}
}
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self bounds].size.height - pt.Y;
return pt;
}
+ (AvnPoint)toAvnPoint:(CGPoint)p
{
AvnPoint result;
result.X = p.x;
result.Y = p.y;
return result;
}
- (void) viewDidChangeBackingProperties
{
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
if(_parent != nullptr)
{
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
}
[super viewDidChangeBackingProperties];
}
- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
if(_parent == nullptr)
{
return TRUE;
}
auto parentWindow = _parent->GetWindowProtocol();
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{
if(trigerInputWhenDisabled)
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
window->WindowEvents->GotInputWhenDisabled();
}
}
return TRUE;
}
return FALSE;
}
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
bool triggerInputWhenDisabled = type != Move;
if([self ignoreUserInput: triggerInputWhenDisabled])
{
return;
}
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta = { 0, 0};
if(type == Wheel)
{
auto speed = 5;
if([event hasPreciseScrollingDeltas])
{
speed = 50;
}
delta.X = [event scrollingDeltaX] / speed;
delta.Y = [event scrollingDeltaY] / speed;
if(delta.X == 0 && delta.Y == 0)
{
return;
}
}
else if (type == Magnify)
{
delta.X = delta.Y = [event magnification];
}
else if (type == Rotate)
{
delta.X = delta.Y = [event rotation];
}
else if (type == Swipe)
{
delta.X = [event deltaX];
delta.Y = [event deltaY];
}
uint32 timestamp = static_cast<uint32>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]];
if(type != Move ||
(
[self window] != nil &&
(
[[self window] firstResponder] == nil
|| ![[[self window] firstResponder] isKindOfClass: [NSView class]]
)
)
)
[self becomeFirstResponder];
if(_parent != nullptr)
{
_parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
}
[super mouseMoved:event];
}
- (BOOL) resignFirstResponder
{
_parent->BaseEvents->LostFocus();
return YES;
}
- (void)mouseMoved:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
}
- (void)mouseDown:(NSEvent *)event
{
_isLeftPressed = true;
_lastMouseDownEvent = event;
[self mouseEvent:event withType:LeftButtonDown];
}
- (void)otherMouseDown:(NSEvent *)event
{
_lastMouseDownEvent = event;
switch(event.buttonNumber)
{
case 2:
case 3:
_isMiddlePressed = true;
[self mouseEvent:event withType:MiddleButtonDown];
break;
case 4:
_isXButton1Pressed = true;
[self mouseEvent:event withType:XButton1Down];
break;
case 5:
_isXButton2Pressed = true;
[self mouseEvent:event withType:XButton2Down];
break;
default:
break;
}
}
- (void)rightMouseDown:(NSEvent *)event
{
_isRightPressed = true;
_lastMouseDownEvent = event;
[self mouseEvent:event withType:RightButtonDown];
}
- (void)mouseUp:(NSEvent *)event
{
_isLeftPressed = false;
[self mouseEvent:event withType:LeftButtonUp];
}
- (void)otherMouseUp:(NSEvent *)event
{
switch(event.buttonNumber)
{
case 2:
case 3:
_isMiddlePressed = false;
[self mouseEvent:event withType:MiddleButtonUp];
break;
case 4:
_isXButton1Pressed = false;
[self mouseEvent:event withType:XButton1Up];
break;
case 5:
_isXButton2Pressed = false;
[self mouseEvent:event withType:XButton2Up];
break;
default:
break;
}
}
- (void)rightMouseUp:(NSEvent *)event
{
_isRightPressed = false;
[self mouseEvent:event withType:RightButtonUp];
}
- (void)mouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super mouseDragged:event];
}
- (void)otherMouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super otherMouseDragged:event];
}
- (void)rightMouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super rightMouseDragged:event];
}
- (void)scrollWheel:(NSEvent *)event
{
[self mouseEvent:event withType:Wheel];
[super scrollWheel:event];
}
- (void)magnifyWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Magnify];
[super magnifyWithEvent:event];
}
- (void)rotateWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Rotate];
[super rotateWithEvent:event];
}
- (void)swipeWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Swipe];
[super swipeWithEvent:event];
}
- (void)mouseEntered:(NSEvent *)event
{
[super mouseEntered:event];
}
- (void)mouseExited:(NSEvent *)event
{
[self mouseEvent:event withType:LeaveWindow];
[super mouseExited:event];
}
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
if([self ignoreUserInput: false])
{
return;
}
auto key = s_KeyMap[[event keyCode]];
uint32_t timestamp = static_cast<uint32_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]];
if(_parent != nullptr)
{
_lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
}
}
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
bool result = _lastKeyHandled;
_lastKeyHandled = false;
return result;
}
- (void)flagsChanged:(NSEvent *)event
{
auto newModifierState = [self getModifiers:[event modifierFlags]];
bool isAltCurrentlyPressed = (_modifierState & Alt) == Alt;
bool isControlCurrentlyPressed = (_modifierState & Control) == Control;
bool isShiftCurrentlyPressed = (_modifierState & Shift) == Shift;
bool isCommandCurrentlyPressed = (_modifierState & Windows) == Windows;
bool isAltPressed = (newModifierState & Alt) == Alt;
bool isControlPressed = (newModifierState & Control) == Control;
bool isShiftPressed = (newModifierState & Shift) == Shift;
bool isCommandPressed = (newModifierState & Windows) == Windows;
if (isAltPressed && !isAltCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if (isAltCurrentlyPressed && !isAltPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if (isControlPressed && !isControlCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if (isControlCurrentlyPressed && !isControlPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if (isShiftPressed && !isShiftCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if(isShiftCurrentlyPressed && !isShiftPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if(isCommandPressed && !isCommandCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if(isCommandCurrentlyPressed && ! isCommandPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
_modifierState = newModifierState;
[[self inputContext] handleEvent:event];
[super flagsChanged:event];
}
- (void)keyDown:(NSEvent *)event
{
[self keyboardEvent:event withType:KeyDown];
[[self inputContext] handleEvent:event];
[super keyDown:event];
}
- (void)keyUp:(NSEvent *)event
{
[self keyboardEvent:event withType:KeyUp];
[super keyUp:event];
}
- (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod
{
unsigned int rv = 0;
if (mod & NSEventModifierFlagControl)
rv |= Control;
if (mod & NSEventModifierFlagShift)
rv |= Shift;
if (mod & NSEventModifierFlagOption)
rv |= Alt;
if (mod & NSEventModifierFlagCommand)
rv |= Windows;
if (_isLeftPressed)
rv |= LeftMouseButton;
if (_isMiddlePressed)
rv |= MiddleMouseButton;
if (_isRightPressed)
rv |= RightMouseButton;
if (_isXButton1Pressed)
rv |= XButton1MouseButton;
if (_isXButton2Pressed)
rv |= XButton2MouseButton;
return (AvnInputModifiers)rv;
}
- (BOOL)hasMarkedText
{
return _lastKeyHandled;
}
- (NSRange)markedRange
{
return NSMakeRange(NSNotFound, 0);
}
- (NSRange)selectedRange
{
return NSMakeRange(NSNotFound, 0);
}
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
}
- (void)unmarkText
{
}
- (NSArray<NSString *> *)validAttributesForMarkedText
{
return [NSArray new];
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
return [NSAttributedString new];
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
if(!_lastKeyHandled)
{
if(_parent != nullptr)
{
_lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
}
}
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point
{
return 0;
}
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
CGRect result = { 0 };
return result;
}
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
{
auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
auto avnPoint = [AvnView toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
auto effects = ConvertDragDropEffects(nsop);
int reffects = (int)_parent->BaseEvents
->DragEvent(type, point, modifiers, effects,
CreateClipboard([info draggingPasteboard], nil),
GetAvnDataObjectHandleFromDraggingInfo(info));
NSDragOperation ret = static_cast<NSDragOperation>(0);
// Ensure that the managed part didn't add any new effects
reffects = (int)effects & reffects;
// OSX requires exactly one operation
if((reffects & (int)AvnDragDropEffects::Copy) != 0)
ret = NSDragOperationCopy;
else if((reffects & (int)AvnDragDropEffects::Move) != 0)
ret = NSDragOperationMove;
else if((reffects & (int)AvnDragDropEffects::Link) != 0)
ret = NSDragOperationLink;
if(ret == 0)
ret = NSDragOperationNone;
return ret;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
[self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
}
- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
{
}
- (AvnPlatformResizeReason)getResizeReason
{
return _resizeReason;
}
- (void)setResizeReason:(AvnPlatformResizeReason)reason
{
_resizeReason = reason;
}
- (AvnAccessibilityElement *) accessibilityChild
{
if (_accessibilityChild == nil)
{
auto peer = _parent->BaseEvents->GetAutomationPeer();
if (peer == nil)
return nil;
_accessibilityChild = [AvnAccessibilityElement acquire:peer];
}
return _accessibilityChild;
}
- (NSArray *)accessibilityChildren
{
auto child = [self accessibilityChild];
return NSAccessibilityUnignoredChildrenForOnlyChild(child);
}
- (id)accessibilityHitTest:(NSPoint)point
{
return [[self accessibilityChild] accessibilityHitTest:point];
}
- (id)accessibilityFocusedUIElement
{
return [[self accessibilityChild] accessibilityFocusedUIElement];
}
@end

441
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -0,0 +1,441 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <AppKit/AppKit.h>
#import "WindowProtocol.h"
#import "WindowBaseImpl.h"
#ifdef IS_NSPANEL
#define BASE_CLASS NSPanel
#define CLASS_NAME AvnPanel
#else
#define BASE_CLASS NSWindow
#define CLASS_NAME AvnWindow
#endif
#import <AppKit/AppKit.h>
#include "common.h"
#include "menu.h"
#include "automation.h"
#include "WindowBaseImpl.h"
#include "WindowImpl.h"
#include "AvnView.h"
#include "WindowInterfaces.h"
#include "PopupImpl.h"
@implementation CLASS_NAME
{
ComPtr<WindowBaseImpl> _parent;
bool _closed;
bool _isEnabled;
bool _isExtended;
AvnMenu* _menu;
}
-(void) setIsExtended:(bool)value;
{
_isExtended = value;
}
-(bool) isDialog
{
return _parent->IsDialog();
}
-(double) getExtendedTitleBarHeight
{
if(_isExtended)
{
for (id subview in self.contentView.superview.subviews)
{
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
{
NSView *titlebarView = [subview subviews][0];
return (double)titlebarView.frame.size.height;
}
}
return -1;
}
else
{
return 0;
}
}
- (void)performClose:(id)sender
{
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{
if(![[self delegate] windowShouldClose:self]) return;
}
else if([self respondsToSelector:@selector(windowShouldClose:)])
{
if(![self windowShouldClose:self]) return;
}
[self close];
}
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
if(response == NSModalResponseContinue)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self pollModalSession:session];
});
}
else if (!_closed)
{
[self orderOut:self];
[NSApp endModalSession:session];
}
}
-(void) showWindowMenuWithAppMenu
{
if(_menu != nullptr)
{
auto appMenuItem = ::GetAppMenuItem();
if(appMenuItem != nullptr)
{
auto appMenu = [appMenuItem menu];
[appMenu removeItem:appMenuItem];
[_menu insertItem:appMenuItem atIndex:0];
[_menu setHasGlobalMenuItem:true];
}
[NSApp setMenu:_menu];
}
else
{
[self showAppMenuOnly];
}
}
-(void) showAppMenuOnly
{
auto appMenuItem = ::GetAppMenuItem();
if(appMenuItem != nullptr)
{
auto appMenu = ::GetAppMenu();
auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
[[appMenuItem menu] removeItem:appMenuItem];
if(_menu != nullptr)
{
[_menu setHasGlobalMenuItem:false];
}
[nativeAppMenu->GetNative() addItem:appMenuItem];
[NSApp setMenu:nativeAppMenu->GetNative()];
}
}
-(void) applyMenu:(AvnMenu *)menu
{
if(menu == nullptr)
{
menu = [AvnMenu new];
}
_menu = menu;
}
-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
{
// https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
// create nswindow with specific contentRect, otherwise we wont be able to resize the window
// until several ms after the window is physically on the screen.
self = [super initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:false];
[self setReleasedWhenClosed:false];
_parent = parent;
[self setDelegate:self];
_closed = false;
_isEnabled = true;
[self backingScaleFactor];
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
_isExtended = false;
return self;
}
- (BOOL)windowShouldClose:(NSWindow *)sender
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
return !window->WindowEvents->Closing();
}
return true;
}
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
{
[self backingScaleFactor];
}
- (void)windowWillClose:(NSNotification *)notification
{
_closed = true;
if(_parent)
{
ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL;
[self restoreParentWindow];
parent->BaseEvents->Closed();
[parent->View onClosed];
}
}
-(BOOL)canBecomeKeyWindow
{
// If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows])
{
auto ch = static_cast<id <AvnWindowProtocol>>(uch);
if(ch == nil)
continue;
if (ch.isDialog)
return false;
}
return true;
}
-(BOOL)canBecomeMainWindow
{
#ifdef IS_NSPANEL
return false;
#else
return true;
#endif
}
-(bool)shouldTryToHandleEvents
{
return _isEnabled;
}
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
}
-(void)becomeKeyWindow
{
[self showWindowMenuWithAppMenu];
if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
}
[super becomeKeyWindow];
}
-(void) restoreParentWindow;
{
auto parent = [self parentWindow];
if(parent != nil)
{
[parent removeChildWindow:self];
}
}
- (void)windowDidMiniaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowDidResize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowWillExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
{
NSRect screenRect = [[self screen] visibleFrame];
[self setFrame:screenRect display:YES];
}
if(parent->WindowState() == Minimized)
{
[self miniaturize:nullptr];
}
parent->WindowStateChanged();
}
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
parent->WindowStateChanged();
}
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{
return true;
}
-(void)resignKeyWindow
{
if(_parent)
_parent->BaseEvents->Deactivated();
[self showAppMenuOnly];
[super resignKeyWindow];
}
- (void)windowDidMove:(NSNotification *)notification
{
AvnPoint position;
if(_parent != nullptr)
{
auto cparent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(cparent != nullptr)
{
if(cparent->WindowState() == Maximized)
{
cparent->SetWindowState(Normal);
}
}
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position);
}
}
- (AvnPoint) translateLocalPoint:(AvnPoint)pt
{
pt.Y = [self frame].size.height - pt.Y;
return pt;
}
- (void)sendEvent:(NSEvent *)event
{
[super sendEvent:event];
/// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
if(_parent != nullptr && dynamic_cast<WindowImpl*>(_parent.getRaw()) != nullptr)
{
switch(event.type)
{
case NSEventTypeLeftMouseDown:
{
AvnView* view = _parent->View;
NSPoint windowPoint = [event locationInWindow];
NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
if (!NSPointInRect(viewPoint, view.bounds))
{
auto avnPoint = [AvnView toAvnPoint:windowPoint];
auto point = [self translateLocalPoint:avnPoint];
AvnVector delta = { 0, 0 };
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
}
}
break;
case NSEventTypeMouseEntered:
{
_parent->UpdateCursor();
}
break;
case NSEventTypeMouseExited:
{
[[NSCursor arrowCursor] set];
}
break;
default:
break;
}
}
}
- (void)disconnectParent {
_parent = nullptr;
}
@end

17
native/Avalonia.Native/src/OSX/INSWindowHolder.h

@ -0,0 +1,17 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
#define AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
@class AvnView;
struct INSWindowHolder
{
virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0;
};
#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

18
native/Avalonia.Native/src/OSX/IWindowStateChanged.h

@ -0,0 +1,18 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
#define AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
struct IWindowStateChanged
{
virtual void WindowStateChanged () = 0;
virtual void StartStateTransition () = 0;
virtual void EndStateTransition () = 0;
virtual SystemDecorations Decorations () = 0;
virtual AvnWindowState WindowState () = 0;
};
#endif //AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H

9
native/Avalonia.Native/src/OSX/PopupImpl.h

@ -0,0 +1,9 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_POPUPIMPL_H
#define AVALONIA_NATIVE_OSX_POPUPIMPL_H
#endif //AVALONIA_NATIVE_OSX_POPUPIMPL_H

68
native/Avalonia.Native/src/OSX/PopupImpl.mm

@ -0,0 +1,68 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#include "WindowInterfaces.h"
#include "AvnView.h"
#include "WindowImpl.h"
#include "automation.h"
#include "menu.h"
#include "common.h"
#import "WindowBaseImpl.h"
#import "WindowProtocol.h"
#import <AppKit/AppKit.h>
#include "PopupImpl.h"
class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup
{
private:
BEGIN_INTERFACE_MAP()
INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup)
END_INTERFACE_MAP()
virtual ~PopupImpl(){}
ComPtr<IAvnWindowEvents> WindowEvents;
PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
return NSWindowStyleMaskBorderless;
}
virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
{
START_COM_CALL;
@autoreleasepool
{
if (Window != nullptr)
{
[Window setContentSize:NSSize{x, y}];
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
}
return S_OK;
}
}
public:
virtual bool ShouldTakeFocusOnShow() override
{
return false;
}
};
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool
{
IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
return ptr;
}
}

24
native/Avalonia.Native/src/OSX/ResizeScope.h

@ -0,0 +1,24 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_RESIZESCOPE_H
#define AVALONIA_NATIVE_OSX_RESIZESCOPE_H
#include "avalonia-native.h"
@class AvnView;
class ResizeScope
{
public:
ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason);
~ResizeScope();
private:
AvnView* _Nonnull _view;
AvnPlatformResizeReason _restore;
};
#endif //AVALONIA_NATIVE_OSX_RESIZESCOPE_H

18
native/Avalonia.Native/src/OSX/ResizeScope.mm

@ -0,0 +1,18 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <AppKit/AppKit.h>
#include "ResizeScope.h"
#include "AvnView.h"
ResizeScope::ResizeScope(AvnView *view, AvnPlatformResizeReason reason) {
_view = view;
_restore = [view getResizeReason];
[view setResizeReason:reason];
}
ResizeScope::~ResizeScope() {
[_view setResizeReason:_restore];
}

2
native/Avalonia.Native/src/OSX/SystemDialogs.mm

@ -1,5 +1,5 @@
#include "common.h" #include "common.h"
#include "window.h" #include "INSWindowHolder.h"
class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs> class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
{ {

130
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -0,0 +1,130 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
#define AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
#include "rendertarget.h"
#include "INSWindowHolder.h"
@class AutoFitContentView;
@class AvnMenu;
@protocol AvnWindowProtocol;
class WindowBaseImpl : public virtual ComObject,
public virtual IAvnWindowBase,
public INSWindowHolder {
private:
NSCursor *cursor;
public:
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase)
END_INTERFACE_MAP()
virtual ~WindowBaseImpl();
AutoFitContentView *StandardContainer;
AvnView *View;
NSWindow * Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
ComPtr<IAvnGlContext> _glContext;
NSObject <IRenderTarget> *renderTarget;
AvnPoint lastPositionSet;
NSSize lastSize;
NSSize lastMinSize;
NSSize lastMaxSize;
AvnMenu* lastMenu;
NSString *_lastTitle;
bool _shown;
bool _inResize;
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
virtual HRESULT ObtainNSWindowHandle(void **ret) override;
virtual HRESULT ObtainNSWindowHandleRetained(void **ret) override;
virtual HRESULT ObtainNSViewHandle(void **ret) override;
virtual HRESULT ObtainNSViewHandleRetained(void **ret) override;
virtual NSWindow *GetNSWindow() override;
virtual NSView *GetNSView() override;
virtual HRESULT Show(bool activate, bool isDialog) override;
virtual bool ShouldTakeFocusOnShow();
virtual HRESULT Hide() override;
virtual HRESULT Activate() override;
virtual HRESULT SetTopMost(bool value) override;
virtual HRESULT Close() override;
virtual HRESULT GetClientSize(AvnSize *ret) override;
virtual HRESULT GetFrameSize(AvnSize *ret) override;
virtual HRESULT GetScaling(double *ret) override;
virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) override;
virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override;
virtual HRESULT Invalidate(__attribute__((unused)) AvnRect rect) override;
virtual HRESULT SetMainMenu(IAvnMenu *menu) override;
virtual HRESULT BeginMoveDrag() override;
virtual HRESULT BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) override;
virtual HRESULT GetPosition(AvnPoint *ret) override;
virtual HRESULT SetPosition(AvnPoint point) override;
virtual HRESULT PointToClient(AvnPoint point, AvnPoint *ret) override;
virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override;
virtual HRESULT SetCursor(IAvnCursor *cursor) override;
virtual void UpdateCursor();
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override;
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override;
virtual HRESULT SetBlurEnabled(bool enable) override;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
void *sourceHandle) override;
virtual bool IsDialog();
id<AvnWindowProtocol> GetWindowProtocol ();
protected:
virtual NSWindowStyleMask GetStyle();
void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);
void CleanNSWindow ();
void InitialiseNSWindow ();
};
#endif //AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H

589
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -0,0 +1,589 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <AppKit/AppKit.h>
#include "common.h"
#include "AvnView.h"
#include "menu.h"
#include "automation.h"
#include "cursor.h"
#include "ResizeScope.h"
#include "AutoFitContentView.h"
#import "WindowProtocol.h"
#import "WindowInterfaces.h"
#include "WindowBaseImpl.h"
WindowBaseImpl::~WindowBaseImpl() {
View = nullptr;
Window = nullptr;
}
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) {
_shown = false;
_inResize = false;
BaseEvents = events;
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
View = [[AvnView alloc] initWithParent:this];
StandardContainer = [[AutoFitContentView new] initWithContent:View];
lastPositionSet.X = 100;
lastPositionSet.Y = 100;
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
_lastTitle = @"";
Window = nullptr;
lastMenu = nullptr;
}
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge void *) View;
return S_OK;
}
HRESULT WindowBaseImpl::ObtainNSViewHandleRetained(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge_retained void *) View;
return S_OK;
}
NSWindow *WindowBaseImpl::GetNSWindow() {
return Window;
}
NSView *WindowBaseImpl::GetNSView() {
return View;
}
HRESULT WindowBaseImpl::ObtainNSWindowHandleRetained(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge_retained void *) Window;
return S_OK;
}
HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
CreateNSWindow(isDialog);
InitialiseNSWindow();
SetPosition(lastPositionSet);
UpdateStyle();
[Window setTitle:_lastTitle];
if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window];
[Window makeKeyAndOrderFront:Window];
[Window makeFirstResponder:View];
[NSApp activateIgnoringOtherApps:YES];
} else {
[Window orderFront:Window];
}
_shown = true;
return S_OK;
}
}
bool WindowBaseImpl::ShouldTakeFocusOnShow() {
return true;
}
HRESULT WindowBaseImpl::ObtainNSWindowHandle(void **ret) {
START_COM_CALL;
if (ret == nullptr) {
return E_POINTER;
}
*ret = (__bridge void *) Window;
return S_OK;
}
HRESULT WindowBaseImpl::Hide() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
[Window orderOut:Window];
[GetWindowProtocol() restoreParentWindow];
}
return S_OK;
}
}
HRESULT WindowBaseImpl::Activate() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
[Window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
}
}
return S_OK;
}
HRESULT WindowBaseImpl::SetTopMost(bool value) {
START_COM_CALL;
@autoreleasepool {
[Window setLevel:value ? NSFloatingWindowLevel : NSNormalWindowLevel];
return S_OK;
}
}
HRESULT WindowBaseImpl::Close() {
START_COM_CALL;
@autoreleasepool {
if (Window != nullptr) {
auto window = Window;
Window = nullptr;
try {
// Seems to throw sometimes on application exit.
[window close];
}
catch (NSException *) {}
}
return S_OK;
}
}
HRESULT WindowBaseImpl::GetClientSize(AvnSize *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr)
return E_POINTER;
auto frame = [View frame];
ret->Width = frame.size.width;
ret->Height = frame.size.height;
return S_OK;
}
}
HRESULT WindowBaseImpl::GetFrameSize(AvnSize *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr)
return E_POINTER;
auto frame = [Window frame];
ret->Width = frame.size.width;
ret->Height = frame.size.height;
return S_OK;
}
}
HRESULT WindowBaseImpl::GetScaling(double *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr)
return E_POINTER;
if (Window == nullptr) {
*ret = 1;
return S_OK;
}
*ret = [Window backingScaleFactor];
return S_OK;
}
}
HRESULT WindowBaseImpl::SetMinMaxSize(AvnSize minSize, AvnSize maxSize) {
START_COM_CALL;
@autoreleasepool {
lastMinSize = ToNSSize(minSize);
lastMaxSize = ToNSSize(maxSize);
if(Window != nullptr) {
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
}
return S_OK;
}
}
HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reason) {
if (_inResize) {
return S_OK;
}
_inResize = true;
START_COM_CALL;
auto resizeBlock = ResizeScope(View, reason);
@autoreleasepool {
auto maxSize = lastMaxSize;
auto minSize = lastMinSize;
if (x < minSize.width) {
x = minSize.width;
}
if (y < minSize.height) {
y = minSize.height;
}
if (x > maxSize.width) {
x = maxSize.width;
}
if (y > maxSize.height) {
y = maxSize.height;
}
@try {
if (!_shown) {
BaseEvents->Resized(AvnSize{x, y}, reason);
}
lastSize = NSSize {x, y};
if(Window != nullptr) {
[Window setContentSize:lastSize];
}
}
@finally {
_inResize = false;
}
return S_OK;
}
}
HRESULT WindowBaseImpl::Invalidate(__attribute__((unused)) AvnRect rect) {
START_COM_CALL;
@autoreleasepool {
[View setNeedsDisplayInRect:[View frame]];
return S_OK;
}
}
HRESULT WindowBaseImpl::SetMainMenu(IAvnMenu *menu) {
START_COM_CALL;
auto nativeMenu = dynamic_cast<AvnAppMenu *>(menu);
lastMenu = nativeMenu->GetNative();
if(Window != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
return S_OK;
}
HRESULT WindowBaseImpl::BeginMoveDrag() {
START_COM_CALL;
@autoreleasepool {
auto lastEvent = [View lastMouseDownEvent];
if (lastEvent == nullptr) {
return S_OK;
}
[Window performWindowDragWithEvent:lastEvent];
return S_OK;
}
}
HRESULT WindowBaseImpl::BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) {
START_COM_CALL;
return S_OK;
}
HRESULT WindowBaseImpl::GetPosition(AvnPoint *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
auto frame = [Window frame];
ret->X = frame.origin.x;
ret->Y = frame.origin.y + frame.size.height;
*ret = ConvertPointY(*ret);
return S_OK;
}
}
HRESULT WindowBaseImpl::SetPosition(AvnPoint point) {
START_COM_CALL;
@autoreleasepool {
lastPositionSet = point;
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
return S_OK;
}
}
HRESULT WindowBaseImpl::PointToClient(AvnPoint point, AvnPoint *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
point = ConvertPointY(point);
NSRect convertRect = [Window convertRectFromScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
auto viewPoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
*ret = [View translateLocalPoint:ToAvnPoint(viewPoint)];
return S_OK;
}
}
HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]);
NSRect convertRect = [Window convertRectToScreen:NSMakeRect(cocoaViewPoint.x, cocoaViewPoint.y, 0.0, 0.0)];
auto cocoaScreenPoint = NSPointFromCGPoint(NSMakePoint(convertRect.origin.x, convertRect.origin.y));
*ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint));
return S_OK;
}
}
HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) {
START_COM_CALL;
[View setSwRenderedFrame:fb dispose:dispose];
return S_OK;
}
HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) {
START_COM_CALL;
@autoreleasepool {
Cursor *avnCursor = dynamic_cast<Cursor *>(cursor);
this->cursor = avnCursor->GetNative();
UpdateCursor();
if (avnCursor->IsHidden()) {
[NSCursor hide];
} else {
[NSCursor unhide];
}
return S_OK;
}
}
void WindowBaseImpl::UpdateCursor() {
if (cursor != nil) {
[cursor set];
}
}
HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) {
START_COM_CALL;
if (View == NULL)
return E_FAIL;
*ppv = [renderTarget createSurfaceRenderTarget];
return static_cast<HRESULT>(*ppv == nil ? E_FAIL : S_OK);
}
HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) {
START_COM_CALL;
if (View == NULL)
return E_FAIL;
*retOut = ::CreateNativeControlHost(View);
return S_OK;
}
HRESULT WindowBaseImpl::SetBlurEnabled(bool enable) {
START_COM_CALL;
[StandardContainer ShowBlur:enable];
return S_OK;
}
HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
START_COM_CALL;
auto item = TryGetPasteboardItem(clipboard);
[item setString:@"" forType:GetAvnCustomDataType()];
if (item == nil)
return E_INVALIDARG;
if (View == NULL)
return E_FAIL;
auto nsevent = [NSApp currentEvent];
auto nseventType = [nsevent type];
// If current event isn't a mouse one (probably due to malfunctioning user app)
// attempt to forge a new one
if (!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
|| (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) {
NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
CGPoint cgpoint = NSPointToCGPoint(nspoint);
auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
nsevent = [NSEvent eventWithCGEvent:cgevent];
CFRelease(cgevent);
}
auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item];
auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
NSRect dragItemRect = {(float) point.X, (float) point.Y, [dragItemImage size].width, [dragItemImage size].height};
[dragItem setDraggingFrame:dragItemRect contents:dragItemImage];
int op = 0;
int ieffects = (int) effects;
if ((ieffects & (int) AvnDragDropEffects::Copy) != 0)
op |= NSDragOperationCopy;
if ((ieffects & (int) AvnDragDropEffects::Link) != 0)
op |= NSDragOperationLink;
if ((ieffects & (int) AvnDragDropEffects::Move) != 0)
op |= NSDragOperationMove;
[View beginDraggingSessionWithItems:@[dragItem] event:nsevent
source:CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
return S_OK;
}
bool WindowBaseImpl::IsDialog() {
return false;
}
NSWindowStyleMask WindowBaseImpl::GetStyle() {
return NSWindowStyleMaskBorderless;
}
void WindowBaseImpl::UpdateStyle() {
[Window setStyleMask:GetStyle()];
}
void WindowBaseImpl::CleanNSWindow() {
if(Window != nullptr) {
[GetWindowProtocol() disconnectParent];
[Window close];
Window = nullptr;
}
}
void WindowBaseImpl::CreateNSWindow(bool isDialog) {
if (isDialog) {
if (![Window isKindOfClass:[AvnPanel class]]) {
CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
}
} else {
if (![Window isKindOfClass:[AvnWindow class]]) {
CleanNSWindow();
Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
}
}
}
void WindowBaseImpl::InitialiseNSWindow() {
if(Window != nullptr) {
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentSize:lastSize];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
if (lastMenu != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
}
}
id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
if(Window == nullptr)
{
return nullptr;
}
return static_cast<id <AvnWindowProtocol>>(Window);
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool
{
IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
return ptr;
}
}

96
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -0,0 +1,96 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#ifndef AVALONIA_NATIVE_OSX_WINDOWIMPL_H
#define AVALONIA_NATIVE_OSX_WINDOWIMPL_H
#import "WindowBaseImpl.h"
#include "IWindowStateChanged.h"
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{
private:
bool _canResize;
bool _fullScreenActive;
SystemDecorations _decorations;
AvnWindowState _lastWindowState;
AvnWindowState _actualWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
bool _isDialog;
AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
END_INTERFACE_MAP()
virtual ~WindowImpl()
{
}
ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
void HideOrShowTrafficLights ();
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
virtual HRESULT SetParent (IAvnWindow* parent) override;
void StartStateTransition () override ;
void EndStateTransition () override ;
SystemDecorations Decorations () override ;
AvnWindowState WindowState () override ;
void WindowStateChanged () override ;
bool UndecoratedIsMaximized ();
bool IsZoomed ();
void DoZoom();
virtual HRESULT SetCanResize(bool value) override;
virtual HRESULT SetDecorations(SystemDecorations value) override;
virtual HRESULT SetTitle (char* utf8title) override;
virtual HRESULT SetTitleBarColor(AvnColor color) override;
virtual HRESULT GetWindowState (AvnWindowState*ret) override;
virtual HRESULT TakeFocusFromChildren () override;
virtual HRESULT SetExtendClientArea (bool enable) override;
virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override;
virtual HRESULT GetExtendTitleBarHeight (double*ret) override;
virtual HRESULT SetExtendTitleBarHeight (double value) override;
void EnterFullScreenMode ();
void ExitFullScreenMode ();
virtual HRESULT SetWindowState (AvnWindowState state) override;
virtual bool IsDialog() override;
protected:
virtual NSWindowStyleMask GetStyle() override;
};
#endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H

552
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -0,0 +1,552 @@
//
// Created by Dan Walmsley on 04/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <AppKit/AppKit.h>
#include "AutoFitContentView.h"
#include "AvnView.h"
#include "automation.h"
#include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
_isClientAreaExtended = false;
_extendClientHints = AvnDefaultChrome;
_fullScreenActive = false;
_canResize = true;
_decorations = SystemDecorationsFull;
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal;
_actualWindowState = Normal;
WindowEvents = events;
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
}
void WindowImpl::HideOrShowTrafficLights() {
if (Window == nil) {
return;
}
for (id subview in Window.contentView.superview.subviews) {
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) {
if ([button isKindOfClass:[NSButton class]]) {
if (_isClientAreaExtended) {
auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
[button setHidden:!wantsChrome];
} else {
[button setHidden:(_decorations != SystemDecorationsFull)];
}
[button setWantsLayer:true];
}
}
}
}
}
HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
_isDialog = isDialog;
bool created = Window == nullptr;
WindowBaseImpl::Show(activate, isDialog);
if(created)
{
if(_isClientAreaExtended)
{
[GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true);
}
}
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState);
}
}
HRESULT WindowImpl::SetEnabled(bool enable) {
START_COM_CALL;
@autoreleasepool {
[GetWindowProtocol() setEnabled:enable];
return S_OK;
}
}
HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
START_COM_CALL;
@autoreleasepool {
if (parent == nullptr)
return E_POINTER;
auto cparent = dynamic_cast<WindowImpl *>(parent);
if (cparent == nullptr)
return E_INVALIDARG;
// If one tries to show a child window with a minimized parent window, then the parent window will be
// restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
UpdateStyle();
return S_OK;
}
}
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
}
SystemDecorations WindowImpl::Decorations() {
return _decorations;
}
AvnWindowState WindowImpl::WindowState() {
return _lastWindowState;
}
void WindowImpl::WindowStateChanged() {
if (_shown && !_inSetWindowState && !_transitioningWindowState) {
AvnWindowState state;
GetWindowState(&state);
if (_lastWindowState != state) {
if (_isClientAreaExtended) {
if (_lastWindowState == FullScreen) {
// we exited fs.
if (_extendClientHints & AvnOSXThickTitleBar) {
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = false;
}
[Window setTitlebarAppearsTransparent:true];
[StandardContainer setFrameSize:StandardContainer.frame.size];
} else if (state == FullScreen) {
// we entered fs.
if (_extendClientHints & AvnOSXThickTitleBar) {
Window.toolbar = nullptr;
}
[Window setTitlebarAppearsTransparent:false];
[StandardContainer setFrameSize:StandardContainer.frame.size];
}
}
_lastWindowState = state;
_actualWindowState = state;
WindowEvents->WindowStateChanged(state);
}
}
}
bool WindowImpl::UndecoratedIsMaximized() {
auto windowSize = [Window frame];
auto available = [Window screen].visibleFrame;
return CGRectEqualToRect(windowSize, available);
}
bool WindowImpl::IsZoomed() {
return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
}
void WindowImpl::DoZoom() {
switch (_decorations) {
case SystemDecorationsNone:
case SystemDecorationsBorderOnly:
[Window setFrame:[Window screen].visibleFrame display:true];
break;
case SystemDecorationsFull:
[Window performZoom:Window];
break;
}
}
HRESULT WindowImpl::SetCanResize(bool value) {
START_COM_CALL;
@autoreleasepool {
_canResize = value;
UpdateStyle();
return S_OK;
}
}
HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
START_COM_CALL;
@autoreleasepool {
auto currentWindowState = _lastWindowState;
_decorations = value;
if (_fullScreenActive) {
return S_OK;
}
UpdateStyle();
HideOrShowTrafficLights();
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
if (currentWindowState == Maximized) {
if (!UndecoratedIsMaximized()) {
DoZoom();
}
}
break;
case SystemDecorationsBorderOnly:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
if (currentWindowState == Maximized) {
if (!UndecoratedIsMaximized()) {
DoZoom();
}
}
break;
case SystemDecorationsFull:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
if (currentWindowState == Maximized) {
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
break;
}
return S_OK;
}
}
HRESULT WindowImpl::SetTitle(char *utf8title) {
START_COM_CALL;
@autoreleasepool {
_lastTitle = [NSString stringWithUTF8String:(const char *) utf8title];
[Window setTitle:_lastTitle];
return S_OK;
}
}
HRESULT WindowImpl::SetTitleBarColor(AvnColor color) {
START_COM_CALL;
@autoreleasepool {
float a = (float) color.Alpha / 255.0f;
float r = (float) color.Red / 255.0f;
float g = (float) color.Green / 255.0f;
float b = (float) color.Blue / 255.0f;
auto nscolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
// Based on the titlebar color we have to choose either light or dark
// OSX doesnt let you set a foreground color for titlebar.
if ((r * 0.299 + g * 0.587 + b * 0.114) > 186.0f / 255.0f) {
[Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
} else {
[Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
}
[Window setTitlebarAppearsTransparent:true];
[Window setBackgroundColor:nscolor];
}
return S_OK;
}
HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
if (([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen) {
*ret = FullScreen;
return S_OK;
}
if ([Window isMiniaturized]) {
*ret = Minimized;
return S_OK;
}
if (IsZoomed()) {
*ret = Maximized;
return S_OK;
}
*ret = Normal;
return S_OK;
}
}
HRESULT WindowImpl::TakeFocusFromChildren() {
START_COM_CALL;
@autoreleasepool {
if (Window == nil)
return S_OK;
if ([Window isKeyWindow])
[Window makeFirstResponder:View];
return S_OK;
}
}
HRESULT WindowImpl::SetExtendClientArea(bool enable) {
START_COM_CALL;
@autoreleasepool {
_isClientAreaExtended = enable;
if(Window != nullptr) {
if (enable) {
Window.titleVisibility = NSWindowTitleHidden;
[Window setTitlebarAppearsTransparent:true];
auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if (wantsTitleBar) {
[StandardContainer ShowTitleBar:true];
} else {
[StandardContainer ShowTitleBar:false];
}
if (_extendClientHints & AvnOSXThickTitleBar) {
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = false;
} else {
Window.toolbar = nullptr;
}
} else {
Window.titleVisibility = NSWindowTitleVisible;
Window.toolbar = nullptr;
[Window setTitlebarAppearsTransparent:false];
View.layer.zPosition = 0;
}
[GetWindowProtocol() setIsExtended:enable];
HideOrShowTrafficLights();
UpdateStyle();
}
return S_OK;
}
}
HRESULT WindowImpl::SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints) {
START_COM_CALL;
@autoreleasepool {
_extendClientHints = hints;
SetExtendClientArea(_isClientAreaExtended);
return S_OK;
}
}
HRESULT WindowImpl::GetExtendTitleBarHeight(double *ret) {
START_COM_CALL;
@autoreleasepool {
if (ret == nullptr) {
return E_POINTER;
}
*ret = [GetWindowProtocol() getExtendedTitleBarHeight];
return S_OK;
}
}
HRESULT WindowImpl::SetExtendTitleBarHeight(double value) {
START_COM_CALL;
@autoreleasepool {
[StandardContainer SetTitleBarHeightHint:value];
return S_OK;
}
}
void WindowImpl::EnterFullScreenMode() {
_fullScreenActive = true;
[Window setTitle:_lastTitle];
[Window toggleFullScreen:nullptr];
}
void WindowImpl::ExitFullScreenMode() {
[Window toggleFullScreen:nullptr];
_fullScreenActive = false;
SetDecorations(_decorations);
}
HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
START_COM_CALL;
@autoreleasepool {
if (Window == nullptr) {
return S_OK;
}
if (_actualWindowState == state) {
return S_OK;
}
_inSetWindowState = true;
auto currentState = _actualWindowState;
_lastWindowState = state;
if (currentState == Normal) {
_preZoomSize = [Window frame];
}
if (_shown) {
switch (state) {
case Maximized:
if (currentState == FullScreen) {
ExitFullScreenMode();
}
lastPositionSet.X = 0;
lastPositionSet.Y = 0;
if ([Window isMiniaturized]) {
[Window deminiaturize:Window];
}
if (!IsZoomed()) {
DoZoom();
}
break;
case Minimized:
if (currentState == FullScreen) {
ExitFullScreenMode();
} else {
[Window miniaturize:Window];
}
break;
case FullScreen:
if ([Window isMiniaturized]) {
[Window deminiaturize:Window];
}
EnterFullScreenMode();
break;
case Normal:
if ([Window isMiniaturized]) {
[Window deminiaturize:Window];
}
if (currentState == FullScreen) {
ExitFullScreenMode();
}
if (IsZoomed()) {
if (_decorations == SystemDecorationsFull) {
DoZoom();
} else {
[Window setFrame:_preZoomSize display:true];
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
}
break;
}
_actualWindowState = _lastWindowState;
WindowEvents->WindowStateChanged(_actualWindowState);
}
_inSetWindowState = false;
return S_OK;
}
}
bool WindowImpl::IsDialog() {
return _isDialog;
}
NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless;
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsBorderOnly:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
if (_canResize) {
s = s | NSWindowStyleMaskResizable;
}
break;
}
if ([Window parentWindow] == nullptr) {
s |= NSWindowStyleMaskMiniaturizable;
}
if (_isClientAreaExtended) {
s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
}
return s;
}

17
native/Avalonia.Native/src/OSX/WindowInterfaces.h

@ -0,0 +1,17 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include "WindowProtocol.h"
#include "WindowBaseImpl.h"
@interface AvnWindow : NSWindow <AvnWindowProtocol, NSWindowDelegate>
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
@end
@interface AvnPanel : NSPanel <AvnWindowProtocol, NSWindowDelegate>
-(AvnPanel* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
@end

25
native/Avalonia.Native/src/OSX/WindowProtocol.h

@ -0,0 +1,25 @@
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#pragma once
#import <AppKit/AppKit.h>
@class AvnMenu;
@protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(AvnMenu* _Nullable)menu;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(void) disconnectParent;
-(bool) isDialog;
@end

12
native/Avalonia.Native/src/OSX/app.mm

@ -82,18 +82,6 @@ ComPtr<IAvnApplicationEvents> _events;
_isHandlingSendEvent = oldHandling; _isHandlingSendEvent = oldHandling;
} }
} }
// This is needed for certain embedded controls
- (BOOL) isHandlingSendEvent
{
return _isHandlingSendEvent;
}
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
{
_isHandlingSendEvent = handlingSendEvent;
}
@end @end
extern void InitializeAvnApp(IAvnApplicationEvents* events) extern void InitializeAvnApp(IAvnApplicationEvents* events)

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

@ -1,6 +1,6 @@
#import <Cocoa/Cocoa.h> #pragma once
#include "window.h"
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
class IAvnAutomationPeer; class IAvnAutomationPeer;

3
native/Avalonia.Native/src/OSX/automation.mm

@ -1,7 +1,8 @@
#include "common.h" #include "common.h"
#include "automation.h" #include "automation.h"
#include "AvnString.h" #include "AvnString.h"
#include "window.h" #include "INSWindowHolder.h"
#include "AvnView.h"
@interface AvnAccessibilityElement (Events) @interface AvnAccessibilityElement (Events)
- (void) raiseChildrenChanged; - (void) raiseChildrenChanged;

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

@ -27,7 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator(); extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands(); extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu); extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu); extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu (); extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem (); extern NSMenuItem* GetAppMenuItem ();
@ -38,7 +38,6 @@ extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r); extern NSRect ToNSRect (AvnRect r);
extern AvnPoint ToAvnPoint (NSPoint p); extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p); extern AvnPoint ConvertPointY (AvnPoint p);
extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s); extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG #ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__) #define NSDebugLog(...) NSLog(__VA_ARGS__)

1
native/Avalonia.Native/src/OSX/cursor.mm

@ -1,6 +1,5 @@
#include "common.h" #include "common.h"
#include "cursor.h" #include "cursor.h"
#include <map>
class CursorFactory : public ComSingleObject<IAvnCursorFactory, &IID_IAvnCursorFactory> class CursorFactory : public ComSingleObject<IAvnCursorFactory, &IID_IAvnCursorFactory>
{ {

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

@ -1,7 +1,6 @@
//This file will contain actual IID structures //This file will contain actual IID structures
#define COM_GUIDS_MATERIALIZE #define COM_GUIDS_MATERIALIZE
#include "common.h" #include "common.h"
#include "window.h"
static NSString* s_appTitle = @"Avalonia"; static NSString* s_appTitle = @"Avalonia";
@ -343,7 +342,7 @@ public:
@autoreleasepool @autoreleasepool
{ {
::SetAppMenu(s_appTitle, appMenu); ::SetAppMenu(appMenu);
return S_OK; return S_OK;
} }
} }
@ -428,7 +427,3 @@ AvnPoint ConvertPointY (AvnPoint p)
return p; return p;
} }
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}

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

@ -31,7 +31,6 @@ private:
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
IAvnActionCallback* _callback; IAvnActionCallback* _callback;
IAvnPredicateCallback* _predicate; IAvnPredicateCallback* _predicate;
bool _isSeparator;
bool _isCheckable; bool _isCheckable;
public: public:

6
native/Avalonia.Native/src/OSX/menu.mm

@ -1,7 +1,6 @@
#include "common.h" #include "common.h"
#include "menu.h" #include "menu.h"
#include "window.h"
#include "KeyTransform.h" #include "KeyTransform.h"
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */ #include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */
@ -74,8 +73,7 @@
AvnAppMenuItem::AvnAppMenuItem(bool isSeparator) AvnAppMenuItem::AvnAppMenuItem(bool isSeparator)
{ {
_isCheckable = false; _isCheckable = false;
_isSeparator = isSeparator;
if(isSeparator) if(isSeparator)
{ {
_native = [NSMenuItem separatorItem]; _native = [NSMenuItem separatorItem];
@ -460,7 +458,7 @@ extern IAvnMenuItem* CreateAppMenuItemSeparator()
static IAvnMenu* s_appMenu = nullptr; static IAvnMenu* s_appMenu = nullptr;
static NSMenuItem* s_appMenuItem = nullptr; static NSMenuItem* s_appMenuItem = nullptr;
extern void SetAppMenu (NSString* appName, IAvnMenu* menu) extern void SetAppMenu(IAvnMenu *menu)
{ {
s_appMenu = menu; s_appMenu = menu;

4
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -1,14 +1,10 @@
#include "common.h" #include "common.h"
#include "rendertarget.h" #include "rendertarget.h"
#import <IOSurface/IOSurface.h>
#import <IOSurface/IOSurfaceObjC.h> #import <IOSurface/IOSurfaceObjC.h>
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#include <OpenGL/CGLIOSurface.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/glext.h> #include <OpenGL/glext.h>
#include <OpenGL/gl3.h> #include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
@interface IOSurfaceHolder : NSObject @interface IOSurfaceHolder : NSObject
@end @end

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

@ -1,77 +0,0 @@
#ifndef window_h
#define window_h
class WindowBaseImpl;
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPixelSize) getPixelSize;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
@interface AutoFitContentView : NSView
-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
-(void) ShowTitleBar: (bool) show;
-(void) SetTitleBarHeightHint: (double) height;
-(void) SetContent: (NSView* _Nonnull) content;
-(void) ShowBlur: (bool) show;
@end
@interface AvnWindow : NSWindow <NSWindowDelegate>
+(void) closeAll;
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(void) setCanBecomeKeyAndMain;
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(NSMenu* _Nullable)menu;
-(double) getScaling;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(bool) isDialog;
@end
struct INSWindowHolder
{
virtual AvnWindow* _Nonnull GetNSWindow () = 0;
virtual AvnView* _Nonnull GetNSView () = 0;
};
struct IWindowStateChanged
{
virtual void WindowStateChanged () = 0;
virtual void StartStateTransition () = 0;
virtual void EndStateTransition () = 0;
virtual SystemDecorations Decorations () = 0;
virtual AvnWindowState WindowState () = 0;
};
class ResizeScope
{
public:
ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason)
{
_view = view;
_restore = [view getResizeReason];
[view setResizeReason:reason];
}
~ResizeScope()
{
[_view setResizeReason:_restore];
}
private:
AvnView* _Nonnull _view;
AvnPlatformResizeReason _restore;
};
#endif /* window_h */

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

File diff suppressed because it is too large

1
samples/BindingDemo/BindingDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

1
samples/ControlCatalog.NetCore/Program.cs

@ -117,7 +117,6 @@ namespace ControlCatalog.NetCore
EnableMultitouch = true EnableMultitouch = true
}) })
.UseSkia() .UseSkia()
.UseManagedSystemDialogs()
.AfterSetup(builder => .AfterSetup(builder =>
{ {
builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()

2
samples/ControlCatalog/ControlCatalog.csproj

@ -25,6 +25,8 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup> </ItemGroup>

10
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -86,6 +86,16 @@
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
</ComboBox> </ComboBox>
<ComboBox PlaceholderText="Scaled" Width="166" RenderTransformOrigin="0,0">
<ComboBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</ComboBox.RenderTransform>
<ComboBoxItem>Inline Items</ComboBoxItem>
<ComboBoxItem>Inline Item 2</ComboBoxItem>
<ComboBoxItem>Inline Item 3</ComboBoxItem>
<ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox>
</WrapPanel> </WrapPanel>
<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox> <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>

6
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -66,6 +66,12 @@
FontFamily="Comic Sans MS" FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False" InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/> Foreground="Red"/>
<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Width="200"
Height="125"
LineHeight="32"
Text="Multiline TextBox with TextWrapping and increased LineHeight.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Margin="8"> <StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label> <Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>

2
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
private WindowState _windowState; private WindowState _windowState;
private WindowState[] _windowStates; private WindowState[] _windowStates;
private int _transparencyLevel; private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints; private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
private bool _extendClientAreaEnabled; private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled; private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled; private bool _preferSystemChromeEnabled;

3
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />

1
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,6 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup> </ItemGroup>

3
samples/Previewer/Previewer.csproj

@ -9,6 +9,9 @@
</Compile> </Compile>
<EmbeddedResource Include="**\*.xaml" /> <EmbeddedResource Include="**\*.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> <Import Project="..\..\build\ReferenceCoreLibraries.props" />

1
samples/RenderDemo/RenderDemo.csproj

@ -12,6 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

1
samples/Sandbox/Sandbox.csproj

@ -10,6 +10,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />

1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" />

1
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" /> <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />

6
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -394,7 +394,13 @@ namespace Avalonia.Collections
} while (en.MoveNext()); } while (en.MoveNext());
if (notificationItems is not null) if (notificationItems is not null)
{
NotifyAdd(notificationItems, index); NotifyAdd(notificationItems, index);
}
else
{
NotifyCountChanged();
}
} }
} }
} }

19
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins
new Dictionary<(Type, string), PropertyInfo?>(); new Dictionary<(Type, string), PropertyInfo?>();
/// <inheritdoc/> /// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
/// <summary> /// <summary>
/// Starts monitoring the value of a property on an object. /// Starts monitoring the value of a property on an object.
@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins
if (!reference.TryGetTarget(out var instance) || instance is null) if (!reference.TryGetTarget(out var instance) || instance is null)
return null; return null;
var p = GetFirstPropertyWithName(instance.GetType(), propertyName); var p = GetFirstPropertyWithName(instance, propertyName);
if (p != null) if (p != null)
{ {
@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins
} }
} }
private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName) private const BindingFlags PropertyBindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
{ {
if (instance is IReflectableType reflectableType)
return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);
var type = instance.GetType();
var key = (type, propertyName); var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out var propertyInfo)) if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins
{ {
PropertyInfo? found = null; PropertyInfo? found = null;
const BindingFlags bindingFlags = var properties = type.GetProperties(PropertyBindingFlags);
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var properties = type.GetProperties(bindingFlags);
foreach (PropertyInfo propertyInfo in properties) foreach (PropertyInfo propertyInfo in properties)
{ {

5
src/Avalonia.Base/DirectPropertyBase.cs

@ -2,7 +2,6 @@
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -188,10 +187,10 @@ namespace Avalonia
} }
else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
{ {
return new PropertySetterLazyInstance<TValue>( return new PropertySetterTemplateInstance<TValue>(
target, target,
this, this,
() => (TValue)template.Build()); template);
} }
else else
{ {

18
src/Avalonia.Base/Input/InputElement.cs

@ -94,7 +94,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<KeyEventArgs> KeyDownEvent = public static readonly RoutedEvent<KeyEventArgs> KeyDownEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>( RoutedEvent.Register<InputElement, KeyEventArgs>(
"KeyDown", nameof(KeyDown),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -102,7 +102,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<KeyEventArgs> KeyUpEvent = public static readonly RoutedEvent<KeyEventArgs> KeyUpEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>( RoutedEvent.Register<InputElement, KeyEventArgs>(
"KeyUp", nameof(KeyUp),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -116,7 +116,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<TextInputEventArgs> TextInputEvent = public static readonly RoutedEvent<TextInputEventArgs> TextInputEvent =
RoutedEvent.Register<InputElement, TextInputEventArgs>( RoutedEvent.Register<InputElement, TextInputEventArgs>(
"TextInput", nameof(TextInput),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -124,7 +124,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequestedEvent = public static readonly RoutedEvent<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequestedEvent =
RoutedEvent.Register<InputElement, TextInputMethodClientRequestedEventArgs>( RoutedEvent.Register<InputElement, TextInputMethodClientRequestedEventArgs>(
"TextInputMethodClientRequested", nameof(TextInputMethodClientRequested),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -144,7 +144,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent = public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>( RoutedEvent.Register<InputElement, PointerEventArgs>(
"PointerMove", nameof(PointerMoved),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -152,7 +152,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerPressedEventArgs> PointerPressedEvent = public static readonly RoutedEvent<PointerPressedEventArgs> PointerPressedEvent =
RoutedEvent.Register<InputElement, PointerPressedEventArgs>( RoutedEvent.Register<InputElement, PointerPressedEventArgs>(
"PointerPressed", nameof(PointerPressed),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -160,7 +160,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerReleasedEventArgs> PointerReleasedEvent = public static readonly RoutedEvent<PointerReleasedEventArgs> PointerReleasedEvent =
RoutedEvent.Register<InputElement, PointerReleasedEventArgs>( RoutedEvent.Register<InputElement, PointerReleasedEventArgs>(
"PointerReleased", nameof(PointerReleased),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>
@ -168,7 +168,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerCaptureLostEventArgs> PointerCaptureLostEvent = public static readonly RoutedEvent<PointerCaptureLostEventArgs> PointerCaptureLostEvent =
RoutedEvent.Register<InputElement, PointerCaptureLostEventArgs>( RoutedEvent.Register<InputElement, PointerCaptureLostEventArgs>(
"PointerCaptureLost", nameof(PointerCaptureLost),
RoutingStrategies.Direct); RoutingStrategies.Direct);
/// <summary> /// <summary>
@ -176,7 +176,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerWheelEventArgs> PointerWheelChangedEvent = public static readonly RoutedEvent<PointerWheelEventArgs> PointerWheelChangedEvent =
RoutedEvent.Register<InputElement, PointerWheelEventArgs>( RoutedEvent.Register<InputElement, PointerWheelEventArgs>(
"PointerWheelChanged", nameof(PointerWheelChanged),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary> /// <summary>

2
src/Avalonia.Base/Layout/UniformGridLayout.cs

@ -116,7 +116,7 @@ namespace Avalonia.Layout
/// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property. /// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty = public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MinItemWidth)); AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns));
/// <summary> /// <summary>
/// Defines the <see cref="Orientation"/> property. /// Defines the <see cref="Orientation"/> property.

51
src/Avalonia.Base/Logging/ILogSink.cs

@ -26,57 +26,6 @@ namespace Avalonia.Logging
object? source, object? source,
string messageTemplate); string messageTemplate);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
void Log<T0>(
LogEventLevel level,
string area,
object? source,
string messageTemplate,
T0 propertyValue0);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
void Log<T0, T1>(
LogEventLevel level,
string area,
object? source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
void Log<T0, T1, T2>(
LogEventLevel level,
string area,
object? source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2);
/// <summary> /// <summary>
/// Logs a new event. /// Logs a new event.
/// </summary> /// </summary>

38
src/Avalonia.Base/Logging/TraceLogSink.cs

@ -28,31 +28,7 @@ namespace Avalonia.Logging
{ {
if (IsEnabled(level, area)) if (IsEnabled(level, area))
{ {
Trace.WriteLine(Format<object, object, object>(area, messageTemplate, source)); Trace.WriteLine(Format<object, object, object>(area, messageTemplate, source, null));
}
}
public void Log<T0>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0)
{
if (IsEnabled(level, area))
{
Trace.WriteLine(Format<T0, object, object>(area, messageTemplate, source, propertyValue0));
}
}
public void Log<T0, T1>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
{
if (IsEnabled(level, area))
{
Trace.WriteLine(Format<T0, T1, object>(area, messageTemplate, source, propertyValue0, propertyValue1));
}
}
public void Log<T0, T1, T2>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
{
if (IsEnabled(level, area))
{
Trace.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2));
} }
} }
@ -68,9 +44,7 @@ namespace Avalonia.Logging
string area, string area,
string template, string template,
object? source, object? source,
T0? v0 = default, object?[]? values)
T1? v1 = default,
T2? v2 = default)
{ {
var result = new StringBuilder(template.Length); var result = new StringBuilder(template.Length);
var r = new CharacterReader(template.AsSpan()); var r = new CharacterReader(template.AsSpan());
@ -93,13 +67,7 @@ namespace Avalonia.Logging
if (r.Peek != '{') if (r.Peek != '{')
{ {
result.Append('\''); result.Append('\'');
result.Append(i++ switch result.Append(values?[i++]);
{
0 => v0,
1 => v1,
2 => v2,
_ => null
});
result.Append('\''); result.Append('\'');
r.TakeUntil('}'); r.TakeUntil('}');
r.Take(); r.Take();

4
src/Avalonia.Base/Media/ConicGradientBrush.cs

@ -11,7 +11,7 @@ namespace Avalonia.Media
/// Defines the <see cref="Center"/> property. /// Defines the <see cref="Center"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<RelativePoint> CenterProperty = public static readonly StyledProperty<RelativePoint> CenterProperty =
AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>( AvaloniaProperty.Register<ConicGradientBrush, RelativePoint>(
nameof(Center), nameof(Center),
RelativePoint.Center); RelativePoint.Center);
@ -19,7 +19,7 @@ namespace Avalonia.Media
/// Defines the <see cref="Angle"/> property. /// Defines the <see cref="Angle"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> AngleProperty = public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<RadialGradientBrush, double>( AvaloniaProperty.Register<ConicGradientBrush, double>(
nameof(Angle), nameof(Angle),
0); 0);

8
src/Avalonia.Base/StyledPropertyBase.cs

@ -1,9 +1,7 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -12,7 +10,7 @@ namespace Avalonia
/// </summary> /// </summary>
public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
{ {
private bool _inherits; private readonly bool _inherits;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class. /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
@ -243,10 +241,10 @@ namespace Avalonia
} }
else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
{ {
return new PropertySetterLazyInstance<TValue>( return new PropertySetterTemplateInstance<TValue>(
target, target,
this, this,
() => (TValue)template.Build()); template);
} }
else else
{ {

5
src/Avalonia.Base/Styling/IStyleInstance.cs

@ -14,6 +14,11 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
IStyle Source { get; } IStyle Source { get; }
/// <summary>
/// Gets a value indicating whether this style has an activator.
/// </summary>
bool HasActivator { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this style is active. /// Gets a value indicating whether this style is active.
/// </summary> /// </summary>

10
src/Avalonia.Base/Styling/PropertySetterInstance.cs

@ -44,7 +44,7 @@ namespace Avalonia.Styling
{ {
if (hasActivator) if (hasActivator)
{ {
if (_styledProperty is object) if (_styledProperty is not null)
{ {
_subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger); _subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger);
} }
@ -55,13 +55,15 @@ namespace Avalonia.Styling
} }
else else
{ {
if (_styledProperty is object) var target = (AvaloniaObject) _target;
if (_styledProperty is not null)
{ {
_subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style); _subscription = target.SetValue(_styledProperty!, _value, BindingPriority.Style);
} }
else else
{ {
_target.SetValue(_directProperty!, _value); target.SetValue(_directProperty!, _value);
} }
} }
} }

31
src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs → src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs

@ -11,42 +11,42 @@ namespace Avalonia.Styling
/// evaluated. /// evaluated.
/// </summary> /// </summary>
/// <typeparam name="T">The target property type.</typeparam> /// <typeparam name="T">The target property type.</typeparam>
internal class PropertySetterLazyInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>, internal class PropertySetterTemplateInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>,
ISetterInstance ISetterInstance
{ {
private readonly IStyleable _target; private readonly IStyleable _target;
private readonly StyledPropertyBase<T>? _styledProperty; private readonly StyledPropertyBase<T>? _styledProperty;
private readonly DirectPropertyBase<T>? _directProperty; private readonly DirectPropertyBase<T>? _directProperty;
private readonly Func<T> _valueFactory; private readonly ITemplate _template;
private BindingValue<T> _value; private BindingValue<T> _value;
private IDisposable? _subscription; private IDisposable? _subscription;
private bool _isActive; private bool _isActive;
public PropertySetterLazyInstance( public PropertySetterTemplateInstance(
IStyleable target, IStyleable target,
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
Func<T> valueFactory) ITemplate template)
{ {
_target = target; _target = target;
_styledProperty = property; _styledProperty = property;
_valueFactory = valueFactory; _template = template;
} }
public PropertySetterLazyInstance( public PropertySetterTemplateInstance(
IStyleable target, IStyleable target,
DirectPropertyBase<T> property, DirectPropertyBase<T> property,
Func<T> valueFactory) ITemplate template)
{ {
_target = target; _target = target;
_directProperty = property; _directProperty = property;
_valueFactory = valueFactory; _template = template;
} }
public void Start(bool hasActivator) public void Start(bool hasActivator)
{ {
_isActive = !hasActivator; _isActive = !hasActivator;
if (_styledProperty is object) if (_styledProperty is not null)
{ {
var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style;
_subscription = _target.Bind(_styledProperty, this, priority); _subscription = _target.Bind(_styledProperty, this, priority);
@ -77,7 +77,7 @@ namespace Avalonia.Styling
public override void Dispose() public override void Dispose()
{ {
if (_subscription is object) if (_subscription is not null)
{ {
var sub = _subscription; var sub = _subscription;
_subscription = null; _subscription = null;
@ -85,7 +85,7 @@ namespace Avalonia.Styling
} }
else if (_isActive) else if (_isActive)
{ {
if (_styledProperty is object) if (_styledProperty is not null)
{ {
_target.ClearValue(_styledProperty); _target.ClearValue(_styledProperty);
} }
@ -101,22 +101,21 @@ namespace Avalonia.Styling
protected override void Subscribed() => PublishNext(); protected override void Subscribed() => PublishNext();
protected override void Unsubscribed() { } protected override void Unsubscribed() { }
private T GetValue() private void EnsureTemplate()
{ {
if (_value.HasValue) if (_value.HasValue)
{ {
return _value.Value; return;
} }
_value = _valueFactory(); _value = (T) _template.Build();
return _value.Value;
} }
private void PublishNext() private void PublishNext()
{ {
if (_isActive) if (_isActive)
{ {
GetValue(); EnsureTemplate();
PublishNext(_value); PublishNext(_value);
} }
else else

9
src/Avalonia.Base/Styling/Setter.cs

@ -1,9 +1,7 @@
using System; using System;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -70,12 +68,5 @@ namespace Avalonia.Styling
return Property.CreateSetterInstance(target, Value); return Property.CreateSetterInstance(target, Value);
} }
private struct SetterVisitorData
{
public IStyleable target;
public object? value;
public ISetterInstance? result;
}
} }
} }

35
src/Avalonia.Base/Styling/StyleInstance.cs

@ -11,10 +11,10 @@ namespace Avalonia.Styling
/// <summary> /// <summary>
/// A <see cref="Style"/> which has been instanced on a control. /// A <see cref="Style"/> which has been instanced on a control.
/// </summary> /// </summary>
internal class StyleInstance : IStyleInstance, IStyleActivatorSink internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink
{ {
private readonly List<ISetterInstance>? _setters; private readonly ISetterInstance[]? _setters;
private readonly List<IDisposable>? _animations; private readonly IDisposable[]? _animations;
private readonly IStyleActivator? _activator; private readonly IStyleActivator? _activator;
private readonly Subject<bool>? _animationTrigger; private readonly Subject<bool>? _animationTrigger;
@ -30,41 +30,42 @@ namespace Avalonia.Styling
_activator = activator; _activator = activator;
IsActive = _activator is null; IsActive = _activator is null;
if (setters is object) if (setters is not null)
{ {
var setterCount = setters.Count; var setterCount = setters.Count;
_setters = new List<ISetterInstance>(setterCount); _setters = new ISetterInstance[setterCount];
for (var i = 0; i < setterCount; ++i) for (var i = 0; i < setterCount; ++i)
{ {
_setters.Add(setters[i].Instance(Target)); _setters[i] = setters[i].Instance(Target);
} }
} }
if (animations is object && target is Animatable animatable) if (animations is not null && target is Animatable animatable)
{ {
var animationsCount = animations.Count; var animationsCount = animations.Count;
_animations = new List<IDisposable>(animationsCount); _animations = new IDisposable[animationsCount];
_animationTrigger = new Subject<bool>(); _animationTrigger = new Subject<bool>();
for (var i = 0; i < animationsCount; ++i) for (var i = 0; i < animationsCount; ++i)
{ {
_animations.Add(animations[i].Apply(animatable, null, _animationTrigger)); _animations[i] = animations[i].Apply(animatable, null, _animationTrigger);
} }
} }
} }
public bool HasActivator => _activator is not null;
public bool IsActive { get; private set; } public bool IsActive { get; private set; }
public IStyle Source { get; } public IStyle Source { get; }
public IStyleable Target { get; } public IStyleable Target { get; }
public void Start() public void Start()
{ {
var hasActivator = _activator is object; var hasActivator = HasActivator;
if (_setters is object) if (_setters is not null)
{ {
foreach (var setter in _setters) foreach (var setter in _setters)
{ {
@ -76,7 +77,7 @@ namespace Avalonia.Styling
{ {
_activator!.Subscribe(this, 0); _activator!.Subscribe(this, 0);
} }
else if (_animationTrigger != null) else if (_animationTrigger is not null)
{ {
_animationTrigger.OnNext(true); _animationTrigger.OnNext(true);
} }
@ -84,7 +85,7 @@ namespace Avalonia.Styling
public void Dispose() public void Dispose()
{ {
if (_setters is object) if (_setters is not null)
{ {
foreach (var setter in _setters) foreach (var setter in _setters)
{ {
@ -92,11 +93,11 @@ namespace Avalonia.Styling
} }
} }
if (_animations is object) if (_animations is not null)
{ {
foreach (var subscripion in _animations) foreach (var subscription in _animations)
{ {
subscripion.Dispose(); subscription.Dispose();
} }
} }
@ -111,7 +112,7 @@ namespace Avalonia.Styling
_animationTrigger?.OnNext(value); _animationTrigger?.OnNext(value);
if (_setters is object) if (_setters is not null)
{ {
if (IsActive) if (IsActive)
{ {

3
src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs

@ -5,12 +5,11 @@ namespace Avalonia.Threading
public class ThreadSafeObjectPool<T> where T : class, new() public class ThreadSafeObjectPool<T> where T : class, new()
{ {
private Stack<T> _stack = new Stack<T>(); private Stack<T> _stack = new Stack<T>();
private object _lock = new object();
public static ThreadSafeObjectPool<T> Default { get; } = new ThreadSafeObjectPool<T>(); public static ThreadSafeObjectPool<T> Default { get; } = new ThreadSafeObjectPool<T>();
public T Get() public T Get()
{ {
lock (_lock) lock (_stack)
{ {
if(_stack.Count == 0) if(_stack.Count == 0)
return new T(); return new T();

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@ -218,6 +218,8 @@ namespace Avalonia.Controls.Primitives
{ {
// No explicit height values were set so we can autosize // No explicit height values were set so we can autosize
autoSizeHeight = true; autoSizeHeight = true;
// We need to invalidate desired height in order to grow or shrink as needed
InvalidateDesiredHeight();
measureHeight = double.PositiveInfinity; measureHeight = double.PositiveInfinity;
} }
else else

33
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Avalonia.Collections.Pooled;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
@ -172,13 +173,13 @@ namespace Avalonia.Controls.Primitives
if (MonthView != null) if (MonthView != null)
{ {
var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth; var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth;
var children = new List<IControl>(childCount); using var children = new PooledList<IControl>(childCount);
for (int i = 0; i < Calendar.RowsPerMonth; i++) for (int i = 0; i < Calendar.RowsPerMonth; i++)
{ {
if (_dayTitleTemplate != null) if (_dayTitleTemplate != null)
{ {
var cell = _dayTitleTemplate.Build(); var cell = (Control) _dayTitleTemplate.Build();
cell.DataContext = string.Empty; cell.DataContext = string.Empty;
cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.RowProperty, 0);
cell.SetValue(Grid.ColumnProperty, i); cell.SetValue(Grid.ColumnProperty, i);
@ -186,11 +187,16 @@ namespace Avalonia.Controls.Primitives
} }
} }
EventHandler<PointerPressedEventArgs> cellMouseLeftButtonDown = Cell_MouseLeftButtonDown;
EventHandler<PointerReleasedEventArgs> cellMouseLeftButtonUp = Cell_MouseLeftButtonUp;
EventHandler<PointerEventArgs> cellMouseEnter = Cell_MouseEnter;
EventHandler<RoutedEventArgs> cellClick = Cell_Click;
for (int i = 1; i < Calendar.RowsPerMonth; i++) for (int i = 1; i < Calendar.RowsPerMonth; i++)
{ {
for (int j = 0; j < Calendar.ColumnsPerMonth; j++) for (int j = 0; j < Calendar.ColumnsPerMonth; j++)
{ {
CalendarDayButton cell = new CalendarDayButton(); var cell = new CalendarDayButton();
if (Owner != null) if (Owner != null)
{ {
@ -198,10 +204,10 @@ namespace Avalonia.Controls.Primitives
} }
cell.SetValue(Grid.RowProperty, i); cell.SetValue(Grid.RowProperty, i);
cell.SetValue(Grid.ColumnProperty, j); cell.SetValue(Grid.ColumnProperty, j);
cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown; cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown;
cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp; cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp;
cell.PointerEnter += Cell_MouseEnter; cell.PointerEnter += cellMouseEnter;
cell.Click += Cell_Click; cell.Click += cellClick;
children.Add(cell); children.Add(cell);
} }
} }
@ -214,12 +220,15 @@ namespace Avalonia.Controls.Primitives
var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear; var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear;
var children = new List<IControl>(childCount); var children = new List<IControl>(childCount);
CalendarButton month; EventHandler<PointerPressedEventArgs> monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown;
EventHandler<PointerReleasedEventArgs> monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp;
EventHandler<PointerEventArgs> monthMouseEnter = Month_MouseEnter;
for (int i = 0; i < Calendar.RowsPerYear; i++) for (int i = 0; i < Calendar.RowsPerYear; i++)
{ {
for (int j = 0; j < Calendar.ColumnsPerYear; j++) for (int j = 0; j < Calendar.ColumnsPerYear; j++)
{ {
month = new CalendarButton(); var month = new CalendarButton();
if (Owner != null) if (Owner != null)
{ {
@ -227,9 +236,9 @@ namespace Avalonia.Controls.Primitives
} }
month.SetValue(Grid.RowProperty, i); month.SetValue(Grid.RowProperty, i);
month.SetValue(Grid.ColumnProperty, j); month.SetValue(Grid.ColumnProperty, j);
month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown; month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown;
month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp; month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp;
month.PointerEnter += Month_MouseEnter; month.PointerEnter += monthMouseEnter;
children.Add(month); children.Add(month);
} }
} }

75
src/Avalonia.Controls/Canvas.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Reactive.Concurrency;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
@ -159,47 +160,57 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Arranges the control's children. /// Arranges a single child.
/// </summary> /// </summary>
/// <param name="finalSize">The size allocated to the control.</param> /// <param name="child">The child to arrange.</param>
/// <returns>The space taken.</returns> /// <param name="finalSize">The size allocated to the canvas.</param>
protected override Size ArrangeOverride(Size finalSize) protected virtual void ArrangeChild(Control child, Size finalSize)
{ {
foreach (Control child in Children) double x = 0.0;
{ double y = 0.0;
double x = 0.0; double elementLeft = GetLeft(child);
double y = 0.0;
double elementLeft = GetLeft(child);
if (!double.IsNaN(elementLeft)) if (!double.IsNaN(elementLeft))
{ {
x = elementLeft; x = elementLeft;
} }
else else
{
// Arrange with right.
double elementRight = GetRight(child);
if (!double.IsNaN(elementRight))
{ {
// Arrange with right. x = finalSize.Width - child.DesiredSize.Width - elementRight;
double elementRight = GetRight(child);
if (!double.IsNaN(elementRight))
{
x = finalSize.Width - child.DesiredSize.Width - elementRight;
}
} }
}
double elementTop = GetTop(child); double elementTop = GetTop(child);
if (!double.IsNaN(elementTop) ) if (!double.IsNaN(elementTop))
{ {
y = elementTop; y = elementTop;
} }
else else
{
double elementBottom = GetBottom(child);
if (!double.IsNaN(elementBottom))
{ {
double elementBottom = GetBottom(child); y = finalSize.Height - child.DesiredSize.Height - elementBottom;
if (!double.IsNaN(elementBottom))
{
y = finalSize.Height - child.DesiredSize.Height - elementBottom;
}
} }
}
child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
}
/// <summary>
/// Arranges the control's children.
/// </summary>
/// <param name="finalSize">The size allocated to the control.</param>
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
foreach (Control child in Children)
{
ArrangeChild(child, finalSize);
} }
return finalSize; return finalSize;

2
src/Avalonia.Controls/ContextMenu.cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="PlacementRect"/> property. /// Defines the <see cref="PlacementRect"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Rect?> PlacementRectProperty = public static readonly StyledProperty<Rect?> PlacementRectProperty =
AvaloniaProperty.Register<Popup, Rect?>(nameof(PlacementRect)); Popup.PlacementRectProperty.AddOwner<ContextMenu>();
/// <summary> /// <summary>
/// Defines the <see cref="WindowManagerAddShadowHint"/> property. /// Defines the <see cref="WindowManagerAddShadowHint"/> property.

4
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -38,13 +38,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="Header"/> property /// Defines the <see cref="Header"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<object> HeaderProperty = public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
/// <summary> /// <summary>
/// Defines the <see cref="HeaderTemplate"/> property /// Defines the <see cref="HeaderTemplate"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property

2
src/Avalonia.Controls/DockPanel.cs

@ -34,7 +34,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public static readonly StyledProperty<bool> LastChildFillProperty = public static readonly StyledProperty<bool> LastChildFillProperty =
AvaloniaProperty.Register<DockPanel, bool>( AvaloniaProperty.Register<DockPanel, bool>(
nameof(LastChildFillProperty), nameof(LastChildFill),
defaultValue: true); defaultValue: true);
/// <summary> /// <summary>

2
src/Avalonia.Controls/MaskedTextBox.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty); AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
public static new readonly StyledProperty<char> PasswordCharProperty = public static new readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar), '\0'); AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PasswordChar), '\0');
public static readonly StyledProperty<char> PromptCharProperty = public static readonly StyledProperty<char> PromptCharProperty =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_'); AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');

22
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -52,67 +52,67 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<ContentPresenter>(); TextElement.ForegroundProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<ContentPresenter>(); TextElement.FontFamilyProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly StyledProperty<double> FontSizeProperty =
TextElement.FontSizeProperty.AddOwner<ContentPresenter>(); TextElement.FontSizeProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextElement.FontStyleProperty.AddOwner<ContentPresenter>(); TextElement.FontStyleProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly StyledProperty<FontWeight> FontWeightProperty =
TextElement.FontWeightProperty.AddOwner<ContentPresenter>(); TextElement.FontWeightProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="FontStretch"/> property. /// Defines the <see cref="FontStretch"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty = public static readonly StyledProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<ContentPresenter>(); TextElement.FontStretchProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextAlignment"/> property /// Defines the <see cref="TextAlignment"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty = public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>(); TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextWrapping"/> property /// Defines the <see cref="TextWrapping"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>(); TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="TextTrimming"/> property /// Defines the <see cref="TextTrimming"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty = public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>(); TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="LineHeight"/> property /// Defines the <see cref="LineHeight"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<double> LineHeightProperty = public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<ContentPresenter>(); TextBlock.LineHeightProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="MaxLines"/> property /// Defines the <see cref="MaxLines"/> property
/// </summary> /// </summary>
public static readonly AttachedProperty<int> MaxLinesProperty = public static readonly StyledProperty<int> MaxLinesProperty =
TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>(); TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>

31
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -26,14 +26,14 @@ namespace Avalonia.Controls.Presenters
AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(SelectionBrushProperty)); AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(SelectionBrush));
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty = public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(SelectionForegroundBrushProperty)); AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(SelectionForegroundBrush));
public static readonly StyledProperty<IBrush?> CaretBrushProperty = public static readonly StyledProperty<IBrush?> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrushProperty)); AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty = public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>( TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart, o => o.SelectionStart,
@ -43,7 +43,7 @@ namespace Avalonia.Controls.Presenters
TextBox.SelectionEndProperty.AddOwner<TextPresenter>( TextBox.SelectionEndProperty.AddOwner<TextPresenter>(
o => o.SelectionEnd, o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v); (o, v) => o.SelectionEnd = v);
/// <summary> /// <summary>
/// Defines the <see cref="Text"/> property. /// Defines the <see cref="Text"/> property.
/// </summary> /// </summary>
@ -65,6 +65,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextPresenter>(); TextBlock.TextWrappingProperty.AddOwner<TextPresenter>();
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
/// </summary> /// </summary>
@ -179,6 +185,15 @@ namespace Avalonia.Controls.Presenters
get => GetValue(TextWrappingProperty); get => GetValue(TextWrappingProperty);
set => SetValue(TextWrappingProperty, value); set => SetValue(TextWrappingProperty, value);
} }
/// <summary>
/// Gets or sets the line height. By default, this is set to <see cref="double.NaN"/>, which determines the appropriate height automatically.
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the text alignment. /// Gets or sets the text alignment.
@ -253,7 +268,7 @@ namespace Avalonia.Controls.Presenters
get => GetValue(CaretBrushProperty); get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value); set => SetValue(CaretBrushProperty, value);
} }
public int SelectionStart public int SelectionStart
{ {
get get
@ -281,7 +296,7 @@ namespace Avalonia.Controls.Presenters
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
} }
} }
protected override bool BypassFlowDirectionPolicies => true; protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
@ -301,7 +316,7 @@ namespace Avalonia.Controls.Presenters
var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection); flowDirection: FlowDirection, lineHeight: LineHeight);
return textLayout; return textLayout;
} }

2
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -105,7 +105,7 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
child.Arrange(new Rect(finalSize)); ArrangeChild((Control) child, finalSize);
} }
} }
} }

56
src/Avalonia.Controls/Primitives/IPopupHost.cs

@ -2,6 +2,7 @@ using System;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
@ -17,16 +18,50 @@ namespace Avalonia.Controls.Primitives
public interface IPopupHost : IDisposable, IFocusScope public interface IPopupHost : IDisposable, IFocusScope
{ {
/// <summary> /// <summary>
/// Sets the control to display in the popup. /// Gets or sets the fixed width of the popup.
/// </summary> /// </summary>
/// <param name="control"></param> double Width { get; set; }
void SetChild(IControl? control);
/// <summary>
/// Gets or sets the minimum width of the popup.
/// </summary>
double MinWidth { get; set; }
/// <summary>
/// Gets or sets the maximum width of the popup.
/// </summary>
double MaxWidth { get; set; }
/// <summary>
/// Gets or sets the fixed height of the popup.
/// </summary>
double Height { get; set; }
/// <summary>
/// Gets or sets the minimum height of the popup.
/// </summary>
double MinHeight { get; set; }
/// <summary>
/// Gets or sets the maximum height of the popup.
/// </summary>
double MaxHeight { get; set; }
/// <summary> /// <summary>
/// Gets the presenter from the control's template. /// Gets the presenter from the control's template.
/// </summary> /// </summary>
IContentPresenter? Presenter { get; } IContentPresenter? Presenter { get; }
/// <summary>
/// Gets or sets whether the popup appears on top of all other windows.
/// </summary>
bool Topmost { get; set; }
/// <summary>
/// Gets or sets a transform that will be applied to the popup.
/// </summary>
Transform? Transform { get; set; }
/// <summary> /// <summary>
/// Gets the root of the visual tree in the case where the popup is presented using a /// Gets the root of the visual tree in the case where the popup is presented using a
/// separate visual tree. /// separate visual tree.
@ -57,6 +92,12 @@ namespace Avalonia.Controls.Primitives
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
Rect? rect = null); Rect? rect = null);
/// <summary>
/// Sets the control to display in the popup.
/// </summary>
/// <param name="control"></param>
void SetChild(IControl? control);
/// <summary> /// <summary>
/// Shows the popup. /// Shows the popup.
/// </summary> /// </summary>
@ -66,14 +107,5 @@ namespace Avalonia.Controls.Primitives
/// Hides the popup. /// Hides the popup.
/// </summary> /// </summary>
void Hide(); void Hide();
/// <summary>
/// Binds the constraints of the popup host to a set of properties, usally those present on
/// <see cref="Popup"/>.
/// </summary>
IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty,
StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty,
StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty);
} }
} }

42
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -11,6 +11,12 @@ namespace Avalonia.Controls.Primitives
{ {
public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup
{ {
/// <summary>
/// Defines the <see cref="Transform"/> property.
/// </summary>
public static readonly StyledProperty<Transform?> TransformProperty =
PopupRoot.TransformProperty.AddOwner<OverlayPopupHost>();
private readonly OverlayLayer _overlayLayer; private readonly OverlayLayer _overlayLayer;
private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters(); private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
private ManagedPopupPositioner _positioner; private ManagedPopupPositioner _positioner;
@ -29,10 +35,22 @@ namespace Avalonia.Controls.Primitives
} }
public IVisual? HostedVisualTreeRoot => null; public IVisual? HostedVisualTreeRoot => null;
public Transform? Transform
{
get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value);
}
/// <inheritdoc/> /// <inheritdoc/>
IInteractive? IInteractive.InteractiveParent => Parent; IInteractive? IInteractive.InteractiveParent => Parent;
bool IPopupHost.Topmost
{
get => false;
set { /* Not currently supported in overlay popups */ }
}
public void Dispose() => Hide(); public void Dispose() => Hide();
@ -48,28 +66,6 @@ namespace Avalonia.Controls.Primitives
_shown = false; _shown = false;
} }
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
// Topmost property is not supported
var bindings = new List<IDisposable>();
void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
Bind(WidthProperty, widthProperty);
Bind(MinWidthProperty, minWidthProperty);
Bind(MaxWidthProperty, maxWidthProperty);
Bind(HeightProperty, heightProperty);
Bind(MinHeightProperty, minHeightProperty);
Bind(MaxHeightProperty, maxHeightProperty);
return Disposable.Create(() =>
{
foreach (var x in bindings)
x.Dispose();
});
}
public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None,
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,

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

@ -14,6 +14,8 @@ using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -25,7 +27,7 @@ namespace Avalonia.Controls.Primitives
#pragma warning restore CS0612 // Type or member is obsolete #pragma warning restore CS0612 // Type or member is obsolete
{ {
public static readonly StyledProperty<bool> WindowManagerAddShadowHintProperty = public static readonly StyledProperty<bool> WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register<PopupRoot, bool>(nameof(WindowManagerAddShadowHint), false); AvaloniaProperty.Register<Popup, bool>(nameof(WindowManagerAddShadowHint), false);
/// <summary> /// <summary>
/// Defines the <see cref="Child"/> property. /// Defines the <see cref="Child"/> property.
@ -33,6 +35,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<Control?> ChildProperty = public static readonly StyledProperty<Control?> ChildProperty =
AvaloniaProperty.Register<Popup, Control?>(nameof(Child)); AvaloniaProperty.Register<Popup, Control?>(nameof(Child));
/// <summary>
/// Defines the <see cref="InheritsTransform"/> property.
/// </summary>
public static readonly StyledProperty<bool> InheritsTransformProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(InheritsTransform));
/// <summary> /// <summary>
/// Defines the <see cref="IsOpen"/> property. /// Defines the <see cref="IsOpen"/> property.
/// </summary> /// </summary>
@ -196,6 +204,16 @@ namespace Avalonia.Controls.Primitives
set; set;
} }
/// <summary>
/// Gets or sets a value that determines whether the popup inherits the render transform
/// from its <see cref="PlacementTarget"/>. Defaults to false.
/// </summary>
public bool InheritsTransform
{
get => GetValue(InheritsTransformProperty);
set => SetValue(InheritsTransformProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed. /// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
/// </summary> /// </summary>
@ -395,24 +413,29 @@ namespace Avalonia.Controls.Primitives
} }
_isOpenRequested = false; _isOpenRequested = false;
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var handlerCleanup = new CompositeDisposable(7); var handlerCleanup = new CompositeDisposable(7);
popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty, UpdateHostSizing(popupHost, topLevel, placementTarget);
HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty).DisposeWith(handlerCleanup); popupHost.Topmost = Topmost;
popupHost.SetChild(Child); popupHost.SetChild(Child);
((ISetLogicalParent)popupHost).SetParent(this); ((ISetLogicalParent)popupHost).SetParent(this);
popupHost.ConfigurePosition( if (InheritsTransform && placementTarget is Control c)
placementTarget, {
PlacementMode, SubscribeToEventHandler<Control, EventHandler<AvaloniaPropertyChangedEventArgs>>(
new Point(HorizontalOffset, VerticalOffset), c,
PlacementAnchor, PlacementTargetPropertyChanged,
PlacementGravity, (x, handler) => x.PropertyChanged += handler,
PlacementConstraintAdjustment, (x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup);
PlacementRect); }
else
{
popupHost.Transform = null;
}
UpdateHostPosition(popupHost, placementTarget);
SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied, SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied,
(x, handler) => x.TemplateApplied += handler, (x, handler) => x.TemplateApplied += handler,
@ -494,7 +517,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
_openState = new PopupOpenState(topLevel, popupHost, cleanupPopup); _openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup);
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint); WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
@ -542,7 +565,93 @@ namespace Avalonia.Controls.Primitives
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
Close(); Close();
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (_openState is not null)
{
if (change.Property == WidthProperty ||
change.Property == MinWidthProperty ||
change.Property == MaxWidthProperty ||
change.Property == HeightProperty ||
change.Property == MinHeightProperty ||
change.Property == MaxHeightProperty)
{
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
}
else if (change.Property == PlacementTargetProperty ||
change.Property == PlacementModeProperty ||
change.Property == HorizontalOffsetProperty ||
change.Property == VerticalOffsetProperty ||
change.Property == PlacementAnchorProperty ||
change.Property == PlacementConstraintAdjustmentProperty ||
change.Property == PlacementRectProperty)
{
if (change.Property == PlacementTargetProperty)
{
var newTarget = change.GetNewValue<Control?>() ?? this.FindLogicalAncestorOfType<IControl>();
if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel)
{
Close();
return;
}
_openState.PlacementTarget = newTarget;
}
UpdateHostPosition(_openState.PopupHost, _openState.PlacementTarget);
}
else if (change.Property == TopmostProperty)
{
_openState.PopupHost.Topmost = change.GetNewValue<bool>();
}
}
}
private void UpdateHostPosition(IPopupHost popupHost, IControl placementTarget)
{
popupHost.ConfigurePosition(
placementTarget,
PlacementMode,
new Point(HorizontalOffset, VerticalOffset),
PlacementAnchor,
PlacementGravity,
PlacementConstraintAdjustment,
PlacementRect ?? new Rect(default, placementTarget.Bounds.Size));
}
private void UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, IControl placementTarget)
{
var scaleX = 1.0;
var scaleY = 1.0;
if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m)
{
scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
// Ideally we'd only assign a ScaleTransform here when the scale != 1, but there's
// an issue with LayoutTransformControl in that it sets its LayoutTransform property
// with LocalValue priority in ArrangeOverride in certain cases when LayoutTransform
// is null, which breaks TemplateBindings to this property. Offending commit/line:
//
// https://github.com/AvaloniaUI/Avalonia/commit/6fbe1c2180ef45a940e193f1b4637e64eaab80ed#diff-5344e793df13f462126a8153ef46c44194f244b6890f25501709bae51df97f82R54
popupHost.Transform = new ScaleTransform(scaleX, scaleY);
}
else
{
popupHost.Transform = null;
}
popupHost.Width = Width * scaleX;
popupHost.MinWidth = MinWidth * scaleX;
popupHost.MaxWidth = MaxWidth * scaleX;
popupHost.Height = Height * scaleY;
popupHost.MinHeight = MinHeight * scaleY;
popupHost.MaxHeight = MaxHeight * scaleY;
}
private void HandlePositionChange() private void HandlePositionChange()
{ {
if (_openState != null) if (_openState != null)
@ -824,6 +933,14 @@ namespace Avalonia.Controls.Primitives
} }
} }
private void PlacementTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (_openState is not null && e.Property == Visual.TransformedBoundsProperty)
{
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
}
}
private void WindowLostFocus() private void WindowLostFocus()
{ {
if (IsLightDismissEnabled) if (IsLightDismissEnabled)
@ -862,15 +979,16 @@ namespace Avalonia.Controls.Primitives
private readonly IDisposable _cleanup; private readonly IDisposable _cleanup;
private IDisposable? _presenterCleanup; private IDisposable? _presenterCleanup;
public PopupOpenState(TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup) public PopupOpenState(IControl placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup)
{ {
PlacementTarget = placementTarget;
TopLevel = topLevel; TopLevel = topLevel;
PopupHost = popupHost; PopupHost = popupHost;
_cleanup = cleanup; _cleanup = cleanup;
} }
public TopLevel TopLevel { get; } public TopLevel TopLevel { get; }
public IControl PlacementTarget { get; set; }
public IPopupHost PopupHost { get; } public IPopupHost PopupHost { get; }
public void SetPresenterSubscription(IDisposable? presenterCleanup) public void SetPresenterSubscription(IDisposable? presenterCleanup)

39
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -8,7 +6,6 @@ using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -17,6 +14,12 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
{ {
/// <summary>
/// Defines the <see cref="Transform"/> property.
/// </summary>
public static readonly StyledProperty<Transform?> TransformProperty =
AvaloniaProperty.Register<PopupRoot, Transform?>(nameof(Transform));
private PopupPositionerParameters _positionerParameters; private PopupPositionerParameters _positionerParameters;
/// <summary> /// <summary>
@ -54,6 +57,15 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl; public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl;
/// <summary>
/// Gets or sets a transform that will be applied to the popup.
/// </summary>
public Transform? Transform
{
get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value);
}
/// <summary> /// <summary>
/// Gets the parent control in the event route. /// Gets the parent control in the event route.
/// </summary> /// </summary>
@ -103,27 +115,6 @@ namespace Avalonia.Controls.Primitives
IVisual IPopupHost.HostedVisualTreeRoot => this; IVisual IPopupHost.HostedVisualTreeRoot => this;
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
var bindings = new List<IDisposable>();
void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
Bind(WidthProperty, widthProperty);
Bind(MinWidthProperty, minWidthProperty);
Bind(MaxWidthProperty, maxWidthProperty);
Bind(HeightProperty, heightProperty);
Bind(MinHeightProperty, minHeightProperty);
Bind(MaxHeightProperty, maxHeightProperty);
Bind(TopmostProperty, topmostProperty);
return Disposable.Create(() =>
{
foreach (var x in bindings)
x.Dispose();
});
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity; var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;

6
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="IsTextSearchEnabled"/> property. /// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty = public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(IsTextSearchEnabled), false); AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(IsTextSearchEnabled), false);
/// <summary> /// <summary>
/// Event that should be raised by items that implement <see cref="ISelectable"/> to /// Event that should be raised by items that implement <see cref="ISelectable"/> to
@ -111,14 +111,14 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent = public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<SelectingItemsControl, SelectionChangedEventArgs>( RoutedEvent.Register<SelectingItemsControl, SelectionChangedEventArgs>(
"SelectionChanged", nameof(SelectionChanged),
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
/// <summary> /// <summary>
/// Defines the <see cref="WrapSelection"/> property. /// Defines the <see cref="WrapSelection"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> WrapSelectionProperty = public static readonly StyledProperty<bool> WrapSelectionProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(WrapSelection), defaultValue: false); AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(WrapSelection), defaultValue: false);
private static readonly IList Empty = Array.Empty<object>(); private static readonly IList Empty = Array.Empty<object>();
private string _textSearchTerm = string.Empty; private string _textSearchTerm = string.Empty;

2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -97,7 +97,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public static readonly RoutedEvent<TemplateAppliedEventArgs> TemplateAppliedEvent = public static readonly RoutedEvent<TemplateAppliedEventArgs> TemplateAppliedEvent =
RoutedEvent.Register<TemplatedControl, TemplateAppliedEventArgs>( RoutedEvent.Register<TemplatedControl, TemplateAppliedEventArgs>(
"TemplateApplied", nameof(TemplateApplied),
RoutingStrategies.Direct); RoutingStrategies.Direct);
private IControlTemplate? _appliedTemplate; private IControlTemplate? _appliedTemplate;

6
src/Avalonia.Controls/Primitives/Track.cs

@ -44,7 +44,7 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed)); AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed));
public static readonly StyledProperty<bool> IgnoreThumbDragProperty = public static readonly StyledProperty<bool> IgnoreThumbDragProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IsThumbDragHandled)); AvaloniaProperty.Register<Track, bool>(nameof(IgnoreThumbDrag));
private double _minimum; private double _minimum;
private double _maximum = 100.0; private double _maximum = 100.0;
@ -118,7 +118,7 @@ namespace Avalonia.Controls.Primitives
set { SetValue(IsDirectionReversedProperty, value); } set { SetValue(IsDirectionReversedProperty, value); }
} }
public bool IsThumbDragHandled public bool IgnoreThumbDrag
{ {
get { return GetValue(IgnoreThumbDragProperty); } get { return GetValue(IgnoreThumbDragProperty); }
set { SetValue(IgnoreThumbDragProperty, value); } set { SetValue(IgnoreThumbDragProperty, value); }
@ -442,7 +442,7 @@ namespace Avalonia.Controls.Primitives
private void ThumbDragged(object? sender, VectorEventArgs e) private void ThumbDragged(object? sender, VectorEventArgs e)
{ {
if (IsThumbDragHandled) if (IgnoreThumbDrag)
return; return;
Value = MathUtilities.Clamp( Value = MathUtilities.Clamp(

4
src/Avalonia.Controls/Slider.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="TickPlacement"/> property. /// Defines the <see cref="TickPlacement"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TickPlacement> TickPlacementProperty = public static readonly StyledProperty<TickPlacement> TickPlacementProperty =
AvaloniaProperty.Register<TickBar, TickPlacement>(nameof(TickPlacement), 0d); AvaloniaProperty.Register<Slider, TickPlacement>(nameof(TickPlacement), 0d);
/// <summary> /// <summary>
/// Defines the <see cref="TicksProperty"/> property. /// Defines the <see cref="TicksProperty"/> property.
@ -197,7 +197,7 @@ namespace Avalonia.Controls
if (_track != null) if (_track != null)
{ {
_track.IsThumbDragHandled = true; _track.IgnoreThumbDrag = true;
} }
if (_decreaseButton != null) if (_decreaseButton != null)

2
src/Avalonia.Controls/SplitView.cs

@ -138,7 +138,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="PaneTemplate"/> property. /// Defines the <see cref="PaneTemplate"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IDataTemplate> PaneTemplateProperty = public static readonly StyledProperty<IDataTemplate> PaneTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(PaneTemplate)); AvaloniaProperty.Register<SplitView, IDataTemplate>(nameof(PaneTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="UseLightDismissOverlayMode"/> property /// Defines the <see cref="UseLightDismissOverlayMode"/> property

31
src/Avalonia.Controls/TextBox.cs

@ -55,13 +55,13 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrushProperty)); AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush));
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty = public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionForegroundBrushProperty)); AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionForegroundBrush));
public static readonly StyledProperty<IBrush?> CaretBrushProperty = public static readonly StyledProperty<IBrush?> CaretBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(CaretBrushProperty)); AvaloniaProperty.Register<TextBox, IBrush?>(nameof(CaretBrush));
public static readonly DirectProperty<TextBox, int> SelectionStartProperty = public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>( AvaloniaProperty.RegisterDirect<TextBox, int>(
@ -79,8 +79,8 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
public static readonly StyledProperty<int> MaxLinesProperty = public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty = public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>( TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text, o => o.Text,
@ -105,6 +105,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>(); TextBlock.TextWrappingProperty.AddOwner<TextBox>();
/// <summary>
/// Defines see <see cref="TextPresenter.LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextBox>();
public static readonly StyledProperty<string?> WatermarkProperty = public static readonly StyledProperty<string?> WatermarkProperty =
AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark)); AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark));
@ -154,15 +160,15 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
"CopyingToClipboard", RoutingStrategies.Bubble); nameof(CopyingToClipboard), RoutingStrategies.Bubble);
public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
"CuttingToClipboard", RoutingStrategies.Bubble); nameof(CuttingToClipboard), RoutingStrategies.Bubble);
public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
"PastingFromClipboard", RoutingStrategies.Bubble); nameof(PastingFromClipboard), RoutingStrategies.Bubble);
readonly struct UndoRedoState : IEquatable<UndoRedoState> readonly struct UndoRedoState : IEquatable<UndoRedoState>
{ {
@ -358,6 +364,15 @@ namespace Avalonia.Controls
get { return GetValue(MaxLinesProperty); } get { return GetValue(MaxLinesProperty); }
set { SetValue(MaxLinesProperty, value); } set { SetValue(MaxLinesProperty, value); }
} }
/// <summary>
/// Gets or sets the line height.
/// </summary>
public double LineHeight
{
get { return GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
}
[Content] [Content]
public string? Text public string? Text

53
src/Avalonia.Controls/Viewbox.cs

@ -1,4 +1,5 @@
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -8,7 +9,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Viewbox : Control public class Viewbox : Control
{ {
private Decorator _containerVisual; private readonly ViewboxContainer _containerVisual;
/// <summary> /// <summary>
/// Defines the <see cref="Stretch"/> property. /// Defines the <see cref="Stretch"/> property.
@ -37,9 +38,10 @@ namespace Avalonia.Controls
public Viewbox() public Viewbox()
{ {
_containerVisual = new Decorator(); // The Child control is hosted inside a ViewboxContainer control so that the transform
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft; _containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
LogicalChildren.Add(_containerVisual);
VisualChildren.Add(_containerVisual); VisualChildren.Add(_containerVisual);
} }
@ -88,7 +90,22 @@ namespace Avalonia.Controls
if (change.Property == ChildProperty) if (change.Property == ChildProperty)
{ {
_containerVisual.Child = change.GetNewValue<IControl>(); var (oldChild, newChild) = change.GetOldAndNewValue<IControl>();
if (oldChild is not null)
{
((ISetLogicalParent)oldChild).SetParent(null);
LogicalChildren.Remove(oldChild);
}
_containerVisual.Child = newChild;
if (newChild is not null)
{
((ISetLogicalParent)newChild).SetParent(this);
LogicalChildren.Add(newChild);
}
InvalidateMeasure(); InvalidateMeasure();
} }
} }
@ -120,7 +137,7 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize; var childSize = child.DesiredSize;
var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection); var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
InternalTransform = new ScaleTransform(scale.X, scale.Y); InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y));
child.Arrange(new Rect(childSize)); child.Arrange(new Rect(childSize));
@ -129,5 +146,31 @@ namespace Avalonia.Controls
return finalSize; return finalSize;
} }
/// <summary>
/// A simple container control which hosts its child as a visual but not logical child.
/// </summary>
private class ViewboxContainer : Control
{
private IControl? _child;
public IControl? Child
{
get => _child;
set
{
if (_child != value)
{
if (_child is not null)
VisualChildren.Remove(_child);
_child = value;
if (_child is not null)
VisualChildren.Add(_child);
}
}
}
}
} }
} }

4
src/Avalonia.Controls/Window.cs

@ -171,13 +171,13 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Routed event that can be used for global tracking of window destruction /// Routed event that can be used for global tracking of window destruction
/// </summary> /// </summary>
public static readonly RoutedEvent WindowClosedEvent = public static readonly RoutedEvent<RoutedEventArgs> WindowClosedEvent =
RoutedEvent.Register<Window, RoutedEventArgs>("WindowClosed", RoutingStrategies.Direct); RoutedEvent.Register<Window, RoutedEventArgs>("WindowClosed", RoutingStrategies.Direct);
/// <summary> /// <summary>
/// Routed event that can be used for global tracking of opening windows /// Routed event that can be used for global tracking of opening windows
/// </summary> /// </summary>
public static readonly RoutedEvent WindowOpenedEvent = public static readonly RoutedEvent<RoutedEventArgs> WindowOpenedEvent =
RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct); RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct);

1
src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj

@ -18,7 +18,6 @@
<ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> <ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -60,7 +60,8 @@ namespace Avalonia.Diagnostics.ViewModels
var styleDiagnostics = styledElement.GetStyleDiagnostics(); var styleDiagnostics = styledElement.GetStyleDiagnostics();
foreach (var appliedStyle in styleDiagnostics.AppliedStyles) // We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{ {
var styleSource = appliedStyle.Source; var styleSource = appliedStyle.Source;

13
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs

@ -36,6 +36,13 @@ namespace Avalonia.FreeDesktop
private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x)));
private string UnescapeString(string input, string regexText, int escapeBase) =>
new Regex(regexText).Replace(input, m => Convert.ToChar(Convert.ToByte(m.Groups[1].Value, escapeBase)).ToString());
private string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8);
private string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16);
private void Poll(long _) private void Poll(long _)
{ {
var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) var fProcPartitions = File.ReadAllLines(ProcPartitionsDir)
@ -47,14 +54,14 @@ namespace Avalonia.FreeDesktop
var fProcMounts = File.ReadAllLines(ProcMountsDir) var fProcMounts = File.ReadAllLines(ProcMountsDir)
.Select(x => x.Split(' ')) .Select(x => x.Split(' '))
.Select(x => (x[0], x[1])) .Select(x => (x[0], UnescapePathFromProcMounts(x[1])))
.Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase)); .Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase));
var labelDirEnum = Directory.Exists(DevByLabelDir) ? var labelDirEnum = Directory.Exists(DevByLabelDir) ?
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>(); new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>();
var labelDevPathPairs = labelDirEnum var labelDevPathPairs = labelDirEnum
.Select(x => (GetSymlinkTarget(x.FullName), x.Name)); .Select(x => (GetSymlinkTarget(x.FullName), UnescapeDeviceLabel(x.Name)));
var q1 = from mount in fProcMounts var q1 = from mount in fProcMounts
join device in fProcPartitions on mount.Item1 equals device.Item2 join device in fProcPartitions on mount.Item1 equals device.Item2
@ -64,7 +71,7 @@ namespace Avalonia.FreeDesktop
{ {
VolumePath = mount.Item2, VolumePath = mount.Item2,
VolumeSizeBytes = device.Item1, VolumeSizeBytes = device.Item1,
VolumeLabel = x.Name VolumeLabel = x.Item2
}; };
var mountVolInfos = q1.ToArray(); var mountVolInfos = q1.ToArray();

6
src/Avalonia.FreeDesktop/NativeMethods.cs

@ -14,15 +14,15 @@ namespace Avalonia.FreeDesktop
public static string ReadLink(string path) public static string ReadLink(string path)
{ {
var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length); var symlinkSize = Encoding.UTF8.GetByteCount(path);
var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated
var symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1); var symlink = ArrayPool<byte>.Shared.Rent(symlinkSize + 1);
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize); var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try try
{ {
var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0); Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
symlink[symlinkSize] = 0; symlink[symlinkSize] = 0;
var size = readlink(symlink, buffer, bufferSize); var size = readlink(symlink, buffer, bufferSize);

7
src/Avalonia.Native/WindowImpl.cs

@ -107,6 +107,13 @@ namespace Avalonia.Native
private bool _isExtended; private bool _isExtended;
public bool IsClientAreaExtendedToDecorations => _isExtended; public bool IsClientAreaExtendedToDecorations => _isExtended;
public override void Show(bool activate, bool isDialog)
{
base.Show(activate, isDialog);
InvalidateExtendedMargins();
}
protected override bool ChromeHitTest (RawPointerEventArgs e) protected override bool ChromeHitTest (RawPointerEventArgs e)
{ {
if(_isExtended) if(_isExtended)

1
src/Avalonia.Native/avn.idl

@ -2,6 +2,7 @@
@clr-access internal @clr-access internal
@clr-map bool int @clr-map bool int
@cpp-preamble @@ @cpp-preamble @@
#pragma once
#include "com.h" #include "com.h"
#include "stddef.h" #include "stddef.h"
@@ @@

18
src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml

@ -1,4 +1,4 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="OverlayPopupHost"> Selector="OverlayPopupHost">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" /> <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
@ -9,13 +9,15 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<!-- Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test --> <!-- Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test -->
<VisualLayerManager IsPopup="True"> <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
<ContentPresenter Name="PART_ContentPresenter" <VisualLayerManager IsPopup="True">
Background="{TemplateBinding Background}" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/> Content="{TemplateBinding Content}"
</VisualLayerManager> Padding="{TemplateBinding Padding}"/>
</VisualLayerManager>
</LayoutTransformControl>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

22
src/Avalonia.Themes.Default/Controls/PopupRoot.xaml

@ -10,16 +10,18 @@
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" /> <Panel>
<VisualLayerManager IsPopup="True"> <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<ContentPresenter Name="PART_ContentPresenter" <VisualLayerManager IsPopup="True">
Background="{TemplateBinding Background}" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/> Content="{TemplateBinding Content}"
</VisualLayerManager> Padding="{TemplateBinding Padding}"/>
</Panel> </VisualLayerManager>
</Panel>
</LayoutTransformControl>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

1
src/Avalonia.Themes.Default/Controls/TextBox.xaml

@ -76,6 +76,7 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

3
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -119,7 +119,8 @@
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="Background" PlacementTarget="Background"
IsLightDismissEnabled="True"> IsLightDismissEnabled="True"
InheritsTransform="True">
<Border x:Name="PopupBorder" <Border x:Name="PopupBorder"
Background="{DynamicResource ComboBoxDropDownBackground}" Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}" BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"

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

@ -6,13 +6,15 @@
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<VisualLayerManager IsPopup="True"> <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
<ContentPresenter Name="PART_ContentPresenter" <VisualLayerManager IsPopup="True">
Background="{TemplateBinding Background}" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/> Content="{TemplateBinding Content}"
</VisualLayerManager> Padding="{TemplateBinding Padding}"/>
</VisualLayerManager>
</LayoutTransformControl>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

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

@ -10,16 +10,18 @@
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" /> <Panel>
<VisualLayerManager IsPopup="True"> <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<ContentPresenter Name="PART_ContentPresenter" <VisualLayerManager IsPopup="True">
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</VisualLayerManager> </VisualLayerManager>
</Panel> </Panel>
</LayoutTransformControl>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

1
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -89,6 +89,7 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

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

Loading…
Cancel
Save