Browse Source

Merge branch 'compiled-bindings' of https://github.com/jkoritzinsky/Avalonia into compiled-bindings

pull/2734/head
Jeremy Koritzinsky 7 years ago
parent
commit
d415513341
  1. 1
      Avalonia.sln.DotSettings
  2. 2
      build/Microsoft.Reactive.Testing.props
  3. 2
      build/ReactiveUI.props
  4. 2
      build/Rx.props
  5. 2
      build/System.Memory.props
  6. 3
      dirs.proj
  7. 2
      global.json
  8. 33
      native/Avalonia.Native/inc/avalonia-native.h
  9. 9
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  10. 8
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  11. 2
      native/Avalonia.Native/src/OSX/Screens.mm
  12. 12
      native/Avalonia.Native/src/OSX/common.h
  13. 151
      native/Avalonia.Native/src/OSX/main.mm
  14. 80
      native/Avalonia.Native/src/OSX/menu.h
  15. 305
      native/Avalonia.Native/src/OSX/menu.mm
  16. 6
      native/Avalonia.Native/src/OSX/platformthreading.mm
  17. 1
      native/Avalonia.Native/src/OSX/window.h
  18. 128
      native/Avalonia.Native/src/OSX/window.mm
  19. 3
      samples/BindingDemo/MainWindow.xaml
  20. 7
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  21. 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  22. 2
      samples/ControlCatalog.Android/MainActivity.cs
  23. 6
      samples/ControlCatalog.NetCore/Program.cs
  24. 18
      samples/ControlCatalog/App.xaml
  25. 13
      samples/ControlCatalog/App.xaml.cs
  26. 25
      samples/ControlCatalog/DecoratedWindow.xaml
  27. 3
      samples/ControlCatalog/MainView.xaml
  28. 31
      samples/ControlCatalog/MainWindow.xaml
  29. 14
      samples/ControlCatalog/MainWindow.xaml.cs
  30. 9
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  31. 2
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  32. 2
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  33. 5
      samples/ControlCatalog/Pages/MenuPage.xaml
  34. 7
      samples/ControlCatalog/Pages/ScreenPage.cs
  35. 39
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  36. 5
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  37. 20
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  38. 6
      src/Android/Avalonia.Android/AndroidPlatform.cs
  39. 2
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  40. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  41. 2
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  42. 7
      src/Avalonia.Animation/Animatable.cs
  43. 16
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  44. 23
      src/Avalonia.Base/AvaloniaObject.cs
  45. 9
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  46. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  47. 10
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  48. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  49. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  50. 71
      src/Avalonia.Base/Logging/ILogSink.cs
  51. 125
      src/Avalonia.Base/Logging/Logger.cs
  52. 174
      src/Avalonia.Base/Logging/ParametrizedLogger.cs
  53. 23
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  54. 23
      src/Avalonia.Base/PriorityBindingEntry.cs
  55. 62
      src/Avalonia.Base/PriorityLevel.cs
  56. 2
      src/Avalonia.Base/PriorityValue.cs
  57. 2
      src/Avalonia.Base/Utilities/SingleOrDictionary.cs
  58. 8
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  59. 50
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  60. 4
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  61. 4
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  62. 8
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  63. 4
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  64. 50
      src/Avalonia.Controls/AppBuilderBase.cs
  65. 19
      src/Avalonia.Controls/Application.cs
  66. 3
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  67. 18
      src/Avalonia.Controls/AutoCompleteBox.cs
  68. 85
      src/Avalonia.Controls/Button.cs
  69. 25
      src/Avalonia.Controls/Calendar/Calendar.cs
  70. 2
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  71. 2
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  72. 22
      src/Avalonia.Controls/Calendar/DatePicker.cs
  73. 12
      src/Avalonia.Controls/Carousel.cs
  74. 4
      src/Avalonia.Controls/ComboBox.cs
  75. 2
      src/Avalonia.Controls/ContentControl.cs
  76. 2
      src/Avalonia.Controls/ContextMenu.cs
  77. 2
      src/Avalonia.Controls/DataValidationErrors.cs
  78. 2
      src/Avalonia.Controls/Decorator.cs
  79. 4
      src/Avalonia.Controls/DropDown.cs
  80. 2
      src/Avalonia.Controls/Expander.cs
  81. 4
      src/Avalonia.Controls/ItemsControl.cs
  82. 8
      src/Avalonia.Controls/LayoutTransformControl.cs
  83. 2
      src/Avalonia.Controls/MenuBase.cs
  84. 16
      src/Avalonia.Controls/MenuItem.cs
  85. 85
      src/Avalonia.Controls/NativeMenu.Export.cs
  86. 60
      src/Avalonia.Controls/NativeMenu.cs
  87. 36
      src/Avalonia.Controls/NativeMenuBar.cs
  88. 162
      src/Avalonia.Controls/NativeMenuItem.cs
  89. 23
      src/Avalonia.Controls/NativeMenuItemBase.cs
  90. 10
      src/Avalonia.Controls/NativeMenuItemSeperator.cs
  91. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  92. 18
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  93. 5
      src/Avalonia.Controls/Platform/Screen.cs
  94. 4
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  95. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  96. 2
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  97. 2
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  98. 6
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  99. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  100. 2
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

1
Avalonia.sln.DotSettings

@ -3,6 +3,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>

2
build/Microsoft.Reactive.Testing.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" /> <PackageReference Include="Microsoft.Reactive.Testing" Version="4.1.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="reactiveui" Version="9.0.1" /> <PackageReference Include="ReactiveUI" Version="10.3.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Rx.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive" Version="4.0.0" /> <PackageReference Include="System.Reactive" Version="4.1.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/System.Memory.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.1" /> <PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

3
dirs.proj

@ -8,7 +8,8 @@
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')"> <!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
<ItemGroup>
<ProjectReference Remove="src/Android/**/*.*proj" /> <ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" /> <ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup> </ItemGroup>

2
global.json

@ -1,7 +1,7 @@
{ {
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43", "Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "1.6.65", "MSBuild.Sdk.Extras": "2.0.46",
"AggregatePackage.NuGet.Sdk" : "0.1.12" "AggregatePackage.NuGet.Sdk" : "0.1.12"
} }
} }

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

@ -22,6 +22,8 @@ struct IAvnGlContext;
struct IAvnGlDisplay; struct IAvnGlDisplay;
struct IAvnGlSurfaceRenderTarget; struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession; struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
struct AvnSize struct AvnSize
{ {
@ -52,6 +54,7 @@ struct AvnScreen
{ {
AvnRect Bounds; AvnRect Bounds;
AvnRect WorkingArea; AvnRect WorkingArea;
float PixelDensity;
bool Primary; bool Primary;
}; };
@ -172,6 +175,11 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0;
}; };
AVNCOM(IAvnString, 17) : IUnknown AVNCOM(IAvnString, 17) : IUnknown
@ -187,7 +195,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT Close() = 0; virtual HRESULT Close() = 0;
virtual HRESULT Activate () = 0; virtual HRESULT Activate () = 0;
virtual HRESULT GetClientSize(AvnSize*ret) = 0; virtual HRESULT GetClientSize(AvnSize*ret) = 0;
virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0;
virtual HRESULT GetScaling(double*ret)=0; virtual HRESULT GetScaling(double*ret)=0;
virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0; virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0;
virtual HRESULT Resize(double width, double height) = 0; virtual HRESULT Resize(double width, double height) = 0;
@ -203,6 +210,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT SetCursor(IAvnCursor* cursor) = 0; virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0; virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0; virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
virtual bool TryLock() = 0; virtual bool TryLock() = 0;
virtual void Unlock() = 0; virtual void Unlock() = 0;
}; };
@ -258,6 +267,7 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
AVNCOM(IAvnMacOptions, 07) : IUnknown AVNCOM(IAvnMacOptions, 07) : IUnknown
{ {
virtual HRESULT SetShowInDock(int show) = 0; virtual HRESULT SetShowInDock(int show) = 0;
virtual HRESULT SetApplicationTitle (void* utf8string) = 0;
}; };
AVNCOM(IAvnActionCallback, 08) : IUnknown AVNCOM(IAvnActionCallback, 08) : IUnknown
@ -367,4 +377,25 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown
virtual HRESULT GetScaling(double* ret) = 0; virtual HRESULT GetScaling(double* ret) = 0;
}; };
AVNCOM(IAvnAppMenu, 17) : IUnknown
{
virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT Clear () = 0;
};
AVNCOM(IAvnPredicateCallback, 18) : IUnknown
{
virtual bool Evaluate() = 0;
};
AVNCOM(IAvnAppMenuItem, 19) : IUnknown
{
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative(); extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

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

@ -8,10 +8,12 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; }; 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; }; 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; }; 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -24,6 +26,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; }; 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; }; 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
@ -32,6 +35,7 @@
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; }; 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; }; 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -85,6 +89,8 @@
AB661C1F2148286E00291242 /* window.mm */, AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */, 37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */, AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
37A517B22159597E00FBA241 /* Screens.mm */, 37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */, AB7A61F02147C815003C5833 /* Products */,
@ -107,6 +113,7 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -150,6 +157,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
); );
mainGroup = AB7A61E62147C814003C5833; mainGroup = AB7A61E62147C814003C5833;
@ -173,6 +181,7 @@
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */, 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,

8
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

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

@ -38,6 +38,8 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width; ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->PixelDensity = [screen backingScaleFactor];
ret->Primary = index == 0; ret->Primary = index == 0;
return S_OK; return S_OK;

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

@ -19,6 +19,13 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature(); extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view); extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem();
extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu);
extern IAvnAppMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp(); extern void InitializeAvnApp();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p); extern NSPoint ToNSPoint (AvnPoint p);
@ -40,4 +47,9 @@ template<typename T> inline T* objc_cast(id from) {
return nil; return nil;
} }
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
#endif #endif

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

@ -5,10 +5,121 @@
#define COM_GUIDS_MATERIALIZE #define COM_GUIDS_MATERIALIZE
#include "common.h" #include "common.h"
static NSString* s_appTitle = @"Avalonia";
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void SetProcessName(NSString* appTitle) {
s_appTitle = appTitle;
CFStringRef process_name = (__bridge CFStringRef)appTitle;
if (!process_name || CFStringGetLength(process_name) == 0) {
//NOTREACHED() << "SetProcessName given bad name.";
return;
}
if (![NSThread isMainThread]) {
//NOTREACHED() << "Should only set process name from main thread.";
return;
}
// Warning: here be dragons! This is SPI reverse-engineered from WebKit's
// plugin host, and could break at any time (although realistically it's only
// likely to break in a new major release).
// When 10.7 is available, check that this still works, and update this
// comment for 10.8.
// Private CFType used in these LaunchServices calls.
typedef CFTypeRef PrivateLSASN;
typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
CFStringRef,
CFStringRef,
CFDictionaryRef*);
static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
NULL;
static LSSetApplicationInformationItemType
ls_set_application_information_item_func = NULL;
static CFStringRef ls_display_name_key = NULL;
static bool did_symbol_lookup = false;
if (!did_symbol_lookup) {
did_symbol_lookup = true;
CFBundleRef launch_services_bundle =
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
if (!launch_services_bundle) {
//LOG(ERROR) << "Failed to look up LaunchServices bundle";
return;
}
ls_get_current_application_asn_func =
reinterpret_cast<LSGetCurrentApplicationASNType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
if (!ls_get_current_application_asn_func){}
//LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";
ls_set_application_information_item_func =
reinterpret_cast<LSSetApplicationInformationItemType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle,
CFSTR("_LSSetApplicationInformationItem")));
if (!ls_set_application_information_item_func){}
//LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";
CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
CFBundleGetDataPointerForName(launch_services_bundle,
CFSTR("_kLSDisplayNameKey")));
ls_display_name_key = key_pointer ? *key_pointer : NULL;
if (!ls_display_name_key){}
//LOG(ERROR) << "Could not find _kLSDisplayNameKey";
// Internally, this call relies on the Mach ports that are started up by the
// Carbon Process Manager. In debug builds this usually happens due to how
// the logging layers are started up; but in release, it isn't started in as
// much of a defined order. So if the symbols had to be loaded, go ahead
// and force a call to make sure the manager has been initialized and hence
// the ports are opened.
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
}
if (!ls_get_current_application_asn_func ||
!ls_set_application_information_item_func ||
!ls_display_name_key) {
return;
}
PrivateLSASN asn = ls_get_current_application_asn_func();
// Constant used by WebKit; what exactly it means is unknown.
const int magic_session_constant = -2;
OSErr err =
ls_set_application_information_item_func(magic_session_constant, asn,
ls_display_name_key,
process_name,
NULL /* optional out param */);
//LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}
class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions> class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
{ {
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual HRESULT SetApplicationTitle(void* utf8String) override
{
auto appTitle = [NSString stringWithUTF8String:(const char*)utf8String];
[[NSProcessInfo processInfo] setProcessName:appTitle];
SetProcessName(appTitle);
return S_OK;
}
virtual HRESULT SetShowInDock(int show) override virtual HRESULT SetShowInDock(int show) override
{ {
AvnDesiredActivationPolicy = show AvnDesiredActivationPolicy = show
@ -17,8 +128,6 @@ public:
} }
}; };
/// See "Using POSIX Threads in a Cocoa Application" section here: /// See "Using POSIX Threads in a Cocoa Application" section here:
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024 /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
@interface ThreadingInitializer : NSObject @interface ThreadingInitializer : NSObject
@ -43,8 +152,6 @@ public:
close(_fds[0]); close(_fds[0]);
close(_fds[1]); close(_fds[1]);
} }
@end @end
@ -123,6 +230,42 @@ public:
*ppv = rv; *ppv = rv;
return S_OK; return S_OK;
} }
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override
{
*ppv = ::CreateAppMenu();
return S_OK;
}
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItem();
return S_OK;
}
virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItemSeperator();
return S_OK;
}
virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override
{
::SetAppMenu(s_appTitle, appMenu);
return S_OK;
}
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override
{
if(retOut == nullptr)
{
return E_POINTER;
}
*retOut = ::GetAppMenu();
return S_OK;
}
}; };
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

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

@ -0,0 +1,80 @@
//
// menu.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 01/08/2019.
// Copyright © 2019 Avalonia. All rights reserved.
//
#ifndef menu_h
#define menu_h
#include "common.h"
class AvnAppMenuItem;
class AvnAppMenu;
@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
- (void)setMenu:(NSMenu*) menu;
@end
@interface AvnMenuItem : NSMenuItem
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem;
- (void)didSelectItem:(id)sender;
@end
class AvnAppMenuItem : public ComSingleObject<IAvnAppMenuItem, &IID_IAvnAppMenuItem>
{
private:
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
IAvnActionCallback* _callback;
IAvnPredicateCallback* _predicate;
bool _isSeperator;
public:
FORWARD_IUNKNOWN()
AvnAppMenuItem(bool isSeperator);
NSMenuItem* GetNative();
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override;
bool EvaluateItemEnabled();
void RaiseOnClicked();
};
class AvnAppMenu : public ComSingleObject<IAvnAppMenu, &IID_IAvnAppMenu>
{
private:
AvnMenu* _native;
public:
FORWARD_IUNKNOWN()
AvnAppMenu();
AvnAppMenu(AvnMenu* native);
AvnMenu* GetNative();
virtual HRESULT AddItem (IAvnAppMenuItem* item) override;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT Clear () override;
};
#endif

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

@ -0,0 +1,305 @@
#include "common.h"
#include "menu.h"
@implementation AvnMenu
@end
@implementation AvnMenuItem
{
AvnAppMenuItem* _item;
}
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
{
if(self != nil)
{
_item = menuItem;
self = [super initWithTitle:@""
action:@selector(didSelectItem:)
keyEquivalent:@""];
[self setEnabled:YES];
[self setTarget:self];
}
return self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if([self submenu] != nil)
{
return YES;
}
return _item->EvaluateItemEnabled();
}
- (void)didSelectItem:(nullable id)sender
{
_item->RaiseOnClicked();
}
@end
AvnAppMenuItem::AvnAppMenuItem(bool isSeperator)
{
_isSeperator = isSeperator;
if(isSeperator)
{
_native = [NSMenuItem separatorItem];
}
else
{
_native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this];
}
_callback = nullptr;
}
NSMenuItem* AvnAppMenuItem::GetNative()
{
return _native;
}
HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu)
{
auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative();
[_native setSubmenu: nsMenu];
return S_OK;
}
HRESULT AvnAppMenuItem::SetTitle (void* utf8String)
{
if (utf8String != nullptr)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
}
return S_OK;
}
HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers)
{
NSEventModifierFlags flags = 0;
if (modifiers & Control)
flags |= NSEventModifierFlagControl;
if (modifiers & Shift)
flags |= NSEventModifierFlagShift;
if (modifiers & Alt)
flags |= NSEventModifierFlagOption;
if (modifiers & Windows)
flags |= NSEventModifierFlagCommand;
[_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
[_native setKeyEquivalentModifierMask:flags];
return S_OK;
}
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
{
_predicate = predicate;
_callback = callback;
return S_OK;
}
bool AvnAppMenuItem::EvaluateItemEnabled()
{
if(_predicate != nullptr)
{
auto result = _predicate->Evaluate ();
return result;
}
return false;
}
void AvnAppMenuItem::RaiseOnClicked()
{
if(_callback != nullptr)
{
_callback->Run();
}
}
AvnAppMenu::AvnAppMenu()
{
_native = [AvnMenu new];
}
AvnAppMenu::AvnAppMenu(AvnMenu* native)
{
_native = native;
}
AvnMenu* AvnAppMenu::GetNative()
{
return _native;
}
HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native addItem: avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native removeItem:avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::SetTitle (void* utf8String)
{
if (utf8String != nullptr)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
}
return S_OK;
}
HRESULT AvnAppMenu::Clear()
{
[_native removeAllItems];
return S_OK;
}
extern IAvnAppMenu* CreateAppMenu()
{
@autoreleasepool
{
id menuBar = [NSMenu new];
return new AvnAppMenu(menuBar);
}
}
extern IAvnAppMenuItem* CreateAppMenuItem()
{
@autoreleasepool
{
return new AvnAppMenuItem(false);
}
}
extern IAvnAppMenuItem* CreateAppMenuItemSeperator()
{
@autoreleasepool
{
return new AvnAppMenuItem(true);
}
}
static IAvnAppMenu* s_appMenu = nullptr;
static NSMenuItem* s_appMenuItem = nullptr;
extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu)
{
s_appMenu = menu;
if(s_appMenu != nullptr)
{
auto nativeMenu = dynamic_cast<AvnAppMenu*>(s_appMenu);
auto currentMenu = [s_appMenuItem menu];
if (currentMenu != nullptr)
{
[currentMenu removeItem:s_appMenuItem];
}
s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0];
if (currentMenu == nullptr)
{
currentMenu = [s_appMenuItem menu];
}
[[s_appMenuItem menu] removeItem:s_appMenuItem];
[currentMenu insertItem:s_appMenuItem atIndex:0];
if([s_appMenuItem submenu] == nullptr)
{
[s_appMenuItem setSubmenu:[NSMenu new]];
}
auto appMenu = [s_appMenuItem submenu];
[appMenu addItem:[NSMenuItem separatorItem]];
// Services item and menu
auto servicesItem = [[NSMenuItem alloc] init];
servicesItem.title = @"Services";
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
servicesItem.submenu = servicesMenu;
[NSApplication sharedApplication].servicesMenu = servicesMenu;
[appMenu addItem:servicesItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Hide Application
auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
[appMenu addItem:hideItem];
// Hide Others
auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"];
hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
[appMenu addItem:hideAllOthersItem];
// Show All
auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:showAllItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Quit Application
auto quitItem = [[NSMenuItem alloc] init];
quitItem.title = [@"Quit " stringByAppendingString:appName];
quitItem.keyEquivalent = @"q";
quitItem.action = @selector(terminate:);
[appMenu addItem:quitItem];
}
else
{
s_appMenuItem = nullptr;
}
}
extern IAvnAppMenu* GetAppMenu ()
{
return s_appMenu;
}
extern NSMenuItem* GetAppMenuItem ()
{
return s_appMenuItem;
}

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

@ -10,12 +10,6 @@ class PlatformThreadingInterface;
-(Signaler*) init; -(Signaler*) init;
@end @end
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
@implementation ActionCallback @implementation ActionCallback
{ {
ComPtr<IAvnActionCallback> _callback; ComPtr<IAvnActionCallback> _callback;

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

@ -20,6 +20,7 @@ class WindowBaseImpl;
-(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow; -(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents; -(bool) shouldTryToHandleEvents;
-(void) applyMenu:(NSMenu *)menu;
@end @end
struct INSWindowHolder struct INSWindowHolder

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

@ -5,6 +5,7 @@
#include "window.h" #include "window.h"
#include "KeyTransform.h" #include "KeyTransform.h"
#include "cursor.h" #include "cursor.h"
#include "menu.h"
#include <OpenGL/gl.h> #include <OpenGL/gl.h>
class SoftwareDrawingOperation class SoftwareDrawingOperation
@ -54,7 +55,6 @@ public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual ~WindowBaseImpl() virtual ~WindowBaseImpl()
{ {
NSDebugLog(@"~WindowBaseImpl()");
View = NULL; View = NULL;
Window = NULL; Window = NULL;
} }
@ -64,9 +64,11 @@ public:
SoftwareDrawingOperation CurrentSwDrawingOperation; SoftwareDrawingOperation CurrentSwDrawingOperation;
AvnPoint lastPositionSet; AvnPoint lastPositionSet;
NSString* _lastTitle; NSString* _lastTitle;
IAvnAppMenu* _mainMenu;
WindowBaseImpl(IAvnWindowBaseEvents* events) WindowBaseImpl(IAvnWindowBaseEvents* events)
{ {
_mainMenu = nullptr;
BaseEvents = events; BaseEvents = events;
View = [[AvnView alloc] initWithParent:this]; View = [[AvnView alloc] initWithParent:this];
@ -94,6 +96,7 @@ public:
UpdateStyle(); UpdateStyle();
[Window makeKeyAndOrderFront:Window]; [Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible]; [Window setTitleVisibility:NSWindowTitleVisible];
@ -123,6 +126,7 @@ public:
if(Window != nullptr) if(Window != nullptr)
{ {
[Window makeKeyWindow]; [Window makeKeyWindow];
[NSApp activateIgnoringOtherApps:YES];
} }
} }
@ -161,22 +165,6 @@ public:
} }
} }
virtual HRESULT GetMaxClientSize(AvnSize* ret) override
{
@autoreleasepool
{
if(ret == nullptr)
return E_POINTER;
auto size = [NSScreen.screens objectAtIndex:0].frame.size;
ret->Height = size.height;
ret->Width = size.width;
return S_OK;
}
}
virtual HRESULT GetScaling (double* ret) override virtual HRESULT GetScaling (double* ret) override
{ {
@autoreleasepool @autoreleasepool
@ -226,6 +214,31 @@ public:
} }
} }
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override
{
_mainMenu = menu;
auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
auto nsmenu = nativeMenu->GetNative();
[Window applyMenu:nsmenu];
return S_OK;
}
virtual HRESULT ObtainMainMenu(IAvnAppMenu** ret) override
{
if(ret == nullptr)
{
return E_POINTER;
}
*ret = _mainMenu;
return S_OK;
}
virtual bool TryLock() override virtual bool TryLock() override
{ {
@autoreleasepool @autoreleasepool
@ -369,12 +382,9 @@ public:
virtual void UpdateCursor() virtual void UpdateCursor()
{ {
[View resetCursorRects];
if (cursor != nil) if (cursor != nil)
{ {
auto rect = [Window frame]; [cursor set];
[View addCursorRect:rect cursor:cursor];
[cursor set];
} }
} }
@ -416,8 +426,8 @@ private:
INHERIT_INTERFACE_MAP(WindowBaseImpl) INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow) INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
END_INTERFACE_MAP() END_INTERFACE_MAP()
virtual ~WindowImpl(){ virtual ~WindowImpl()
NSDebugLog(@"~WindowImpl"); {
} }
ComPtr<IAvnWindowEvents> WindowEvents; ComPtr<IAvnWindowEvents> WindowEvents;
@ -425,6 +435,7 @@ private:
{ {
WindowEvents = events; WindowEvents = events;
[Window setCanBecomeKeyAndMain]; [Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
} }
virtual HRESULT Show () override virtual HRESULT Show () override
@ -664,10 +675,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc - (void)dealloc
{ {
NSDebugLog(@"AvnView dealloc");
} }
- (void)onClosed - (void)onClosed
{ {
_parent = NULL; _parent = NULL;
@ -1063,11 +1072,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
ComPtr<WindowBaseImpl> _parent; ComPtr<WindowBaseImpl> _parent;
bool _canBecomeKeyAndMain; bool _canBecomeKeyAndMain;
bool _closed; bool _closed;
NSMenu* _menu;
bool _isAppMenuApplied;
} }
- (void)dealloc - (void)dealloc
{ {
NSDebugLog(@"AvnWindow dealloc");
} }
- (void)pollModalSession:(nonnull NSModalSession)session - (void)pollModalSession:(nonnull NSModalSession)session
@ -1087,6 +1097,32 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
} }
} }
-(void) applyMenu:(NSMenu *)menu
{
if(menu == nullptr)
{
menu = [NSMenu new];
}
_menu = menu;
if ([self isKeyWindow])
{
auto appMenu = ::GetAppMenuItem();
if(appMenu != nullptr)
{
[[appMenu menu] removeItem:appMenu];
[_menu insertItem:appMenu atIndex:0];
_isAppMenuApplied = true;
}
[NSApp setMenu:menu];
}
}
-(void) setCanBecomeKeyAndMain -(void) setCanBecomeKeyAndMain
{ {
_canBecomeKeyAndMain = true; _canBecomeKeyAndMain = true;
@ -1179,6 +1215,24 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
if([self activateAppropriateChild: true]) if([self activateAppropriateChild: true])
{ {
if(_menu == nullptr)
{
_menu = [NSMenu new];
}
auto appMenu = ::GetAppMenuItem();
if(appMenu != nullptr)
{
[[appMenu menu] removeItem:appMenu];
[_menu insertItem:appMenu atIndex:0];
_isAppMenuApplied = true;
}
[NSApp setMenu:_menu];
_parent->BaseEvents->Activated(); _parent->BaseEvents->Activated();
[super becomeKeyWindow]; [super becomeKeyWindow];
} }
@ -1223,6 +1277,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
if(_parent) if(_parent)
_parent->BaseEvents->Deactivated(); _parent->BaseEvents->Deactivated();
auto appMenuItem = ::GetAppMenuItem();
if(appMenuItem != nullptr)
{
auto appMenu = ::GetAppMenu();
auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
[[appMenuItem menu] removeItem:appMenuItem];
[nativeAppMenu->GetNative() addItem:appMenuItem];
[NSApp setMenu:nativeAppMenu->GetNative()];
}
else
{
[NSApp setMenu:nullptr];
}
// remove window menu items from appmenu?
[super resignKeyWindow]; [super resignKeyWindow];
} }

3
samples/BindingDemo/MainWindow.xaml

@ -25,6 +25,9 @@
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/> <TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/> <TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/> <TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<!-- Removed due to #2983: reinstate when that's fixed.
<TextBox Watermark="One Way to Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
-->
</StackPanel> </StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200"> <StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/> <TextBlock FontSize="16" Text="Collection Bindings"/>

7
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -1,10 +1,11 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using ReactiveUI; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using ReactiveUI;
namespace BindingDemo.ViewModels namespace BindingDemo.ViewModels
{ {
@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
public ObservableCollection<TestItem> Items { get; } public ObservableCollection<TestItem> Items { get; }
public ObservableCollection<TestItem> SelectedItems { get; } public ObservableCollection<TestItem> SelectedItems { get; }
public ReactiveCommand ShuffleItems { get; } public ReactiveCommand<Unit, Unit> ShuffleItems { get; }
public string BooleanString public string BooleanString
{ {
@ -89,7 +90,7 @@ namespace BindingDemo.ViewModels
} }
public IObservable<DateTimeOffset> CurrentTimeObservable { get; } public IObservable<DateTimeOffset> CurrentTimeObservable { get; }
public ReactiveCommand StringValueCommand { get; } public ReactiveCommand<object, Unit> StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();
public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel(); public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();

2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk> <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion> <TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

2
samples/ControlCatalog.Android/MainActivity.cs

@ -20,7 +20,7 @@ namespace ControlCatalog.Android
{ {
if (Avalonia.Application.Current == null) if (Avalonia.Application.Current == null)
{ {
AppBuilder.Configure(new App()) AppBuilder.Configure<App>()
.UseAndroid() .UseAndroid()
.SetupWithoutStarting(); .SetupWithoutStarting();
Content = new MainView(); Content = new MainView();

6
samples/ControlCatalog.NetCore/Program.cs

@ -55,7 +55,11 @@ namespace ControlCatalog.NetCore
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>() => AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.With(new X11PlatformOptions { EnableMultiTouch = true }) .With(new X11PlatformOptions
{
EnableMultiTouch = true,
UseDBusMenu = true
})
.With(new Win32PlatformOptions .With(new Win32PlatformOptions
{ {
EnableMultitouch = true, EnableMultitouch = true,

18
samples/ControlCatalog/App.xaml

@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.App"> x:Class="ControlCatalog.App">
<Application.Styles> <Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<Style Selector="TextBlock.h1"> <Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontWeight" Value="Medium"/> <Setter Property="FontWeight" Value="Medium"/>
@ -17,4 +17,16 @@
</Style> </Style>
<StyleInclude Source="/SideBar.xaml"/> <StyleInclude Source="/SideBar.xaml"/>
</Application.Styles> </Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenu.Menu>
</Application> </Application>

13
samples/ControlCatalog/App.xaml.cs

@ -1,4 +1,6 @@
using System;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
@ -6,9 +8,20 @@ namespace ControlCatalog
{ {
public class App : Application public class App : Application
{ {
private NativeMenu _recentMenu;
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
Name = "Avalonia";
_recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu;
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()

25
samples/ControlCatalog/DecoratedWindow.xaml

@ -3,6 +3,31 @@
x:Class="ControlCatalog.DecoratedWindow" x:Class="ControlCatalog.DecoratedWindow"
Title="Avalonia Control Gallery" Title="Avalonia Control Gallery"
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Decorated">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Open"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Edit">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Copy"/>
<NativeMenuItem Header="Paste"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5"> <Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" > <DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto"> <Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

3
samples/ControlCatalog/MainView.xaml

@ -34,7 +34,8 @@
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem> <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem> <TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="ItemsRepeater" <TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled"> ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ItemsRepeaterPage/> <pages:ItemsRepeaterPage/>
</TabItem> </TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem> <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>

31
samples/ControlCatalog/MainWindow.xaml

@ -8,7 +8,36 @@
xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views" xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow"> x:Class="ControlCatalog.MainWindow">
<Window.DataTemplates>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="File">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItemSeperator/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItemSeperator/>
<NativeMenuItem Header="Quit Avalonia" Clicked="OnCloseClicked" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Edit">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Copy"/>
<NativeMenuItem Header="Paste"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel"> <DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView /> <v:CustomNotificationView />
</DataTemplate> </DataTemplate>

14
samples/ControlCatalog/MainWindow.xaml.cs

@ -14,6 +14,7 @@ namespace ControlCatalog
public class MainWindow : Window public class MainWindow : Window
{ {
private WindowNotificationManager _notificationArea; private WindowNotificationManager _notificationArea;
private NativeMenu _recentMenu;
public MainWindow() public MainWindow()
{ {
@ -29,8 +30,21 @@ namespace ControlCatalog
}; };
DataContext = new MainWindowViewModel(_notificationArea); DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
} }
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
}
public void OnCloseClicked(object sender, EventArgs args)
{
Close();
}
private void InitializeComponent() private void InitializeComponent()
{ {
// TODO: iOS does not support dynamically loading assemblies // TODO: iOS does not support dynamically loading assemblies

9
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -14,12 +14,19 @@
<ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem> <ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
</ComboBox> </ComboBox>
<Button Command="{Binding AddItem}">Add Item</Button> <Button Command="{Binding AddItem}">Add Item</Button>
<Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
</StackPanel> </StackPanel>
<Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16"> <Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16">
<ScrollViewer Name="scroller" <ScrollViewer Name="scroller"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}"/> <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Height="{Binding Height}" Text="{Binding Text}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer> </ScrollViewer>
</Border> </Border>
</DockPanel> </DockPanel>

2
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -74,7 +74,7 @@ namespace ControlCatalog.Pages
private void RepeaterClick(object sender, PointerPressedEventArgs e) private void RepeaterClick(object sender, PointerPressedEventArgs e)
{ {
var item = (e.Source as TextBlock)?.DataContext as string; var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item; ((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item;
} }
} }

2
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@ -78,7 +78,7 @@ namespace ControlCatalog.Pages
} }
} }
private string GenerateItem() => $"Item {_counter++}"; private string GenerateItem() => $"Item {_counter++.ToString()}";
} }
} }
} }

5
samples/ControlCatalog/Pages/MenuPage.xaml

@ -3,8 +3,11 @@
x:Class="ControlCatalog.Pages.MenuPage"> x:Class="ControlCatalog.Pages.MenuPage">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Menu</TextBlock> <TextBlock Classes="h1">Menu</TextBlock>
<TextBlock Classes="h2">Exported menu fallback</TextBlock>
<TextBlock>(Should be only visible on platforms without desktop-global menu bar)</TextBlock>
<NativeMenuBar/>
<TextBlock Classes="h2">A window menu</TextBlock> <TextBlock Classes="h2">A window menu</TextBlock>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Margin="0,16,0,0" Margin="0,16,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"

7
samples/ControlCatalog/Pages/ScreenPage.cs

@ -57,12 +57,15 @@ namespace ControlCatalog.Pages
text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
text.Text = $"Primary: {screen.Primary}"; text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"; text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
} }
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10)); context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));

39
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -1,4 +1,5 @@
using System.Collections.ObjectModel; using System;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using ReactiveUI; using ReactiveUI;
@ -10,18 +11,44 @@ namespace ControlCatalog.ViewModels
public ItemsRepeaterPageViewModel() public ItemsRepeaterPageViewModel()
{ {
Items = new ObservableCollection<string>( Items = new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => $"Item {i}")); Enumerable.Range(1, 100000).Select(i => new Item
{
Text = $"Item {i.ToString()}",
}));
} }
public ObservableCollection<string> Items { get; } public ObservableCollection<Item> Items { get; }
public string SelectedItem { get; set; } public Item SelectedItem { get; set; }
public void AddItem() public void AddItem()
{ {
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1; var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
Items.Insert(index + 1, $"New Item {newItemIndex++}"); Items.Insert(index + 1, new Item { Text = $"New Item {newItemIndex++}" });
}
public void RandomizeHeights()
{
var random = new Random();
foreach (var i in Items)
{
i.Height = random.Next(240) + 10;
}
}
public class Item : ReactiveObject
{
private double _height = double.NaN;
public string Text { get; set; }
public double Height
{
get => _height;
set => this.RaiseAndSetIfChanged(ref _height, value);
}
} }
} }
} }

5
samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Reactive;
using ReactiveUI; using ReactiveUI;
namespace RenderDemo.ViewModels namespace RenderDemo.ViewModels
@ -26,7 +27,7 @@ namespace RenderDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref drawFps, value); } set { this.RaiseAndSetIfChanged(ref drawFps, value); }
} }
public ReactiveCommand ToggleDrawDirtyRects { get; } public ReactiveCommand<Unit, bool> ToggleDrawDirtyRects { get; }
public ReactiveCommand ToggleDrawFps { get; } public ReactiveCommand<Unit, bool> ToggleDrawFps { get; }
} }
} }

20
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -4,10 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI; using ReactiveUI;
using Avalonia.Layout; using Avalonia.Layout;
@ -18,7 +18,7 @@ namespace VirtualizationDemo.ViewModels
private int _itemCount = 200; private int _itemCount = 200;
private string _newItemString = "New Item"; private string _newItemString = "New Item";
private int _newItemIndex; private int _newItemIndex;
private IReactiveList<ItemViewModel> _items; private AvaloniaList<ItemViewModel> _items;
private string _prefix = "Item"; private string _prefix = "Item";
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto; private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto; private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
@ -54,7 +54,7 @@ namespace VirtualizationDemo.ViewModels
public AvaloniaList<ItemViewModel> SelectedItems { get; } public AvaloniaList<ItemViewModel> SelectedItems { get; }
= new AvaloniaList<ItemViewModel>(); = new AvaloniaList<ItemViewModel>();
public IReactiveList<ItemViewModel> Items public AvaloniaList<ItemViewModel> Items
{ {
get { return _items; } get { return _items; }
private set { this.RaiseAndSetIfChanged(ref _items, value); } private set { this.RaiseAndSetIfChanged(ref _items, value); }
@ -93,11 +93,11 @@ namespace VirtualizationDemo.ViewModels
public IEnumerable<ItemVirtualizationMode> VirtualizationModes => public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>(); Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand AddItemCommand { get; private set; } public ReactiveCommand<Unit, Unit> AddItemCommand { get; private set; }
public ReactiveCommand RecreateCommand { get; private set; } public ReactiveCommand<Unit, Unit> RecreateCommand { get; private set; }
public ReactiveCommand RemoveItemCommand { get; private set; } public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; private set; }
public ReactiveCommand SelectFirstCommand { get; private set; } public ReactiveCommand<Unit, Unit> SelectFirstCommand { get; private set; }
public ReactiveCommand SelectLastCommand { get; private set; } public ReactiveCommand<Unit, Unit> SelectLastCommand { get; private set; }
public void RandomizeSize() public void RandomizeSize()
{ {
@ -123,7 +123,7 @@ namespace VirtualizationDemo.ViewModels
{ {
var items = Enumerable.Range(0, count) var items = Enumerable.Range(0, count)
.Select(x => new ItemViewModel(x)); .Select(x => new ItemViewModel(x));
Items = new ReactiveList<ItemViewModel>(items); Items = new AvaloniaList<ItemViewModel>(items);
} }
else if (count > Items.Count) else if (count > Items.Count)
{ {
@ -162,7 +162,7 @@ namespace VirtualizationDemo.ViewModels
_prefix = _prefix == "Item" ? "Recreated" : "Item"; _prefix = _prefix == "Item" ? "Recreated" : "Item";
var items = Enumerable.Range(0, _itemCount) var items = Enumerable.Range(0, _itemCount)
.Select(x => new ItemViewModel(x, _prefix)); .Select(x => new ItemViewModel(x, _prefix));
Items = new ReactiveList<ItemViewModel>(items); Items = new AvaloniaList<ItemViewModel>(items);
} }
private void SelectItem(int index) private void SelectItem(int index)

6
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -17,7 +17,7 @@ namespace Avalonia
{ {
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new() public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
{ {
builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android"); builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
builder.UseSkia(); builder.UseSkia();
return builder; return builder;
} }
@ -41,7 +41,7 @@ namespace Avalonia.Android
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
} }
public static void Initialize(Avalonia.Application app) public static void Initialize(Type appType)
{ {
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>() .Bind<IClipboard>().ToTransient<ClipboardImpl>()
@ -55,7 +55,7 @@ namespace Avalonia.Android
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>() .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly)); .Bind<IAssetLoader>().ToConstant(new AssetLoader(appType.Assembly));
SkiaPlatform.Initialize(); SkiaPlatform.Initialize();
((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext) ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)

2
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -1,6 +1,6 @@
<Project Sdk="MSBuild.Sdk.Extras"> <Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup> <PropertyGroup>
<TargetFramework>monoandroid80</TargetFramework> <TargetFramework>monoandroid90</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -15,10 +15,10 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface
{ {
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper; private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper; private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
private ViewImpl _view; private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false) public TopLevelImpl(Context context, bool placeOnTop = false)
@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p)); p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this };
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels); _view.Resources.DisplayMetrics.HeightPixels);
} }
@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view; public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] {this}; public IEnumerable<object> Surfaces { get; }
public IRenderer CreateRenderer(IRenderRoot root) public IRenderer CreateRenderer(IRenderRoot root)
{ {

2
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk> <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion> <TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

7
src/Avalonia.Animation/Animatable.cs

@ -55,6 +55,11 @@ namespace Avalonia.Animation
} }
set set
{ {
if (value is null)
return;
if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
SetAndRaise(TransitionsProperty, ref _transitions, value); SetAndRaise(TransitionsProperty, ref _transitions, value);
} }
@ -70,7 +75,7 @@ namespace Avalonia.Animation
if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
// PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations). // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
foreach (var transition in Transitions) foreach (var transition in _transitions)
{ {
if (transition.Property == e.Property) if (transition.Property == e.Property)
{ {

16
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -2,14 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -46,6 +39,7 @@ namespace Avalonia.Animation
public void OnError(Exception error) public void OnError(Exception error)
{ {
_lastInstance?.Dispose(); _lastInstance?.Dispose();
_lastInstance = null;
} }
void IObserver<bool>.OnNext(bool matchVal) void IObserver<bool>.OnNext(bool matchVal)
@ -53,12 +47,18 @@ namespace Avalonia.Animation
if (matchVal != _lastMatch) if (matchVal != _lastMatch)
{ {
_lastInstance?.Dispose(); _lastInstance?.Dispose();
if (matchVal) if (matchVal)
{ {
_lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete);
} }
else
{
_lastInstance = null;
}
_lastMatch = matchVal; _lastMatch = matchVal;
} }
} }
} }
} }

23
src/Avalonia.Base/AvaloniaObject.cs

@ -326,8 +326,6 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
var description = GetDescription(source);
if (property.IsDirect) if (property.IsDirect)
{ {
if (property.IsReadOnly) if (property.IsReadOnly)
@ -335,12 +333,12 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly."); throw new ArgumentException($"The property {property.Name} is readonly.");
} }
Logger.Verbose( Logger.TryGet(LogEventLevel.Verbose)?.Log(
LogArea.Property, LogArea.Property,
this, this,
"Bound {Property} to {Binding} with priority LocalValue", "Bound {Property} to {Binding} with priority LocalValue",
property, property,
description); GetDescription(source));
if (_directBindings == null) if (_directBindings == null)
{ {
@ -351,12 +349,12 @@ namespace Avalonia
} }
else else
{ {
Logger.Verbose( Logger.TryGet(LogEventLevel.Verbose)?.Log(
LogArea.Property, LogArea.Property,
this, this,
"Bound {Property} to {Binding} with priority {Priority}", "Bound {Property} to {Binding} with priority {Priority}",
property, property,
description, GetDescription(source),
priority); priority);
return Values.AddBinding(property, source, priority); return Values.AddBinding(property, source, priority);
@ -406,7 +404,7 @@ namespace Avalonia
{ {
RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
Logger.Verbose( Logger.TryGet(LogEventLevel.Verbose)?.Log(
LogArea.Property, LogArea.Property,
this, this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}", "{Property} changed from {$Old} to {$Value} with priority {Priority}",
@ -458,8 +456,7 @@ namespace Avalonia
/// <param name="e">The binding error.</param> /// <param name="e">The binding error.</param>
protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e) protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
{ {
Logger.Log( Logger.TryGet(LogEventLevel.Warning)?.Log(
LogEventLevel.Warning,
LogArea.Binding, LogArea.Binding,
this, this,
"Error in binding to {Target}.{Property}: {Message}", "Error in binding to {Target}.{Property}: {Message}",
@ -812,7 +809,7 @@ namespace Avalonia
/// <param name="priority">The priority.</param> /// <param name="priority">The priority.</param>
private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority)
{ {
Logger.Verbose( Logger.TryGet(LogEventLevel.Verbose)?.Log(
LogArea.Property, LogArea.Property,
this, this,
"Set {Property} to {$Value} with priority {Priority}", "Set {Property} to {$Value} with priority {Priority}",

9
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -192,9 +192,9 @@ namespace Avalonia
{ {
return observable.Subscribe(e => return observable.Subscribe(e =>
{ {
if (e.Sender is TTarget) if (e.Sender is TTarget target)
{ {
action((TTarget)e.Sender, e); action(target, e);
} }
}); });
} }
@ -207,6 +207,7 @@ namespace Avalonia
/// <param name="observable">The property changed observable.</param> /// <param name="observable">The property changed observable.</param>
/// <param name="handler">Given a TTarget, returns the handler.</param> /// <param name="handler">Given a TTarget, returns the handler.</param>
/// <returns>A disposable that can be used to terminate the subscription.</returns> /// <returns>A disposable that can be used to terminate the subscription.</returns>
[Obsolete("Use overload taking Action<TTarget, AvaloniaPropertyChangedEventArgs>.")]
public static IDisposable AddClassHandler<TTarget>( public static IDisposable AddClassHandler<TTarget>(
this IObservable<AvaloniaPropertyChangedEventArgs> observable, this IObservable<AvaloniaPropertyChangedEventArgs> observable,
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler) Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
@ -238,9 +239,7 @@ namespace Avalonia
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler) Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
where TTarget : class where TTarget : class
{ {
var target = e.Sender as TTarget; if (e.Sender is TTarget target)
if (target != null)
{ {
handler(target)(e); handler(target)(e);
} }

2
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -165,7 +165,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
Logger.Error( Logger.TryGet(LogEventLevel.Error)?.Log(
LogArea.Binding, LogArea.Binding,
this, this,
"Could not convert FallbackValue {FallbackValue} to {Type}", "Could not convert FallbackValue {FallbackValue} to {Type}",

10
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -8,9 +8,13 @@ namespace Avalonia.Data.Core
public abstract class ExpressionNode public abstract class ExpressionNode
{ {
private static readonly object CacheInvalid = new object(); private static readonly object CacheInvalid = new object();
protected static readonly WeakReference<object> UnsetReference = protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue); new WeakReference<object>(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> NullReference =
new WeakReference<object>(null);
private WeakReference<object> _target = UnsetReference; private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber; private Action<object> _subscriber;
private bool _listening; private bool _listening;
@ -98,7 +102,7 @@ namespace Avalonia.Data.Core
if (notification == null) if (notification == null)
{ {
LastValue = new WeakReference<object>(value); LastValue = value != null ? new WeakReference<object>(value) : NullReference;
if (Next != null) if (Next != null)
{ {
@ -111,7 +115,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
LastValue = new WeakReference<object>(notification.Value); LastValue = notification.Value != null ? new WeakReference<object>(notification.Value) : NullReference;
if (Next != null) if (Next != null)
{ {
@ -136,8 +140,8 @@ namespace Avalonia.Data.Core
} }
else if (target != AvaloniaProperty.UnsetValue) else if (target != AvaloniaProperty.UnsetValue)
{ {
StartListeningCore(_target);
_listening = true; _listening = true;
StartListeningCore(_target);
} }
else else
{ {

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

@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore() protected override void SubscribeCore()
{ {
SendCurrentValue();
SubscribeToChanges(); SubscribeToChanges();
SendCurrentValue();
} }
protected override void UnsubscribeCore() protected override void UnsubscribeCore()

5
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -29,6 +29,11 @@ namespace Avalonia.Data.Core
if (!isLastValueAlive) if (!isLastValueAlive)
{ {
if (value == null && LastValue == NullReference)
{
return true;
}
return false; return false;
} }

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

@ -8,6 +8,77 @@ namespace Avalonia.Logging
/// </summary> /// </summary>
public interface ILogSink public interface ILogSink
{ {
/// <summary>
/// Checks if given log level is enabled.
/// </summary>
/// <param name="level">The log event level.</param>
/// <returns><see langword="true"/> if given log level is enabled.</returns>
bool IsEnabled(LogEventLevel level);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
void Log(
LogEventLevel level,
string area,
object source,
string messageTemplate);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
void Log<T0>(
LogEventLevel level,
string area,
object source,
string messageTemplate,
T0 propertyValue0);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
void Log<T0, T1>(
LogEventLevel level,
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1);
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
void Log<T0, T1, T2>(
LogEventLevel level,
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2);
/// <summary> /// <summary>
/// Logs a new event. /// Logs a new event.
/// </summary> /// </summary>

125
src/Avalonia.Base/Logging/Logger.cs

@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Runtime.CompilerServices;
namespace Avalonia.Logging namespace Avalonia.Logging
{ {
/// <summary> /// <summary>
@ -16,124 +14,43 @@ namespace Avalonia.Logging
public static ILogSink Sink { get; set; } public static ILogSink Sink { get; set; }
/// <summary> /// <summary>
/// Logs an event. /// Checks if given log level is enabled.
/// </summary> /// </summary>
/// <param name="level">The log event level.</param> /// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates.</param> /// <returns><see langword="true"/> if given log level is enabled.</returns>
/// <param name="source">The object from which the event originates.</param> public static bool IsEnabled(LogEventLevel level)
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(
LogEventLevel level,
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Sink?.Log(level, area, source, messageTemplate, propertyValues);
}
/// <summary>
/// Logs an event with the <see cref="LogEventLevel.Verbose"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Verbose(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{ {
Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); return Sink?.IsEnabled(level) == true;
} }
/// <summary> /// <summary>
/// Logs an event with the <see cref="LogEventLevel.Debug"/> level. /// Returns parametrized logging sink if given log level is enabled.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param> /// <param name="level">The log event level.</param>
/// <param name="source">The object from which the event originates.</param> /// <returns>Log sink or <see langword="null"/> if log level is not enabled.</returns>
/// <param name="messageTemplate">The message template.</param> public static ParametrizedLogger? TryGet(LogEventLevel level)
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Debug(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{ {
Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); if (!IsEnabled(level))
} {
return null;
}
/// <summary> return new ParametrizedLogger(Sink, level);
/// Logs an event with the <see cref="LogEventLevel.Information"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Information(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues);
} }
/// <summary> /// <summary>
/// Logs an event with the <see cref="LogEventLevel.Warning"/> level. /// Returns parametrized logging sink if given log level is enabled.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param> /// <param name="level">The log event level.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="outLogger">Log sink that is valid only if method returns <see langword="true"/>.</param>
/// <param name="messageTemplate">The message template.</param> /// <returns><see langword="true"/> if logger was obtained successfully.</returns>
/// <param name="propertyValues">The message property values.</param> public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Warning(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{ {
Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); ParametrizedLogger? logger = TryGet(level);
}
/// <summary> outLogger = logger.GetValueOrDefault();
/// Logs an event with the <see cref="LogEventLevel.Error"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Error(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues);
}
/// <summary> return logger.HasValue;
/// Logs an event with the <see cref="LogEventLevel.Fatal"/> level.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValues">The message property values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Fatal(
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues);
} }
} }
} }

174
src/Avalonia.Base/Logging/ParametrizedLogger.cs

@ -0,0 +1,174 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Runtime.CompilerServices;
namespace Avalonia.Logging
{
/// <summary>
/// Logger sink parametrized for given logging level.
/// </summary>
public readonly struct ParametrizedLogger
{
private readonly ILogSink _sink;
private readonly LogEventLevel _level;
public ParametrizedLogger(ILogSink sink, LogEventLevel level)
{
_sink = sink;
_level = level;
}
/// <summary>
/// Checks if this logger can be used.
/// </summary>
public bool IsValid => _sink != null;
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(
string area,
object source,
string messageTemplate)
{
_sink.Log(_level, area, source, messageTemplate);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0>(
string area,
object source,
string messageTemplate,
T0 propertyValue0)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1>(
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2>(
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
/// <param name="propertyValue3">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3>(
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2,
T3 propertyValue3)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
/// <param name="propertyValue3">Message property value.</param>
/// <param name="propertyValue4">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3, T4>(
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2,
T3 propertyValue3,
T4 propertyValue4)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4);
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param>
/// <param name="propertyValue2">Message property value.</param>
/// <param name="propertyValue3">Message property value.</param>
/// <param name="propertyValue4">Message property value.</param>
/// <param name="propertyValue5">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3, T4, T5>(
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2,
T3 propertyValue3,
T4 propertyValue4,
T5 propertyValue5)
{
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5);
}
}
}

23
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop
{ {
public class Utf8Buffer : SafeHandle public class Utf8Buffer : SafeHandle
{ {
private GCHandle _gchandle; private GCHandle _gcHandle;
private byte[] _data; private byte[] _data;
public Utf8Buffer(string s) : base(IntPtr.Zero, true) public Utf8Buffer(string s) : base(IntPtr.Zero, true)
@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop
if (s == null) if (s == null)
return; return;
_data = Encoding.UTF8.GetBytes(s); _data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned); _gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject(); handle = _gcHandle.AddrOfPinnedObject();
} }
public int ByteLen => _data.Length; public int ByteLen => _data.Length;
@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop
{ {
handle = IntPtr.Zero; handle = IntPtr.Zero;
_data = null; _data = null;
_gchandle.Free(); _gcHandle.Free();
} }
return true; return true;
} }
@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop
return null; return null;
int len; int len;
for (len = 0; pstr[len] != 0; len++) ; for (len = 0; pstr[len] != 0; len++) ;
var bytes = new byte[len];
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len); var bytes = ArrayPool<byte>.Shared.Rent(len);
try
{
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len);
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
}
} }
} }
} }

23
src/Avalonia.Base/PriorityBindingEntry.cs

@ -99,37 +99,42 @@ namespace Avalonia
void IObserver<object>.OnNext(object value) void IObserver<object>.OnNext(object value)
{ {
void Signal() void Signal(PriorityBindingEntry instance, object newValue)
{ {
var notification = value as BindingNotification; var notification = newValue as BindingNotification;
if (notification != null) if (notification != null)
{ {
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
{ {
Value = notification.Value; instance.Value = notification.Value;
_owner.Changed(this); instance._owner.Changed(instance);
} }
if (notification.ErrorType != BindingErrorType.None) if (notification.ErrorType != BindingErrorType.None)
{ {
_owner.Error(this, notification); instance._owner.Error(instance, notification);
} }
} }
else else
{ {
Value = value; instance.Value = newValue;
_owner.Changed(this); instance._owner.Changed(instance);
} }
} }
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Signal(); Signal(this, value);
} }
else else
{ {
Dispatcher.UIThread.Post(Signal); // To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Signal(instance, newValue));
} }
} }

62
src/Avalonia.Base/PriorityLevel.cs

@ -3,7 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Diagnostics;
using System.Threading;
using Avalonia.Data; using Avalonia.Data;
namespace Avalonia namespace Avalonia
@ -110,20 +111,7 @@ namespace Avalonia
entry.Start(binding); entry.Start(binding);
return Disposable.Create(() => return new RemoveBindingDisposable(node, Bindings, this);
{
if (!entry.HasCompleted)
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
} }
/// <summary> /// <summary>
@ -191,5 +179,49 @@ namespace Avalonia
ActiveBindingIndex = -1; ActiveBindingIndex = -1;
Owner.LevelValueChanged(this); Owner.LevelValueChanged(this);
} }
private sealed class RemoveBindingDisposable : IDisposable
{
private readonly LinkedList<PriorityBindingEntry> _bindings;
private readonly PriorityLevel _priorityLevel;
private LinkedListNode<PriorityBindingEntry> _binding;
public RemoveBindingDisposable(
LinkedListNode<PriorityBindingEntry> binding,
LinkedList<PriorityBindingEntry> bindings,
PriorityLevel priorityLevel)
{
_binding = binding;
_bindings = bindings;
_priorityLevel = priorityLevel;
}
public void Dispose()
{
LinkedListNode<PriorityBindingEntry> binding = Interlocked.Exchange(ref _binding, null);
if (binding == null)
{
// Some system is trying to remove binding twice.
Debug.Assert(false);
return;
}
PriorityBindingEntry entry = binding.Value;
if (!entry.HasCompleted)
{
_bindings.Remove(binding);
entry.Dispose();
if (entry.Index >= _priorityLevel.ActiveBindingIndex)
{
_priorityLevel.ActivateFirstBinding();
}
}
}
}
} }
} }

2
src/Avalonia.Base/PriorityValue.cs

@ -301,7 +301,7 @@ namespace Avalonia
} }
else else
{ {
Logger.Error( Logger.TryGet(LogEventLevel.Error)?.Log(
LogArea.Binding, LogArea.Binding,
Owner, Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",

2
src/Avalonia.Base/Utilities/SingleOrDictionary.cs

@ -38,7 +38,7 @@ namespace Avalonia.Utilities
{ {
if (dictionary == null) if (dictionary == null)
{ {
if (!_singleValue.HasValue || !_singleValue.Value.Key.Equals(key)) if (!_singleValue.HasValue || !EqualityComparer<TKey>.Default.Equals(_singleValue.Value.Key, key))
{ {
value = default(TValue); value = default(TValue);
return false; return false;

8
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -142,10 +142,12 @@ namespace Avalonia.Utilities
_data = ndata; _data = ndata;
} }
MethodInfo method = s.Method;
var subscriber = (TSubscriber)s.Target; var subscriber = (TSubscriber)s.Target;
if (!s_Callers.TryGetValue(s.Method, out var caller)) if (!s_Callers.TryGetValue(method, out var caller))
s_Callers[s.Method] = caller = s_Callers[method] = caller =
(CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, s.Method); (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method);
_data[_count] = new Descriptor _data[_count] = new Descriptor
{ {
Caller = caller, Caller = caller,

50
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -723,29 +723,29 @@ namespace Avalonia.Controls
PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid"); PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
ItemsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnItemsPropertyChanged); ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnCanUserResizeColumnsChanged); CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
ColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnColumnWidthChanged); ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));
RowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged); RowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged); AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>(x => x.OnFrozenColumnCountChanged); FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnFrozenColumnCountChanged(e));
GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnGridLinesVisibilityChanged); GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnGridLinesVisibilityChanged(e));
HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHeadersVisibilityChanged); HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHeadersVisibilityChanged(e));
HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHorizontalGridLinesBrushChanged); HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHorizontalGridLinesBrushChanged(e));
IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>(x => x.OnIsReadOnlyChanged); IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnIsReadOnlyChanged(e));
MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMaxColumnWidthChanged); MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMaxColumnWidthChanged(e));
MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMinColumnWidthChanged); MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMinColumnWidthChanged(e));
RowHeightProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeightChanged); RowHeightProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeightChanged(e));
RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeaderWidthChanged); RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeaderWidthChanged(e));
SelectionModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectionModeChanged); SelectionModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectionModeChanged(e));
VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnVerticalGridLinesBrushChanged); VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnVerticalGridLinesBrushChanged(e));
SelectedIndexProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedIndexChanged); SelectedIndexProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedIndexChanged(e));
SelectedItemProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedItemChanged); SelectedItemProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedItemChanged(e));
IsEnabledProperty.Changed.AddClassHandler<DataGrid>(x => x.DataGrid_IsEnabledChanged); IsEnabledProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.DataGrid_IsEnabledChanged(e));
AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAreRowGroupHeadersFrozenChanged); AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e));
RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsTemplateChanged); RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsTemplateChanged(e));
RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsVisibilityModeChanged); RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsVisibilityModeChanged(e));
AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAutoGenerateColumnsChanged); AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAutoGenerateColumnsChanged(e));
} }
/// <summary> /// <summary>
@ -3533,7 +3533,7 @@ namespace Avalonia.Controls
if (AreColumnHeadersVisible && if (AreColumnHeadersVisible &&
_vScrollBar != null && _vScrollBar.IsVisible) _vScrollBar != null && _vScrollBar.IsVisible)
{ {
_topRightCornerHeader.IsVisible = true; ; _topRightCornerHeader.IsVisible = true;
} }
else else
{ {
@ -5594,7 +5594,7 @@ namespace Avalonia.Controls
{ {
// This will trigger a call to this method via Cells_SizeChanged for // This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed. // which no processing is needed.
_vScrollBar.IsVisible = true; ; _vScrollBar.IsVisible = true;
if (_vScrollBar.DesiredSize.Width == 0) if (_vScrollBar.DesiredSize.Width == 0)
{ {
// We need to know the width for the rest of layout to work correctly so measure it now // We need to know the width for the rest of layout to work correctly so measure it now

4
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls
static DataGridCell() static DataGridCell()
{ {
PointerPressedEvent.AddClassHandler<DataGridCell>( PointerPressedEvent.AddClassHandler<DataGridCell>(
x => x.DataGridCell_PointerPressed, handledEventsToo: true); (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true);
} }
public DataGridCell() public DataGridCell()
{ } { }
@ -219,4 +219,4 @@ namespace Avalonia.Controls
} }
} }
} }
} }

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

@ -67,7 +67,7 @@ namespace Avalonia.Controls
static DataGridColumnHeader() static DataGridColumnHeader()
{ {
AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>(x => x.OnAreSeparatorsVisibleChanged); AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x,e) => x.OnAreSeparatorsVisibleChanged(e));
} }
/// <summary> /// <summary>
@ -468,7 +468,7 @@ namespace Avalonia.Controls
private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e) private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e)
{ {
if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left) if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left)
{ {
return; return;
} }

8
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -116,10 +116,10 @@ namespace Avalonia.Controls
static DataGridRow() static DataGridRow()
{ {
HeaderProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnHeaderChanged); HeaderProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnHeaderChanged(e));
DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnDetailsTemplateChanged); DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnDetailsTemplateChanged(e));
AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnAreDetailsVisibleChanged); AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnAreDetailsVisibleChanged(e));
PointerPressedEvent.AddClassHandler<DataGridRow>(x => x.DataGridRow_PointerPressed, handledEventsToo: true); PointerPressedEvent.AddClassHandler<DataGridRow>((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -109,7 +109,7 @@ namespace Avalonia.Controls
static DataGridRowGroupHeader() static DataGridRowGroupHeader()
{ {
SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>(x => x.OnSublevelIndentChanged); SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>((x,e) => x.OnSublevelIndentChanged(e));
} }
/// <summary> /// <summary>
@ -446,4 +446,4 @@ namespace Avalonia.Controls
} }
} }
} }

50
src/Avalonia.Controls/AppBuilderBase.cs

@ -18,7 +18,9 @@ namespace Avalonia.Controls
{ {
private static bool s_setupWasAlreadyCalled; private static bool s_setupWasAlreadyCalled;
private Action _optionsInitializers; private Action _optionsInitializers;
private Func<Application> _appFactory;
private IApplicationLifetime _lifetime;
/// <summary> /// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance. /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary> /// </summary>
@ -30,10 +32,15 @@ namespace Avalonia.Controls
public Action RuntimePlatformServicesInitializer { get; private set; } public Action RuntimePlatformServicesInitializer { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="Application"/> instance being initialized. /// Gets the <see cref="Application"/> instance being initialized.
/// </summary> /// </summary>
public Application Instance { get; protected set; } public Application Instance { get; private set; }
/// <summary>
/// Gets the type of the Instance (even if it's not created yet)
/// </summary>
public Type ApplicationType { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem. /// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary> /// </summary>
@ -76,20 +83,11 @@ namespace Avalonia.Controls
public static TAppBuilder Configure<TApp>() public static TAppBuilder Configure<TApp>()
where TApp : Application, new() where TApp : Application, new()
{ {
return Configure(new TApp());
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// </summary>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public static TAppBuilder Configure(Application app)
{
AvaloniaLocator.CurrentMutable.BindToSelf(app);
return new TAppBuilder() return new TAppBuilder()
{ {
Instance = app, ApplicationType = typeof(TApp),
// Needed for CoreRT compatibility
_appFactory = () => new TApp()
}; };
} }
@ -157,6 +155,18 @@ namespace Avalonia.Controls
return Self; return Self;
} }
/// <summary>
/// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it.
/// </summary>
/// <param name="lifetime"></param>
/// <returns></returns>
public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime)
{
_lifetime = lifetime;
Setup();
return Self;
}
/// <summary> /// <summary>
/// Specifies a windowing subsystem to use. /// Specifies a windowing subsystem to use.
/// </summary> /// </summary>
@ -254,11 +264,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private void Setup() private void Setup()
{ {
if (Instance == null)
{
throw new InvalidOperationException("No App instance configured.");
}
if (RuntimePlatformServicesInitializer == null) if (RuntimePlatformServicesInitializer == null)
{ {
throw new InvalidOperationException("No runtime platform services configured."); throw new InvalidOperationException("No runtime platform services configured.");
@ -285,6 +290,9 @@ namespace Avalonia.Controls
WindowingSubsystemInitializer(); WindowingSubsystemInitializer();
RenderingSubsystemInitializer(); RenderingSubsystemInitializer();
AfterPlatformServicesSetupCallback(Self); AfterPlatformServicesSetupCallback(Self);
Instance = _appFactory();
Instance.ApplicationLifetime = _lifetime;
AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
Instance.RegisterServices(); Instance.RegisterServices();
Instance.Initialize(); Instance.Initialize();
AfterSetupCallback(Self); AfterSetupCallback(Self);

19
src/Avalonia.Controls/Application.cs

@ -32,7 +32,7 @@ namespace Avalonia
/// method. /// method.
/// - Tracks the lifetime of the application. /// - Tracks the lifetime of the application.
/// </remarks> /// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{ {
/// <summary> /// <summary>
/// The application-global data templates. /// The application-global data templates.
@ -210,5 +210,22 @@ namespace Avalonia
{ {
ResourcesChanged?.Invoke(this, e); ResourcesChanged?.Invoke(this, e);
} }
private string _name;
/// <summary>
/// Defines Name property
/// </summary>
public static readonly DirectProperty<Application, string> NameProperty =
AvaloniaProperty.RegisterDirect<Application, string>("Name", o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Application name to be used for various platform-specific purposes
/// </summary>
public string Name
{
get => _name;
set => SetAndRaise(NameProperty, ref _name, value);
}
} }
} }

3
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -125,8 +125,7 @@ namespace Avalonia
where T : AppBuilderBase<T>, new() where T : AppBuilderBase<T>, new()
{ {
var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
builder.Instance.ApplicationLifetime = lifetime; builder.SetupWithLifetime(lifetime);
builder.SetupWithoutStarting();
return lifetime.Start(args); return lifetime.Start(args);
} }
} }

18
src/Avalonia.Controls/AutoCompleteBox.cs

@ -805,15 +805,15 @@ namespace Avalonia.Controls
{ {
FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true); FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged); MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnMinimumPopulateDelayChanged(e));
IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged); IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnIsDropDownOpenChanged(e));
SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged); SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSelectedItemPropertyChanged(e));
TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged); TextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnTextPropertyChanged(e));
SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged); SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSearchTextPropertyChanged(e));
FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged); FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnFilterModePropertyChanged(e));
ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged); ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemFilterPropertyChanged(e));
ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged); ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemsPropertyChanged(e));
IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged); IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnControlIsEnabledChanged(e));
} }
/// <summary> /// <summary>

85
src/Avalonia.Controls/Button.cs

@ -64,6 +64,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsDefaultProperty = public static readonly StyledProperty<bool> IsDefaultProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsDefault)); AvaloniaProperty.Register<Button, bool>(nameof(IsDefault));
/// <summary>
/// Defines the <see cref="IsCancelProperty"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsCancelProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsCancel));
/// <summary> /// <summary>
/// Defines the <see cref="Click"/> event. /// Defines the <see cref="Click"/> event.
/// </summary> /// </summary>
@ -84,6 +90,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(Button), true); FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged);
PseudoClass<Button>(IsPressedProperty, ":pressed"); PseudoClass<Button>(IsPressedProperty, ":pressed");
} }
@ -142,6 +149,16 @@ namespace Avalonia.Controls
set { SetValue(IsDefaultProperty, value); } set { SetValue(IsDefaultProperty, value); }
} }
/// <summary>
/// Gets or sets a value indicating whether the button is the Cancel button for the
/// window.
/// </summary>
public bool IsCancel
{
get { return GetValue(IsCancelProperty); }
set { SetValue(IsCancelProperty, value); }
}
public bool IsPressed public bool IsPressed
{ {
get { return GetValue(IsPressedProperty); } get { return GetValue(IsPressedProperty); }
@ -162,6 +179,13 @@ namespace Avalonia.Controls
ListenForDefault(inputElement); ListenForDefault(inputElement);
} }
} }
if (IsCancel)
{
if (e.Root is IInputElement inputElement)
{
ListenForCancel(inputElement);
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -270,7 +294,7 @@ namespace Avalonia.Controls
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
if (IsPressed && e.MouseButton == MouseButton.Left) if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
{ {
IsPressed = false; IsPressed = false;
e.Handled = true; e.Handled = true;
@ -351,6 +375,28 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Called when the <see cref="IsCancel"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void IsCancelChanged(AvaloniaPropertyChangedEventArgs e)
{
var button = e.Sender as Button;
var isCancel = (bool)e.NewValue;
if (button?.VisualRoot is IInputElement inputRoot)
{
if (isCancel)
{
button.ListenForCancel(inputRoot);
}
else
{
button.StopListeningForCancel(inputRoot);
}
}
}
/// <summary> /// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires. /// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary> /// </summary>
@ -373,7 +419,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param> /// <param name="root">The input root.</param>
private void ListenForDefault(IInputElement root) private void ListenForDefault(IInputElement root)
{ {
root.AddHandler(KeyDownEvent, RootKeyDown); root.AddHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Starts listening for the Escape key when the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void ListenForCancel(IInputElement root)
{
root.AddHandler(KeyDownEvent, RootCancelKeyDown);
} }
/// <summary> /// <summary>
@ -382,7 +437,16 @@ namespace Avalonia.Controls
/// <param name="root">The input root.</param> /// <param name="root">The input root.</param>
private void StopListeningForDefault(IInputElement root) private void StopListeningForDefault(IInputElement root)
{ {
root.RemoveHandler(KeyDownEvent, RootKeyDown); root.RemoveHandler(KeyDownEvent, RootDefaultKeyDown);
}
/// <summary>
/// Stops listening for the Escape key when the button is no longer <see cref="IsCancel"/>.
/// </summary>
/// <param name="root">The input root.</param>
private void StopListeningForCancel(IInputElement root)
{
root.RemoveHandler(KeyDownEvent, RootCancelKeyDown);
} }
/// <summary> /// <summary>
@ -390,12 +454,25 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="sender">The event sender.</param> /// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void RootKeyDown(object sender, KeyEventArgs e) private void RootDefaultKeyDown(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter && IsVisible && IsEnabled) if (e.Key == Key.Enter && IsVisible && IsEnabled)
{ {
OnClick(); OnClick();
} }
} }
/// <summary>
/// Called when a key is pressed on the input root and the button <see cref="IsCancel"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void RootCancelKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && IsVisible && IsEnabled)
{
OnClick();
}
}
} }
} }

25
src/Avalonia.Controls/Calendar/Calendar.cs

@ -1565,7 +1565,7 @@ namespace Avalonia.Controls
protected override void OnPointerReleased(PointerReleasedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
if (!HasFocusInternal && e.MouseButton == MouseButton.Left) if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
{ {
FocusManager.Instance.Focus(this); FocusManager.Instance.Focus(this);
} }
@ -2057,18 +2057,17 @@ namespace Avalonia.Controls
static Calendar() static Calendar()
{ {
IsEnabledProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsEnabledChanged); IsEnabledProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsEnabledChanged(e));
FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>(x => x.OnFirstDayOfWeekChanged); FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnFirstDayOfWeekChanged(e));
IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsTodayHighlightedChanged); IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsTodayHighlightedChanged(e));
DisplayModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayModePropertyChanged); DisplayModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayModePropertyChanged(e));
SelectionModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectionModeChanged); SelectionModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectionModeChanged(e));
SelectedDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectedDateChanged); SelectedDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectedDateChanged(e));
DisplayDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateChanged); DisplayDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateStartChanged); DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateEndChanged); DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateEndChanged(e));
KeyDownEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyDown); KeyDownEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyDown(e));
KeyUpEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyUp); KeyUpEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyUp(e));
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Calendar/CalendarButton.cs

@ -173,7 +173,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerReleased(PointerReleasedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left) if (e.InitialPressMouseButton == MouseButton.Left)
CalendarLeftMouseButtonUp?.Invoke(this, e); CalendarLeftMouseButtonUp?.Invoke(this, e);
} }
} }

2
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -231,7 +231,7 @@ namespace Avalonia.Controls.Primitives
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left) if (e.InitialPressMouseButton == MouseButton.Left)
CalendarDayButtonMouseUp?.Invoke(this, e); CalendarDayButtonMouseUp?.Invoke(this, e);
} }
} }

22
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -393,14 +393,14 @@ namespace Avalonia.Controls
{ {
FocusableProperty.OverrideDefaultValue<DatePicker>(true); FocusableProperty.OverrideDefaultValue<DatePicker>(true);
DisplayDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateChanged); DisplayDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateStartChanged); DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateEndChanged); DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>(x => x.OnIsDropDownOpenChanged); IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
SelectedDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateChanged); SelectedDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateChanged(e));
SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateFormatChanged); SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>(x => x.OnCustomDateFormatStringChanged); CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e));
TextProperty.Changed.AddClassHandler<DatePicker>(x => x.OnTextChanged); TextProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnTextChanged(e));
} }
/// <summary> /// <summary>
/// Initializes a new instance of the /// Initializes a new instance of the
@ -1042,7 +1042,8 @@ namespace Avalonia.Controls
} }
} }
DateTime? d = SetTextBoxValue(s); DateTime? d = SetTextBoxValue(s);
if (!SelectedDate.Equals(d))
if (SelectedDate != d)
{ {
SelectedDate = d; SelectedDate = d;
} }
@ -1058,7 +1059,8 @@ namespace Avalonia.Controls
else else
{ {
DateTime? d = SetTextBoxValue(_defaultText); DateTime? d = SetTextBoxValue(_defaultText);
if (!SelectedDate.Equals(d))
if (SelectedDate != d)
{ {
SelectedDate = d; SelectedDate = d;
} }

12
src/Avalonia.Controls/Carousel.cs

@ -84,17 +84,5 @@ namespace Avalonia.Controls
--SelectedIndex; --SelectedIndex;
} }
} }
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
// Ignore key presses.
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
// Ignore pointer presses.
}
} }
} }

4
src/Avalonia.Controls/ComboBox.cs

@ -65,8 +65,8 @@ namespace Avalonia.Controls
{ {
ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel); ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<ComboBox>(true); FocusableProperty.OverrideDefaultValue<ComboBox>(true);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>(x => x.SelectedItemChanged); SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler<ComboBox>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/ContentControl.cs

@ -43,7 +43,7 @@ namespace Avalonia.Controls
static ContentControl() static ContentControl()
{ {
ContentProperty.Changed.AddClassHandler<ContentControl>(x => x.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentControl>((x, e) => x.ContentChanged(e));
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/ContextMenu.cs

@ -192,7 +192,7 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
} }
if (e.MouseButton == MouseButton.Right) if (e.InitialPressMouseButton == MouseButton.Right)
{ {
if (contextMenu.CancelOpening()) if (contextMenu.CancelOpening())
return; return;

2
src/Avalonia.Controls/DataValidationErrors.cs

@ -56,7 +56,7 @@ namespace Avalonia.Controls
{ {
ErrorsProperty.Changed.Subscribe(ErrorsChanged); ErrorsProperty.Changed.Subscribe(ErrorsChanged);
HasErrorsProperty.Changed.Subscribe(HasErrorsChanged); HasErrorsProperty.Changed.Subscribe(HasErrorsChanged);
TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>(x => x.OnTemplatedParentChange); TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>((x, e) => x.OnTemplatedParentChange(e));
} }
private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e) private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e)

2
src/Avalonia.Controls/Decorator.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls
static Decorator() static Decorator()
{ {
AffectsMeasure<Decorator>(ChildProperty, PaddingProperty); AffectsMeasure<Decorator>(ChildProperty, PaddingProperty);
ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged); ChildProperty.Changed.AddClassHandler<Decorator>((x, e) => x.ChildChanged(e));
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/DropDown.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls
{ {
public DropDown() public DropDown()
{ {
Logger.Warning(LogArea.Control, this, "DropDown is deprecated: Use ComboBox"); Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
} }
Type IStyleable.StyleKey => typeof(ComboBox); Type IStyleable.StyleKey => typeof(ComboBox);
@ -20,7 +20,7 @@ namespace Avalonia.Controls
{ {
public DropDownItem() public DropDownItem()
{ {
Logger.Warning(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem"); Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
} }
Type IStyleable.StyleKey => typeof(ComboBoxItem); Type IStyleable.StyleKey => typeof(ComboBoxItem);

2
src/Avalonia.Controls/Expander.cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls
PseudoClass<Expander>(IsExpandedProperty, ":expanded"); PseudoClass<Expander>(IsExpandedProperty, ":expanded");
IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged); IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
} }
public IPageTransition ContentTransition public IPageTransition ContentTransition

4
src/Avalonia.Controls/ItemsControl.cs

@ -64,8 +64,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static ItemsControl() static ItemsControl()
{ {
ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged); ItemsProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemsChanged(e));
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged); ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemTemplateChanged(e));
} }
/// <summary> /// <summary>

8
src/Avalonia.Controls/LayoutTransformControl.cs

@ -28,11 +28,13 @@ namespace Avalonia.Controls
ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true); ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true);
LayoutTransformProperty.Changed LayoutTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged); .AddClassHandler<LayoutTransformControl>((x, e) => x.OnLayoutTransformChanged(e));
ChildProperty.Changed ChildProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnChildChanged); .AddClassHandler<LayoutTransformControl>((x, e) => x.OnChildChanged(e));
UseRenderTransformProperty.Changed.AddClassHandler<LayoutTransformControl>(x => x.OnUseRenderTransformPropertyChanged);
UseRenderTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnUseRenderTransformPropertyChanged(e));
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/MenuBase.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static MenuBase() static MenuBase()
{ {
MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>(x => x.OnSubmenuOpened); MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>((x, e) => x.OnSubmenuOpened(e));
} }
/// <summary> /// <summary>

16
src/Avalonia.Controls/MenuItem.cs

@ -102,13 +102,13 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty); SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true); FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged); HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged); IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged); IsSelectedProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IsSelectedChanged(e));
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel); ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick); ClickEvent.AddClassHandler<MenuItem>((x, e) => x.OnClick(e));
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened); SubmenuOpenedEvent.AddClassHandler<MenuItem>((x, e) => x.OnSubmenuOpened(e));
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged); IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.SubMenuOpenChanged(e));
} }
public MenuItem() public MenuItem()
@ -337,7 +337,7 @@ namespace Avalonia.Controls
{ {
base.OnPointerEnter(e); base.OnPointerEnter(e);
var point = e.GetPointerPoint(null); var point = e.GetCurrentPoint(null);
RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position, RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
e.Timestamp, point.Properties, e.KeyModifiers)); e.Timestamp, point.Properties, e.KeyModifiers));
} }
@ -347,7 +347,7 @@ namespace Avalonia.Controls
{ {
base.OnPointerLeave(e); base.OnPointerLeave(e);
var point = e.GetPointerPoint(null); var point = e.GetCurrentPoint(null);
RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position, RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
e.Timestamp, point.Properties, e.KeyModifiers)); e.Timestamp, point.Properties, e.KeyModifiers));
} }

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

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform;
using Avalonia.Data;
namespace Avalonia.Controls
{
public partial class NativeMenu
{
public static readonly AttachedProperty<bool> IsNativeMenuExportedProperty =
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, bool>("IsNativeMenuExported");
public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
private static readonly AttachedProperty<NativeMenuInfo> s_nativeMenuInfoProperty =
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo>("___NativeMenuInfo");
class NativeMenuInfo
{
public bool ChangingIsExported { get; set; }
public ITopLevelNativeMenuExporter Exporter { get; }
public NativeMenuInfo(TopLevel target)
{
Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
if (Exporter != null)
{
Exporter.OnIsNativeMenuExportedChanged += delegate
{
SetIsNativeMenuExported(target, Exporter.IsNativeMenuExported);
};
}
}
}
static NativeMenuInfo GetInfo(TopLevel target)
{
var rv = target.GetValue(s_nativeMenuInfoProperty);
if (rv == null)
{
target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target));
SetIsNativeMenuExported(target, rv.Exporter?.IsNativeMenuExported ?? false);
}
return rv;
}
static void SetIsNativeMenuExported(TopLevel tl, bool value)
{
GetInfo(tl).ChangingIsExported = true;
tl.SetValue(IsNativeMenuExportedProperty, value);
}
public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu", validate:
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
});
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu()
{
// This is needed because of the lack of attached direct properties
IsNativeMenuExportedProperty.Changed.Subscribe(args =>
{
var info = GetInfo((TopLevel)args.Sender);
if (!info.ChangingIsExported)
throw new InvalidOperationException("IsNativeMenuExported property is read-only");
info.ChangingIsExported = false;
});
MenuProperty.Changed.Subscribe(args =>
{
if (args.Sender is TopLevel tl)
{
GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue);
}
});
}
}
}

60
src/Avalonia.Controls/NativeMenu.cs

@ -0,0 +1,60 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
public partial class NativeMenu : AvaloniaObject, IEnumerable<NativeMenuItemBase>
{
private readonly AvaloniaList<NativeMenuItemBase> _items =
new AvaloniaList<NativeMenuItemBase> { ResetBehavior = ResetBehavior.Remove };
private NativeMenuItem _parent;
[Content]
public IList<NativeMenuItemBase> Items => _items;
public NativeMenu()
{
_items.Validate = Validator;
_items.CollectionChanged += ItemsChanged;
}
private void Validator(NativeMenuItemBase obj)
{
if (obj.Parent != null)
throw new InvalidOperationException("NativeMenuItem already has a parent");
}
private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.OldItems!=null)
foreach (NativeMenuItemBase i in e.OldItems)
i.Parent = null;
if(e.NewItems!=null)
foreach (NativeMenuItemBase i in e.NewItems)
i.Parent = this;
}
public static readonly DirectProperty<NativeMenu, NativeMenuItem> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenuItem Parent
{
get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value);
}
public void Add(NativeMenuItemBase item) => _items.Add(item);
public IEnumerator<NativeMenuItemBase> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

36
src/Avalonia.Controls/NativeMenuBar.cs

@ -0,0 +1,36 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Styling;
namespace Avalonia.Controls
{
public class NativeMenuBar : TemplatedControl
{
public static readonly AttachedProperty<bool> EnableMenuItemClickForwardingProperty =
AvaloniaProperty.RegisterAttached<NativeMenuBar, MenuItem, Boolean>(
"EnableMenuItemClickForwarding");
static NativeMenuBar()
{
EnableMenuItemClickForwardingProperty.Changed.Subscribe(args =>
{
var item = (MenuItem)args.Sender;
if (args.NewValue.Equals(true))
item.Click += OnMenuItemClick;
else
item.Click -= OnMenuItemClick;
});
}
public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable)
{
menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable);
}
private static void OnMenuItemClick(object sender, RoutedEventArgs e)
{
(((MenuItem)sender).DataContext as NativeMenuItem)?.RaiseClick();
}
}
}

162
src/Avalonia.Controls/NativeMenuItem.cs

@ -0,0 +1,162 @@
using System;
using System.Windows.Input;
using Avalonia.Input;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class NativeMenuItem : NativeMenuItemBase
{
private string _header;
private KeyGesture _gesture;
private bool _enabled = true;
private NativeMenu _menu;
static NativeMenuItem()
{
MenuProperty.Changed.Subscribe(args =>
{
var item = (NativeMenuItem)args.Sender;
var value = (NativeMenu)args.NewValue;
if (value.Parent != null && value.Parent != item)
throw new InvalidOperationException("NativeMenu already has a parent");
value.Parent = item;
});
}
class CanExecuteChangedSubscriber : IWeakSubscriber<EventArgs>
{
private readonly NativeMenuItem _parent;
public CanExecuteChangedSubscriber(NativeMenuItem parent)
{
_parent = parent;
}
public void OnEvent(object sender, EventArgs e)
{
_parent.CanExecuteChanged();
}
}
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
public NativeMenuItem()
{
_canExecuteChangedSubscriber = new CanExecuteChangedSubscriber(this);
}
public NativeMenuItem(string header) : this()
{
Header = header;
}
public static readonly DirectProperty<NativeMenuItem, NativeMenu> MenuProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>(nameof(Menu), o => o._menu,
(o, v) =>
{
if (v.Parent != null && v.Parent != o)
throw new InvalidOperationException("NativeMenu already has a parent");
o._menu = v;
});
public NativeMenu Menu
{
get => _menu;
set
{
if (value.Parent != null && value.Parent != this)
throw new InvalidOperationException("NativeMenu already has a parent");
SetAndRaise(MenuProperty, ref _menu, value);
}
}
public static readonly DirectProperty<NativeMenuItem, string> HeaderProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, string>(nameof(Header), o => o._header, (o, v) => o._header = v);
public string Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly DirectProperty<NativeMenuItem, KeyGesture> GestureProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture>(nameof(Gesture), o => o._gesture, (o, v) => o._gesture = v);
public KeyGesture Gesture
{
get => GetValue(GestureProperty);
set => SetValue(GestureProperty, value);
}
private ICommand _command;
public static readonly DirectProperty<NativeMenuItem, ICommand> CommandProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, ICommand>(nameof(Command),
o => o._command, (o, v) =>
{
if (o._command != null)
WeakSubscriptionManager.Unsubscribe(o._command,
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
o._command = v;
if (o._command != null)
WeakSubscriptionManager.Subscribe(o._command,
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
o.CanExecuteChanged();
});
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
public static readonly DirectProperty<NativeMenuItem, bool> EnabledProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(Enabled), o => o._enabled,
(o, v) => o._enabled = v, true);
public bool Enabled
{
get => GetValue(EnabledProperty);
set => SetValue(EnabledProperty, value);
}
void CanExecuteChanged()
{
Enabled = _command?.CanExecute(null) ?? true;
}
public bool HasClickHandlers => Clicked != null;
public ICommand Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="NativeMenuItem"/>.
/// </summary>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public event EventHandler Clicked;
public void RaiseClick()
{
Clicked?.Invoke(this, new EventArgs());
if (Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
}
}
}
}

23
src/Avalonia.Controls/NativeMenuItemBase.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Controls
{
public class NativeMenuItemBase : AvaloniaObject
{
private NativeMenu _parent;
internal NativeMenuItemBase()
{
}
public static readonly DirectProperty<NativeMenuItem, NativeMenu> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenu Parent
{
get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value);
}
}
}

10
src/Avalonia.Controls/NativeMenuItemSeperator.cs

@ -0,0 +1,10 @@
using System;
namespace Avalonia.Controls
{
public class NativeMenuItemSeperator : NativeMenuItemBase
{
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
public string Header => "-";
}
}

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

@ -356,7 +356,7 @@ namespace Avalonia.Controls.Platform
{ {
var item = GetMenuItem(e.Source as IControl); var item = GetMenuItem(e.Source as IControl);
if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == false) if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false)
{ {
Click(item); Click(item);
e.Handled = true; e.Handled = true;

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

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
public interface ITopLevelNativeMenuExporter
{
bool IsNativeMenuExported { get; }
event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu);
}
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
{
ITopLevelNativeMenuExporter NativeMenuExporter { get; }
}
}

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

@ -2,14 +2,17 @@
{ {
public class Screen public class Screen
{ {
public double PixelDensity { get; }
public PixelRect Bounds { get; } public PixelRect Bounds { get; }
public PixelRect WorkingArea { get; } public PixelRect WorkingArea { get; }
public bool Primary { get; } public bool Primary { get; }
public Screen(PixelRect bounds, PixelRect workingArea, bool primary) public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary)
{ {
this.PixelDensity = pixelDensity;
this.Bounds = bounds; this.Bounds = bounds;
this.WorkingArea = workingArea; this.WorkingArea = workingArea;
this.Primary = primary; this.Primary = primary;

4
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -46,8 +46,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
static CarouselPresenter() static CarouselPresenter()
{ {
IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.IsVirtualizedChanged); IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.IsVirtualizedChanged(e));
SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.SelectedIndexChanged); SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.SelectedIndexChanged(e));
} }
/// <summary> /// <summary>

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

@ -94,9 +94,9 @@ namespace Avalonia.Controls.Presenters
{ {
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty); AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged); TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -35,7 +35,7 @@ namespace Avalonia.Controls.Presenters
KeyboardNavigationMode.Once); KeyboardNavigationMode.Once);
VirtualizationModeProperty.Changed VirtualizationModeProperty.Changed
.AddClassHandler<ItemsPresenter>(x => x.VirtualizationModeChanged); .AddClassHandler<ItemsPresenter>((x, e) => x.VirtualizationModeChanged(e));
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -45,7 +45,7 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
static ItemsPresenterBase() static ItemsPresenterBase()
{ {
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>(x => x.TemplatedParentChanged); TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>((x,e) => x.TemplatedParentChanged(e));
} }
/// <summary> /// <summary>

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

@ -73,7 +73,7 @@ namespace Avalonia.Controls.Presenters
static ScrollContentPresenter() static ScrollContentPresenter()
{ {
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged); ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>((x, e) => x.ChildChanged(e));
AffectsArrange<ScrollContentPresenter>(OffsetProperty); AffectsArrange<ScrollContentPresenter>(OffsetProperty);
} }
@ -246,7 +246,7 @@ namespace Avalonia.Controls.Presenters
if (isLogical) if (isLogical)
_activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta); _activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta);
delta += e.Delta; delta += e.Delta;
if (Extent.Height > Viewport.Height) if (Extent.Height > Viewport.Height)
{ {
double dy; double dy;
@ -293,7 +293,7 @@ namespace Avalonia.Controls.Presenters
} }
} }
private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
=> _activeLogicalGestureScrolls?.Remove(e.Id); => _activeLogicalGestureScrolls?.Remove(e.Id);
/// <inheritdoc/> /// <inheritdoc/>

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

@ -244,7 +244,7 @@ namespace Avalonia.Controls.Presenters
var rect = FormattedText.HitTestTextPosition(caretIndex); var rect = FormattedText.HitTestTextPosition(caretIndex);
this.BringIntoView(rect); this.BringIntoView(rect);
}, },
DispatcherPriority.Normal); DispatcherPriority.Render);
} }
} }
} }

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

@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
static HeaderedContentControl() static HeaderedContentControl()
{ {
ContentProperty.Changed.AddClassHandler<HeaderedContentControl>(x => x.HeaderChanged); ContentProperty.Changed.AddClassHandler<HeaderedContentControl>((x, e) => x.HeaderChanged(e));
} }
/// <summary> /// <summary>

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

Loading…
Cancel
Save