Browse Source

Merge branch 'master' into pr/8095

pull/8095/head
Steven Kirk 4 years ago
parent
commit
4db83be6dc
  1. 2
      build/CoreLibraries.props
  2. 5
      native/Avalonia.Native/inc/rendertarget.h
  3. 4
      native/Avalonia.Native/src/OSX/AutoFitContentView.h
  4. 6
      native/Avalonia.Native/src/OSX/AutoFitContentView.mm
  5. 30
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  6. 11
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  7. 10
      native/Avalonia.Native/src/OSX/AvnView.h
  8. 10
      native/Avalonia.Native/src/OSX/AvnView.mm
  9. 216
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  10. 4
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  11. 9
      native/Avalonia.Native/src/OSX/PopupImpl.h
  12. 68
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  13. 1
      native/Avalonia.Native/src/OSX/ResizeScope.h
  14. 2
      native/Avalonia.Native/src/OSX/ResizeScope.mm
  15. 1
      native/Avalonia.Native/src/OSX/SystemDialogs.mm
  16. 25
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  17. 134
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  18. 1
      native/Avalonia.Native/src/OSX/WindowImpl.h
  19. 69
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  20. 17
      native/Avalonia.Native/src/OSX/WindowInterfaces.h
  21. 21
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  22. 3
      native/Avalonia.Native/src/OSX/automation.h
  23. 7
      native/Avalonia.Native/src/OSX/automation.mm
  24. 1
      native/Avalonia.Native/src/OSX/main.mm
  25. 1
      native/Avalonia.Native/src/OSX/menu.mm
  26. 1
      samples/BindingDemo/BindingDemo.csproj
  27. 2
      samples/ControlCatalog/ControlCatalog.csproj
  28. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  29. 2
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  30. 3
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  31. 1
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  32. 3
      samples/Previewer/Previewer.csproj
  33. 1
      samples/RenderDemo/RenderDemo.csproj
  34. 1
      samples/Sandbox/Sandbox.csproj
  35. 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  36. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  37. 1
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  38. 6
      src/Avalonia.Base/Collections/AvaloniaList.cs
  39. 19
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  40. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  41. 18
      src/Avalonia.Base/Input/InputElement.cs
  42. 2
      src/Avalonia.Base/Layout/UniformGridLayout.cs
  43. 4
      src/Avalonia.Base/Media/ConicGradientBrush.cs
  44. 8
      src/Avalonia.Base/StyledPropertyBase.cs
  45. 5
      src/Avalonia.Base/Styling/IStyleInstance.cs
  46. 10
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  47. 31
      src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs
  48. 9
      src/Avalonia.Base/Styling/Setter.cs
  49. 35
      src/Avalonia.Base/Styling/StyleInstance.cs
  50. 3
      src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
  51. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  52. 33
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  53. 75
      src/Avalonia.Controls/Canvas.cs
  54. 2
      src/Avalonia.Controls/ContextMenu.cs
  55. 4
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  56. 2
      src/Avalonia.Controls/DockPanel.cs
  57. 2
      src/Avalonia.Controls/MaskedTextBox.cs
  58. 22
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  59. 31
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  60. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  61. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  62. 6
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  63. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  64. 6
      src/Avalonia.Controls/Primitives/Track.cs
  65. 4
      src/Avalonia.Controls/Slider.cs
  66. 2
      src/Avalonia.Controls/SplitView.cs
  67. 31
      src/Avalonia.Controls/TextBox.cs
  68. 53
      src/Avalonia.Controls/Viewbox.cs
  69. 4
      src/Avalonia.Controls/Window.cs
  70. 1
      src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj
  71. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  72. 13
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
  73. 6
      src/Avalonia.FreeDesktop/NativeMethods.cs
  74. 7
      src/Avalonia.Native/WindowImpl.cs
  75. 1
      src/Avalonia.Native/avn.idl
  76. 1
      src/Avalonia.Themes.Default/Controls/TextBox.xaml
  77. 1
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  78. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  79. 17
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  80. 4
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  81. 11
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  82. 2
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs
  83. 104
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  84. 19
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  85. 221
      tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs

2
build/CoreLibraries.props

@ -3,8 +3,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Base/Avalonia.Base.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.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.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />

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
-(void) setNewLayer: (CALayer*) layer;

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

@ -3,8 +3,10 @@
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#pragma once
#import <Foundation/Foundation.h>
#import "avalonia-native.h"
#include "avalonia-native.h"
@interface AutoFitContentView : NSView
-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;

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

@ -4,7 +4,9 @@
//
#include "AvnView.h"
#import "AutoFitContentView.h"
#include "AutoFitContentView.h"
#include "WindowInterfaces.h"
#include "WindowProtocol.h"
@implementation AutoFitContentView
{
@ -83,7 +85,7 @@
_settingSize = true;
[super setFrameSize:newSize];
auto window = objc_cast<AvnWindow>([self window]);
auto window = static_cast<id <AvnWindowProtocol>>([self window]);
// TODO get actual titlebar size

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

@ -9,16 +9,22 @@
/* 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 */; };
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
@ -40,24 +46,29 @@
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.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 */; };
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
/* End PBXBuildFile 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>"; };
1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; };
@ -72,7 +83,6 @@
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>"; };
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>"; };
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>"; };
@ -86,7 +96,6 @@
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; };
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>"; };
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>"; };
@ -142,8 +151,6 @@
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
37E2330E21583241000CB7E2 /* KeyTransform.mm */,
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
@ -166,6 +173,12 @@
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>";
};
@ -193,6 +206,9 @@
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;
};
@ -271,12 +287,14 @@
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.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;
};

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"

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

@ -2,17 +2,15 @@
// 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 "window.h"
#import "comimpl.h"
#import "common.h"
#import "WindowImpl.h"
#import "KeyTransform.h"
#include "common.h"
#include "WindowImpl.h"
#include "KeyTransform.h"
@class AvnAccessibilityElement;

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

@ -4,8 +4,9 @@
//
#import <AppKit/AppKit.h>
#import "AvnView.h"
#include "AvnView.h"
#include "automation.h"
#import "WindowInterfaces.h"
@implementation AvnView
{
@ -194,7 +195,12 @@
- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
auto parentWindow = objc_cast<AvnWindow>([self window]);
if(_parent == nullptr)
{
return TRUE;
}
auto parentWindow = _parent->GetWindowProtocol();
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{

216
native/Avalonia.Native/src/OSX/window.mm → native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -1,16 +1,34 @@
//
// 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"
#import "window.h"
#include "menu.h"
#include "automation.h"
#import "WindowBaseImpl.h"
#include "WindowBaseImpl.h"
#include "WindowImpl.h"
#include "AvnView.h"
#include "WindowInterfaces.h"
#include "PopupImpl.h"
@implementation AvnWindow
@implementation CLASS_NAME
{
ComPtr<WindowBaseImpl> _parent;
bool _canBecomeKeyAndMain;
bool _closed;
bool _isEnabled;
bool _isExtended;
@ -66,7 +84,7 @@
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
if(response == NSModalResponseContinue)
{
dispatch_async(dispatch_get_main_queue(), ^{
@ -85,18 +103,18 @@
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
@ -108,22 +126,22 @@
-(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()];
}
}
@ -134,27 +152,27 @@
{
menu = [AvnMenu new];
}
_menu = menu;
}
-(void) setCanBecomeKeyAndMain
-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
{
_canBecomeKeyAndMain = true;
}
// 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];
-(AvnWindow*) initWithParent: (WindowBaseImpl*) parent
{
self = [super init];
[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;
}
@ -162,12 +180,12 @@
- (BOOL)windowShouldClose:(NSWindow *)sender
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
return !window->WindowEvents->Closing();
}
return true;
}
@ -191,27 +209,26 @@
-(BOOL)canBecomeKeyWindow
{
if (_canBecomeKeyAndMain)
// 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])
{
// 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 = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
if (ch.isDialog)
return false;
}
return true;
auto ch = static_cast<id <AvnWindowProtocol>>(uch);
if(ch == nil)
continue;
if (ch.isDialog)
return false;
}
return false;
return true;
}
-(BOOL)canBecomeMainWindow
{
return _canBecomeKeyAndMain;
#ifdef IS_NSPANEL
return false;
#else
return true;
#endif
}
-(bool)shouldTryToHandleEvents
@ -227,7 +244,7 @@
-(void)becomeKeyWindow
{
[self showWindowMenuWithAppMenu];
if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
@ -238,7 +255,8 @@
-(void) restoreParentWindow;
{
auto parent = objc_cast<AvnWindow>([self parentWindow]);
auto parent = [self parentWindow];
if(parent != nil)
{
[parent removeChildWindow:self];
@ -248,7 +266,7 @@
- (void)windowDidMiniaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
@ -258,7 +276,7 @@
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
@ -268,7 +286,7 @@
- (void)windowDidResize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
@ -278,7 +296,7 @@
- (void)windowWillExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
@ -288,22 +306,22 @@
- (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();
}
}
@ -311,7 +329,7 @@
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
@ -321,7 +339,7 @@
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
@ -338,20 +356,20 @@
{
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)
@ -359,7 +377,7 @@
cparent->SetWindowState(Normal);
}
}
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position);
}
@ -374,7 +392,7 @@
- (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)
{
@ -385,95 +403,39 @@
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;
break;
case NSEventTypeMouseEntered:
{
_parent->UpdateCursor();
}
break;
break;
case NSEventTypeMouseExited:
{
[[NSCursor arrowCursor] set];
}
break;
break;
default:
break;
}
}
}
@end
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;
}
- (void)disconnectParent {
_parent = nullptr;
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool
{
IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
return ptr;
}
}
@end

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

@ -10,8 +10,8 @@
struct INSWindowHolder
{
virtual AvnWindow* _Nonnull GetNSWindow () = 0;
virtual AvnView* _Nonnull GetNSView () = 0;
virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0;
};
#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_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;
}
}

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

@ -6,7 +6,6 @@
#ifndef AVALONIA_NATIVE_OSX_RESIZESCOPE_H
#define AVALONIA_NATIVE_OSX_RESIZESCOPE_H
#include "window.h"
#include "avalonia-native.h"
@class AvnView;

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

@ -5,7 +5,7 @@
#import <AppKit/AppKit.h>
#include "ResizeScope.h"
#import "AvnView.h"
#include "AvnView.h"
ResizeScope::ResizeScope(AvnView *view, AvnPlatformResizeReason reason) {
_view = view;

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

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

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

@ -6,10 +6,12 @@
#ifndef AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
#define AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
#import "rendertarget.h"
#include "rendertarget.h"
#include "INSWindowHolder.h"
@class AutoFitContentView;
@class AvnMenu;
@protocol AvnWindowProtocol;
class WindowBaseImpl : public virtual ComObject,
public virtual IAvnWindowBase,
@ -24,18 +26,19 @@ BEGIN_INTERFACE_MAP()
INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase)
END_INTERFACE_MAP()
virtual ~WindowBaseImpl() {
View = NULL;
Window = NULL;
}
virtual ~WindowBaseImpl();
AutoFitContentView *StandardContainer;
AvnView *View;
AvnWindow *Window;
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;
@ -51,9 +54,9 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT ObtainNSViewHandleRetained(void **ret) override;
virtual AvnWindow *GetNSWindow() override;
virtual NSWindow *GetNSWindow() override;
virtual AvnView *GetNSView() override;
virtual NSView *GetNSView() override;
virtual HRESULT Show(bool activate, bool isDialog) override;
@ -111,11 +114,17 @@ BEGIN_INTERFACE_MAP()
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

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

@ -5,13 +5,21 @@
#import <AppKit/AppKit.h>
#include "common.h"
#import "window.h"
#import "AvnView.h"
#include "AvnView.h"
#include "menu.h"
#include "automation.h"
#import "cursor.h"
#include "cursor.h"
#include "ResizeScope.h"
#import "AutoFitContentView.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;
@ -22,17 +30,15 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
View = [[AvnView alloc] initWithParent:this];
StandardContainer = [[AutoFitContentView new] initWithContent:View];
Window = [[AvnWindow alloc] initWithParent:this];
[Window setContentView:StandardContainer];
lastPositionSet.X = 100;
lastPositionSet.Y = 100;
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
_lastTitle = @"";
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setOpaque:false];
Window = nullptr;
lastMenu = nullptr;
}
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
@ -59,11 +65,11 @@ HRESULT WindowBaseImpl::ObtainNSViewHandleRetained(void **ret) {
return S_OK;
}
AvnWindow *WindowBaseImpl::GetNSWindow() {
NSWindow *WindowBaseImpl::GetNSWindow() {
return Window;
}
AvnView *WindowBaseImpl::GetNSView() {
NSView *WindowBaseImpl::GetNSView() {
return View;
}
@ -83,6 +89,9 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
CreateNSWindow(isDialog);
InitialiseNSWindow();
SetPosition(lastPositionSet);
UpdateStyle();
@ -125,7 +134,8 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool {
if (Window != nullptr) {
[Window orderOut:Window];
[Window restoreParentWindow];
[GetWindowProtocol() restoreParentWindow];
}
return S_OK;
@ -225,8 +235,13 @@ HRESULT WindowBaseImpl::SetMinMaxSize(AvnSize minSize, AvnSize maxSize) {
START_COM_CALL;
@autoreleasepool {
[Window setMinSize:ToNSSize(minSize)];
[Window setMaxSize:ToNSSize(maxSize)];
lastMinSize = ToNSSize(minSize);
lastMaxSize = ToNSSize(maxSize);
if(Window != nullptr) {
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
}
return S_OK;
}
@ -243,8 +258,8 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
auto resizeBlock = ResizeScope(View, reason);
@autoreleasepool {
auto maxSize = [Window maxSize];
auto minSize = [Window minSize];
auto maxSize = lastMaxSize;
auto minSize = lastMinSize;
if (x < minSize.width) {
x = minSize.width;
@ -267,8 +282,11 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
BaseEvents->Resized(AvnSize{x, y}, reason);
}
[Window setContentSize:NSSize{x, y}];
[Window invalidateShadow];
lastSize = NSSize {x, y};
if(Window != nullptr) {
[Window setContentSize:lastSize];
}
}
@finally {
_inResize = false;
@ -293,12 +311,14 @@ HRESULT WindowBaseImpl::SetMainMenu(IAvnMenu *menu) {
auto nativeMenu = dynamic_cast<AvnAppMenu *>(menu);
auto nsmenu = nativeMenu->GetNative();
lastMenu = nativeMenu->GetNative();
[Window applyMenu:nsmenu];
if(Window != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[Window showWindowMenuWithAppMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
return S_OK;
@ -502,4 +522,68 @@ NSWindowStyleMask WindowBaseImpl::GetStyle() {
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;
}
}

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

@ -6,7 +6,6 @@
#ifndef AVALONIA_NATIVE_OSX_WINDOWIMPL_H
#define AVALONIA_NATIVE_OSX_WINDOWIMPL_H
#import "WindowBaseImpl.h"
#include "IWindowStateChanged.h"

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

@ -4,10 +4,10 @@
//
#import <AppKit/AppKit.h>
#import "window.h"
#import "AutoFitContentView.h"
#import "AvnView.h"
#include "AutoFitContentView.h"
#include "AvnView.h"
#include "automation.h"
#include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
_isClientAreaExtended = false;
@ -20,7 +20,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_lastWindowState = Normal;
_actualWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
@ -56,8 +55,20 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
@autoreleasepool {
_isDialog = isDialog;
bool created = Window == nullptr;
WindowBaseImpl::Show(activate, isDialog);
if(created)
{
if(_isClientAreaExtended)
{
[GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true);
}
}
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState);
@ -68,7 +79,7 @@ HRESULT WindowImpl::SetEnabled(bool enable) {
START_COM_CALL;
@autoreleasepool {
[Window setEnabled:enable];
[GetWindowProtocol() setEnabled:enable];
return S_OK;
}
}
@ -328,37 +339,39 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
@autoreleasepool {
_isClientAreaExtended = enable;
if (enable) {
Window.titleVisibility = NSWindowTitleHidden;
if(Window != nullptr) {
if (enable) {
Window.titleVisibility = NSWindowTitleHidden;
[Window setTitlebarAppearsTransparent:true];
[Window setTitlebarAppearsTransparent:true];
auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if (wantsTitleBar) {
[StandardContainer ShowTitleBar:true];
} else {
[StandardContainer ShowTitleBar:false];
}
if (wantsTitleBar) {
[StandardContainer ShowTitleBar:true];
} else {
[StandardContainer ShowTitleBar:false];
}
if (_extendClientHints & AvnOSXThickTitleBar) {
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = 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;
}
} else {
Window.titleVisibility = NSWindowTitleVisible;
Window.toolbar = nullptr;
[Window setTitlebarAppearsTransparent:false];
View.layer.zPosition = 0;
}
[Window setIsExtended:enable];
[GetWindowProtocol() setIsExtended:enable];
HideOrShowTrafficLights();
HideOrShowTrafficLights();
UpdateStyle();
UpdateStyle();
}
return S_OK;
}
@ -383,7 +396,7 @@ HRESULT WindowImpl::GetExtendTitleBarHeight(double *ret) {
return E_POINTER;
}
*ret = [Window getExtendedTitleBarHeight];
*ret = [GetWindowProtocol() getExtendedTitleBarHeight];
return S_OK;
}
@ -508,7 +521,7 @@ bool WindowImpl::IsDialog() {
}
NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = NSWindowStyleMaskBorderless;
unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless;
switch (_decorations) {
case SystemDecorationsNone:

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

21
native/Avalonia.Native/src/OSX/window.h → native/Avalonia.Native/src/OSX/WindowProtocol.h

@ -1,15 +1,15 @@
#ifndef window_h
#define window_h
//
// Created by Dan Walmsley on 06/05/2022.
// Copyright (c) 2022 Avalonia. All rights reserved.
//
#import "avalonia-native.h"
#pragma once
@class AvnMenu;
#import <AppKit/AppKit.h>
class WindowBaseImpl;
@class AvnMenu;
@interface AvnWindow : NSWindow <NSWindowDelegate>
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(void) setCanBecomeKeyAndMain;
@protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
@ -20,7 +20,6 @@ class WindowBaseImpl;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(void) disconnectParent;
-(bool) isDialog;
@end
#endif /* window_h */
@end

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

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

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

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

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

@ -1,7 +1,6 @@
//This file will contain actual IID structures
#define COM_GUIDS_MATERIALIZE
#include "common.h"
#include "window.h"
static NSString* s_appTitle = @"Avalonia";

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

@ -1,7 +1,6 @@
#include "common.h"
#include "menu.h"
#import "window.h"
#include "KeyTransform.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */

1
samples/BindingDemo/BindingDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<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="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>

2
samples/ControlCatalog/ControlCatalog.csproj

@ -25,6 +25,8 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.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.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup>

6
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -66,6 +66,12 @@
FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False"
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 Orientation="Vertical" Spacing="8" Margin="8">
<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[] _windowStates;
private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints;
private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled;

3
samples/IntegrationTestApp/IntegrationTestApp.csproj

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

1
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,6 +7,7 @@
<ItemGroup>
<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" />
</ItemGroup>

3
samples/Previewer/Previewer.csproj

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

1
samples/RenderDemo/RenderDemo.csproj

@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<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="..\MiniMvvm\MiniMvvm.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.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />

1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<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="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
</EmbeddedResource>
</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.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" />

1
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -9,6 +9,7 @@
<ItemGroup>
<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\Avalonia.Diagnostics\Avalonia.Diagnostics.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());
if (notificationItems is not null)
{
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?>();
/// <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>
/// 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)
return null;
var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
var p = GetFirstPropertyWithName(instance, propertyName);
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);
if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins
{
PropertyInfo? found = null;
const BindingFlags bindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var properties = type.GetProperties(bindingFlags);
var properties = type.GetProperties(PropertyBindingFlags);
foreach (PropertyInfo propertyInfo in properties)
{

5
src/Avalonia.Base/DirectPropertyBase.cs

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

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

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

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

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

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

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

8
src/Avalonia.Base/StyledPropertyBase.cs

@ -1,9 +1,7 @@
using System;
using System.Diagnostics;
using Avalonia.Data;
using Avalonia.Reactive;
using Avalonia.Styling;
using Avalonia.Utilities;
namespace Avalonia
{
@ -12,7 +10,7 @@ namespace Avalonia
/// </summary>
public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
{
private bool _inherits;
private readonly bool _inherits;
/// <summary>
/// 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))
{
return new PropertySetterLazyInstance<TValue>(
return new PropertySetterTemplateInstance<TValue>(
target,
this,
() => (TValue)template.Build());
template);
}
else
{

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

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

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

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

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

@ -1,9 +1,7 @@
using System;
using Avalonia.Animation;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Metadata;
using Avalonia.Utilities;
#nullable enable
@ -70,12 +68,5 @@ namespace Avalonia.Styling
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>
/// A <see cref="Style"/> which has been instanced on a control.
/// </summary>
internal class StyleInstance : IStyleInstance, IStyleActivatorSink
internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink
{
private readonly List<ISetterInstance>? _setters;
private readonly List<IDisposable>? _animations;
private readonly ISetterInstance[]? _setters;
private readonly IDisposable[]? _animations;
private readonly IStyleActivator? _activator;
private readonly Subject<bool>? _animationTrigger;
@ -30,41 +30,42 @@ namespace Avalonia.Styling
_activator = activator;
IsActive = _activator is null;
if (setters is object)
if (setters is not null)
{
var setterCount = setters.Count;
_setters = new List<ISetterInstance>(setterCount);
_setters = new ISetterInstance[setterCount];
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;
_animations = new List<IDisposable>(animationsCount);
_animations = new IDisposable[animationsCount];
_animationTrigger = new Subject<bool>();
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 IStyle Source { get; }
public IStyleable Target { get; }
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)
{
@ -76,7 +77,7 @@ namespace Avalonia.Styling
{
_activator!.Subscribe(this, 0);
}
else if (_animationTrigger != null)
else if (_animationTrigger is not null)
{
_animationTrigger.OnNext(true);
}
@ -84,7 +85,7 @@ namespace Avalonia.Styling
public void Dispose()
{
if (_setters is object)
if (_setters is not null)
{
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);
if (_setters is object)
if (_setters is not null)
{
if (IsActive)
{

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

@ -5,12 +5,11 @@ namespace Avalonia.Threading
public class ThreadSafeObjectPool<T> where T : class, new()
{
private Stack<T> _stack = new Stack<T>();
private object _lock = new object();
public static ThreadSafeObjectPool<T> Default { get; } = new ThreadSafeObjectPool<T>();
public T Get()
{
lock (_lock)
lock (_stack)
{
if(_stack.Count == 0)
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
autoSizeHeight = true;
// We need to invalidate desired height in order to grow or shrink as needed
InvalidateDesiredHeight();
measureHeight = double.PositiveInfinity;
}
else

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

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

75
src/Avalonia.Controls/Canvas.cs

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

2
src/Avalonia.Controls/ContextMenu.cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="PlacementRect"/> property.
/// </summary>
public static readonly StyledProperty<Rect?> PlacementRectProperty =
AvaloniaProperty.Register<Popup, Rect?>(nameof(PlacementRect));
Popup.PlacementRectProperty.AddOwner<ContextMenu>();
/// <summary>
/// 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
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property

2
src/Avalonia.Controls/DockPanel.cs

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

2
src/Avalonia.Controls/MaskedTextBox.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
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 =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');

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

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

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

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

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

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

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

@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives
#pragma warning restore CS0612 // Type or member is obsolete
{
public static readonly StyledProperty<bool> WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register<PopupRoot, bool>(nameof(WindowManagerAddShadowHint), false);
AvaloniaProperty.Register<Popup, bool>(nameof(WindowManagerAddShadowHint), false);
/// <summary>
/// Defines the <see cref="Child"/> property.

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

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

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

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

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

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

4
src/Avalonia.Controls/Slider.cs

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

2
src/Avalonia.Controls/SplitView.cs

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

53
src/Avalonia.Controls/Viewbox.cs

@ -1,4 +1,5 @@
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -8,7 +9,7 @@ namespace Avalonia.Controls
/// </summary>
public class Viewbox : Control
{
private Decorator _containerVisual;
private readonly ViewboxContainer _containerVisual;
/// <summary>
/// Defines the <see cref="Stretch"/> property.
@ -37,9 +38,10 @@ namespace Avalonia.Controls
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;
LogicalChildren.Add(_containerVisual);
VisualChildren.Add(_containerVisual);
}
@ -88,7 +90,22 @@ namespace Avalonia.Controls
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();
}
}
@ -120,7 +137,7 @@ namespace Avalonia.Controls
var childSize = child.DesiredSize;
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));
@ -129,5 +146,31 @@ namespace Avalonia.Controls
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>
/// Routed event that can be used for global tracking of window destruction
/// </summary>
public static readonly RoutedEvent WindowClosedEvent =
public static readonly RoutedEvent<RoutedEventArgs> WindowClosedEvent =
RoutedEvent.Register<Window, RoutedEventArgs>("WindowClosed", RoutingStrategies.Direct);
/// <summary>
/// Routed event that can be used for global tracking of opening windows
/// </summary>
public static readonly RoutedEvent WindowOpenedEvent =
public static readonly RoutedEvent<RoutedEventArgs> WindowOpenedEvent =
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="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.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();
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;

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 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 _)
{
var fProcPartitions = File.ReadAllLines(ProcPartitionsDir)
@ -47,14 +54,14 @@ namespace Avalonia.FreeDesktop
var fProcMounts = File.ReadAllLines(ProcMountsDir)
.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));
var labelDirEnum = Directory.Exists(DevByLabelDir) ?
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>();
var labelDevPathPairs = labelDirEnum
.Select(x => (GetSymlinkTarget(x.FullName), x.Name));
.Select(x => (GetSymlinkTarget(x.FullName), UnescapeDeviceLabel(x.Name)));
var q1 = from mount in fProcMounts
join device in fProcPartitions on mount.Item1 equals device.Item2
@ -64,7 +71,7 @@ namespace Avalonia.FreeDesktop
{
VolumePath = mount.Item2,
VolumeSizeBytes = device.Item1,
VolumeLabel = x.Name
VolumeLabel = x.Item2
};
var mountVolInfos = q1.ToArray();

6
src/Avalonia.FreeDesktop/NativeMethods.cs

@ -14,15 +14,15 @@ namespace Avalonia.FreeDesktop
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 symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1);
var symlink = ArrayPool<byte>.Shared.Rent(symlinkSize + 1);
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
symlink[symlinkSize] = 0;
var size = readlink(symlink, buffer, bufferSize);

7
src/Avalonia.Native/WindowImpl.cs

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

1
src/Avalonia.Native/avn.idl

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

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

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

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

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

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs

@ -55,7 +55,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return cached.get;
}
var name = lst.Count == 0 ? key : key + "_" + Guid.NewGuid().ToString("N");
var name = lst.Count == 0 ? key : key + "_" + context.Configuration.IdentifierGenerator.GenerateIdentifierPart();
var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true);

17
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@ -146,6 +146,23 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.True(raised);
}
[Fact]
public void AddRange_IEnumerable_Should_Raise_Count_PropertyChanged()
{
var target = new AvaloniaList<int>(new[] { 1, 2, 3, 4, 5 });
var raised = false;
target.PropertyChanged += (s, e) => {
Assert.Equal(e.PropertyName, nameof(target.Count));
Assert.Equal(target.Count, 7);
raised = true;
};
target.AddRange(Enumerable.Range(6, 2));
Assert.True(raised);
}
[Fact]
public void AddRange_Items_Should_Raise_Correct_CollectionChanged()
{

4
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -247,10 +247,10 @@ namespace Avalonia.Base.UnitTests.Input
new[]
{
((object?)decorator, "PointerEnter"),
(decorator, "PointerMove"),
(decorator, "PointerMoved"),
(decorator, "PointerLeave"),
(canvas, "PointerEnter"),
(canvas, "PointerMove")
(canvas, "PointerMoved")
},
result);
}

11
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@ -91,16 +91,13 @@ namespace Avalonia.Base.UnitTests.Styling
[Fact]
public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority()
{
var control = new Mock<IStyleable>();
var style = Mock.Of<Style>();
var control = new Control();
var setter = new Setter(TextBlock.TagProperty, "foo");
setter.Instance(control.Object).Start(false);
setter.Instance(control).Start(false);
control.Verify(x => x.SetValue(
TextBlock.TagProperty,
"foo",
BindingPriority.Style));
Assert.Equal("foo", control.Tag);
Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority);
}
[Fact]

2
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests
first.Day == second.Day;
}
[Fact(Skip ="FIX ME ASAP")]
[Fact]
public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set()
{
bool handled = false;

104
tests/Avalonia.Controls.UnitTests/ViewboxTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
@ -18,11 +19,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
Assert.Equal(2.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(2.0, scale.X);
Assert.Equal(2.0, scale.Y);
}
[Fact]
@ -36,11 +36,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(100, 50), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(1.0, scaleTransform.ScaleX);
Assert.Equal(1.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(1.0, scale.X);
Assert.Equal(1.0, scale.Y);
}
[Fact]
@ -54,11 +53,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
Assert.Equal(4.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(2.0, scale.X);
Assert.Equal(4.0, scale.Y);
}
[Fact]
@ -72,11 +70,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(4.0, scaleTransform.ScaleX);
Assert.Equal(4.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(4.0, scale.X);
Assert.Equal(4.0, scale.Y);
}
[Fact]
@ -90,11 +87,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(400, 200), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(4.0, scaleTransform.ScaleX);
Assert.Equal(4.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(4.0, scale.X);
Assert.Equal(4.0, scale.Y);
}
[Fact]
@ -108,11 +104,10 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
Assert.Equal(2.0, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(2.0, scale.X);
Assert.Equal(2.0, scale.Y);
}
[Theory]
@ -136,11 +131,9 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(expectedScale, scaleTransform.ScaleX);
Assert.Equal(expectedScale, scaleTransform.ScaleY);
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(expectedScale, scale.X);
Assert.Equal(expectedScale, scale.Y);
}
[Theory]
@ -164,11 +157,44 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.True(TryGetScale(target, out Vector scale));
Assert.Equal(expectedScale, scale.X);
Assert.Equal(expectedScale, scale.Y);
}
[Fact]
public void Child_Should_Be_Logical_Child_Of_Viewbox()
{
var target = new Viewbox();
Assert.Empty(target.GetLogicalChildren());
var child = new Canvas();
target.Child = child;
Assert.Single(target.GetLogicalChildren(), child);
Assert.Same(child.GetLogicalParent(), target);
target.Child = null;
Assert.Empty(target.GetLogicalChildren());
Assert.Null(child.GetLogicalParent());
}
private bool TryGetScale(Viewbox viewbox, out Vector scale)
{
if (viewbox.InternalTransform is null)
{
scale = default;
return false;
}
var matrix = viewbox.InternalTransform.Value;
Matrix.TryDecomposeTransform(matrix, out var decomposed);
Assert.NotNull(scaleTransform);
Assert.Equal(expectedScale, scaleTransform.ScaleX);
Assert.Equal(expectedScale, scaleTransform.ScaleY);
scale = decomposed.Scale;
return true;
}
}
}

19
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -617,6 +617,25 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(0, source.SubscriberCount);
}
[Fact]
public void Binding_Can_Resolve_Property_From_IReflectableType_Type()
{
var source = new DynamicReflectableType { ["Foo"] = "foo" };
var target = new TwoWayBindingTest { DataContext = source };
var binding = new Binding
{
Path = "Foo",
};
target.Bind(TwoWayBindingTest.TwoWayProperty, binding);
Assert.Equal("foo", target.TwoWay);
source["Foo"] = "bar";
Assert.Equal("bar", target.TwoWay);
target.TwoWay = "baz";
Assert.Equal("baz", source["Foo"]);
}
private class StyledPropertyClass : AvaloniaObject
{

221
tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs

@ -0,0 +1,221 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using Moq;
namespace Avalonia.Markup.UnitTests.Data;
class DynamicReflectableType : IReflectableType, INotifyPropertyChanged, IEnumerable<KeyValuePair<string, object>>
{
private Dictionary<string, object> _dic = new();
public TypeInfo GetTypeInfo()
{
return new FakeTypeInfo();
}
public void Add(string key, object value)
{
_dic.Add(key, value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}
public object this[string key]
{
get => _dic[key];
set
{
_dic[key] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _dic.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dic).GetEnumerator();
}
class FakeTypeInfo : TypeInfo
{
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types,
ParameterModifier[] modifiers)
{
var propInfo = new Mock<PropertyInfo>();
propInfo.SetupGet(x => x.Name).Returns(name);
propInfo.SetupGet(x => x.PropertyType).Returns(typeof(object));
propInfo.SetupGet(x => x.CanWrite).Returns(true);
propInfo.Setup(x => x.GetValue(It.IsAny<object>(), It.IsAny<object[]>()))
.Returns((object target, object [] _) => ((DynamicReflectableType)target)._dic.GetValueOrDefault(name));
propInfo.Setup(x => x.SetValue(It.IsAny<object>(), It.IsAny<object>(), It.IsAny<object[]>()))
.Callback((object target, object value, object [] _) =>
{
((DynamicReflectableType)target)._dic[name] = value;
});
return propInfo.Object;
}
#region NotSupported
public override object[] GetCustomAttributes(bool inherit)
{
throw new NotSupportedException();
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}
public override bool IsDefined(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}
public override Module Module { get; }
public override string Namespace { get; }
public override string Name { get; }
protected override TypeAttributes GetAttributeFlagsImpl()
{
throw new NotSupportedException();
}
protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}
public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type GetElementType()
{
throw new NotSupportedException();
}
public override EventInfo GetEvent(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override EventInfo[] GetEvents(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override FieldInfo GetField(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override FieldInfo[] GetFields(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention,
Type[] types, ParameterModifier[] modifiers)
{
throw new NotSupportedException();
}
public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args,
ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
throw new NotSupportedException();
}
public override Type UnderlyingSystemType { get; }
protected override bool IsArrayImpl()
{
throw new NotSupportedException();
}
protected override bool IsByRefImpl()
{
throw new NotSupportedException();
}
protected override bool IsCOMObjectImpl()
{
throw new NotSupportedException();
}
protected override bool IsPointerImpl()
{
throw new NotSupportedException();
}
protected override bool IsPrimitiveImpl()
{
throw new NotSupportedException();
}
public override Assembly Assembly { get; }
public override string AssemblyQualifiedName { get; }
public override Type BaseType { get; }
public override string FullName { get; }
public override Guid GUID { get; }
protected override bool HasElementTypeImpl()
{
throw new NotSupportedException();
}
public override Type GetNestedType(string name, BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type[] GetNestedTypes(BindingFlags bindingAttr)
{
throw new NotSupportedException();
}
public override Type GetInterface(string name, bool ignoreCase)
{
throw new NotSupportedException();
}
public override Type[] GetInterfaces()
{
throw new NotSupportedException();
}
#endregion
}
}
Loading…
Cancel
Save