Browse Source

Merge remote-tracking branch 'upstream/master'

pull/8313/head
Benedikt Stebner 4 years ago
parent
commit
f6223f0c7e
  1. 77
      Avalonia.sln
  2. 6
      build/SkiaSharp.props
  3. 2
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  4. 4
      native/Avalonia.Native/src/OSX/AvnView.mm
  5. 96
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  6. 2
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  7. 6
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  8. 9
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  9. 67
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  10. 9
      native/Avalonia.Native/src/OSX/WindowImpl.h
  11. 115
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  12. 1
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  13. 11
      native/Avalonia.Native/src/OSX/app.mm
  14. 15
      readme.md
  15. 35
      samples/ControlCatalog.Android/EmbedSample.Android.cs
  16. 6
      samples/ControlCatalog.Android/MainActivity.cs
  17. 14
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  18. 35
      samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs
  19. 58
      samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs
  20. 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md
  21. 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4
  22. 29
      samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs
  23. 38
      samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs
  24. 45
      samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs
  25. 73
      samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
  26. 8
      samples/ControlCatalog.NetCore/Program.cs
  27. 28
      samples/ControlCatalog.NetCore/app.manifest
  28. 4
      samples/ControlCatalog.Web/App.razor.cs
  29. 34
      samples/ControlCatalog.Web/EmbedSample.Browser.cs
  30. 70
      samples/ControlCatalog.Web/Shared/MainLayout.razor.css
  31. 44
      samples/ControlCatalog.Web/wwwroot/css/app.css
  32. 11
      samples/ControlCatalog.Web/wwwroot/js/app.js
  33. 9
      samples/ControlCatalog.iOS/AppDelegate.cs
  34. 38
      samples/ControlCatalog.iOS/EmbedSample.iOS.cs
  35. 15
      samples/ControlCatalog/ControlCatalog.csproj
  36. 10
      samples/ControlCatalog/Converter/MathSubtractConverter.cs
  37. 10
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  38. 7
      samples/ControlCatalog/MainView.xaml
  39. 16
      samples/ControlCatalog/MainView.xaml.cs
  40. 8
      samples/ControlCatalog/MainWindow.xaml.cs
  41. 4
      samples/ControlCatalog/Models/Countries.cs
  42. 4
      samples/ControlCatalog/Models/GDPValueConverter.cs
  43. 16
      samples/ControlCatalog/Models/Person.cs
  44. 33
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  45. 2
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  46. 4
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  47. 12
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
  48. 4
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  49. 11
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  50. 22
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  51. 6
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  52. 8
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  53. 4
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  54. 2
      samples/ControlCatalog/Pages/DialogsPage.xaml
  55. 62
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  56. 6
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  57. 8
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  58. 13
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  59. 26
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  60. 2
      samples/ControlCatalog/Pages/LabelsPage.axaml.cs
  61. 2
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  62. 68
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml
  63. 86
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  64. 2
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  65. 10
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  66. 2
      samples/ControlCatalog/Pages/ScreenPage.cs
  67. 12
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  68. 2
      samples/ControlCatalog/Pages/TabStripPage.xaml.cs
  69. 2
      samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
  70. 2
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  71. 2
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  72. 19
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  73. 13
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  74. 8
      samples/ControlCatalog/ViewModels/MenuItemViewModel.cs
  75. 2
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  76. 4
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  77. 16
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  78. 4
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  79. 8
      samples/interop/NativeEmbedSample/App.xaml
  80. 22
      samples/interop/NativeEmbedSample/App.xaml.cs
  81. 121
      samples/interop/NativeEmbedSample/EmbedSample.cs
  82. 58
      samples/interop/NativeEmbedSample/GtkHelper.cs
  83. 39
      samples/interop/NativeEmbedSample/MacHelper.cs
  84. 52
      samples/interop/NativeEmbedSample/MainWindow.xaml
  85. 36
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  86. 31
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  87. 17
      samples/interop/NativeEmbedSample/Program.cs
  88. 74
      samples/interop/NativeEmbedSample/WinApi.cs
  89. 32
      src/Android/Avalonia.Android/AndroidViewControlHandle.cs
  90. 9
      src/Android/Avalonia.Android/AvaloniaView.cs
  91. 139
      src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
  92. 10
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  93. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  94. 9
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  95. 76
      src/Avalonia.Base/Matrix.cs
  96. 3
      src/Avalonia.Base/Media/GeometryDrawing.cs
  97. 11
      src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
  98. 29
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  99. 376
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  100. 39
      src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

77
Avalonia.sln

@ -97,6 +97,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\DevAnalyzers.props = build\DevAnalyzers.props build\DevAnalyzers.props = build\DevAnalyzers.props
build\EmbedXaml.props = build\EmbedXaml.props build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\ImageSharp.props = build\ImageSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
@ -117,7 +118,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\System.Memory.props = build\System.Memory.props build\System.Memory.props = build\System.Memory.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props build\XUnit.props = build\XUnit.props
build\ImageSharp.props = build\ImageSharp.props
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@ -179,8 +179,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
@ -1413,6 +1411,30 @@ Global
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -1509,30 +1531,6 @@ Global
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -1965,30 +1963,6 @@ Global
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -2035,7 +2009,6 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<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="SkiaSharp" Version="2.88.0" /> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -3,8 +3,6 @@
// Copyright (c) 2022 Avalonia. All rights reserved. // Copyright (c) 2022 Avalonia. All rights reserved.
// //
#pragma once
#define IS_NSPANEL #define IS_NSPANEL
#include "AvnWindow.mm" #include "AvnWindow.mm"

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

@ -222,7 +222,7 @@
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{ {
bool triggerInputWhenDisabled = type != Move; bool triggerInputWhenDisabled = type != Move && type != LeaveWindow;
if([self ignoreUserInput: triggerInputWhenDisabled]) if([self ignoreUserInput: triggerInputWhenDisabled])
{ {
@ -709,4 +709,4 @@
return [[self accessibilityChild] accessibilityFocusedUIElement]; return [[self accessibilityChild] accessibilityFocusedUIElement];
} }
@end @end

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

@ -33,6 +33,7 @@
bool _isEnabled; bool _isEnabled;
bool _canBecomeKeyWindow; bool _canBecomeKeyWindow;
bool _isExtended; bool _isExtended;
bool _isTransitioningToFullScreen;
AvnMenu* _menu; AvnMenu* _menu;
} }
@ -68,7 +69,7 @@
} }
} }
- (void)performClose:(id)sender - (void)performClose:(id _Nullable )sender
{ {
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{ {
@ -147,7 +148,7 @@
} }
} }
-(void) applyMenu:(AvnMenu *)menu -(void) applyMenu:(AvnMenu *_Nullable)menu
{ {
if(menu == nullptr) if(menu == nullptr)
{ {
@ -157,7 +158,7 @@
_menu = menu; _menu = menu;
} }
-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; -(CLASS_NAME*_Nonnull) initWithParent: (WindowBaseImpl*_Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
{ {
// https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/ // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
// create nswindow with specific contentRect, otherwise we wont be able to resize the window // create nswindow with specific contentRect, otherwise we wont be able to resize the window
@ -175,15 +176,17 @@
[self setBackgroundColor: [NSColor clearColor]]; [self setBackgroundColor: [NSColor clearColor]];
_isExtended = false; _isExtended = false;
_isTransitioningToFullScreen = false;
#ifdef IS_NSPANEL if(self.isDialog)
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary]; {
#endif [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
}
return self; return self;
} }
- (BOOL)windowShouldClose:(NSWindow *)sender - (BOOL)windowShouldClose:(NSWindow *_Nonnull)sender
{ {
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw()); auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
@ -195,21 +198,28 @@
return true; return true;
} }
- (void)windowDidChangeBackingProperties:(NSNotification *)notification - (void)windowDidChangeBackingProperties:(NSNotification *_Nonnull)notification
{ {
[self backingScaleFactor]; [self backingScaleFactor];
} }
- (void)windowWillClose:(NSNotification *)notification - (void)windowWillClose:(NSNotification *_Nonnull)notification
{ {
_closed = true; _closed = true;
if(_parent) if(_parent)
{ {
ComPtr<WindowBaseImpl> parent = _parent; ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL; _parent = NULL;
[self restoreParentWindow];
auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
if(window != nullptr)
{
window->SetParent(nullptr);
}
parent->BaseEvents->Closed(); parent->BaseEvents->Closed();
[parent->View onClosed]; [parent->View onClosed];
} }
@ -220,17 +230,11 @@
if(_canBecomeKeyWindow) if(_canBecomeKeyWindow)
{ {
// If the window has a child window being shown as a dialog then don't allow it to become the key window. // If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows]) auto parent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(parent != nullptr)
{ {
if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)]) return parent->CanBecomeKeyWindow();
{
continue;
}
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
if(ch.isDialog)
return false;
} }
return true; return true;
@ -259,6 +263,10 @@
-(void) setEnabled:(bool)enable -(void) setEnabled:(bool)enable
{ {
_isEnabled = enable; _isEnabled = enable;
[[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
[[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
} }
-(void)becomeKeyWindow -(void)becomeKeyWindow
@ -273,17 +281,20 @@
[super becomeKeyWindow]; [super becomeKeyWindow];
} }
-(void) restoreParentWindow; - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{ {
auto parent = [self parentWindow]; _parent->BringToFront();
if(parent != nil) dispatch_async(dispatch_get_main_queue(), ^{
{ @try {
[parent removeChildWindow:self]; [self invalidateShadow];
} }
@finally{
}
});
} }
- (void)windowDidMiniaturize:(NSNotification *)notification - (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -293,7 +304,7 @@
} }
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification - (void)windowDidDeminiaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -303,7 +314,7 @@
} }
} }
- (void)windowDidResize:(NSNotification *)notification - (void)windowDidResize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -313,7 +324,7 @@
} }
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification - (void)windowWillExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -323,7 +334,7 @@
} }
} }
- (void)windowDidExitFullScreen:(NSNotification *)notification - (void)windowDidExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -346,8 +357,9 @@
} }
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification - (void)windowWillEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
_isTransitioningToFullScreen = true;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr) if(parent != nullptr)
@ -356,8 +368,9 @@
} }
} }
- (void)windowDidEnterFullScreen:(NSNotification *)notification - (void)windowDidEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
_isTransitioningToFullScreen = false;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr) if(parent != nullptr)
@ -367,7 +380,7 @@
} }
} }
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{ {
return true; return true;
} }
@ -378,11 +391,13 @@
_parent->BaseEvents->Deactivated(); _parent->BaseEvents->Deactivated();
[self showAppMenuOnly]; [self showAppMenuOnly];
[self invalidateShadow];
[super resignKeyWindow]; [super resignKeyWindow];
} }
- (void)windowDidMove:(NSNotification *)notification - (void)windowDidMove:(NSNotification *_Nonnull)notification
{ {
AvnPoint position; AvnPoint position;
@ -414,7 +429,7 @@
return pt; return pt;
} }
- (void)sendEvent:(NSEvent *)event - (void)sendEvent:(NSEvent *_Nonnull)event
{ {
[super sendEvent:event]; [super sendEvent:event];
@ -437,8 +452,13 @@
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta); _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
} }
if(!_isTransitioningToFullScreen)
{
_parent->BringToFront();
}
} }
break; break;
case NSEventTypeMouseEntered: case NSEventTypeMouseEntered:
{ {

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

@ -11,7 +11,7 @@
struct INSWindowHolder struct INSWindowHolder
{ {
virtual NSWindow* _Nonnull GetNSWindow () = 0; virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0; virtual AvnView* _Nonnull GetNSView () = 0;
}; };
#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H #endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

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

@ -26,17 +26,13 @@ private:
PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{ {
WindowEvents = events; WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
} }
protected: protected:
virtual NSWindowStyleMask GetStyle() override virtual NSWindowStyleMask GetStyle() override
{ {
return NSWindowStyleMaskBorderless; return NSWindowStyleMaskBorderless;
} }
virtual void OnInitialiseNSWindow () override
{
[Window setLevel:NSPopUpMenuWindowLevel];
}
public: public:
virtual bool ShouldTakeFocusOnShow() override virtual bool ShouldTakeFocusOnShow() override

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

@ -26,7 +26,7 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl(); virtual ~WindowBaseImpl();
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl); WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
virtual HRESULT ObtainNSWindowHandle(void **ret) override; virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -38,7 +38,7 @@ BEGIN_INTERFACE_MAP()
virtual NSWindow *GetNSWindow() override; virtual NSWindow *GetNSWindow() override;
virtual NSView *GetNSView() override; virtual AvnView *GetNSView() override;
virtual HRESULT Show(bool activate, bool isDialog) override; virtual HRESULT Show(bool activate, bool isDialog) override;
@ -99,18 +99,17 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog(); virtual bool IsDialog();
id<AvnWindowProtocol> GetWindowProtocol (); id<AvnWindowProtocol> GetWindowProtocol ();
virtual void BringToFront ();
protected: protected:
virtual NSWindowStyleMask GetStyle(); virtual NSWindowStyleMask GetStyle();
void UpdateStyle(); void UpdateStyle();
virtual void OnInitialiseNSWindow ();
private: private:
void CreateNSWindow (bool isDialog); void CreateNSWindow (bool isDialog);
void CleanNSWindow (); void CleanNSWindow ();
void InitialiseNSWindow ();
NSCursor *cursor; NSCursor *cursor;
ComPtr<IAvnGlContext> _glContext; ComPtr<IAvnGlContext> _glContext;

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

@ -21,7 +21,7 @@ WindowBaseImpl::~WindowBaseImpl() {
Window = nullptr; Window = nullptr;
} }
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) { WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
_shown = false; _shown = false;
_inResize = false; _inResize = false;
BaseEvents = events; BaseEvents = events;
@ -36,8 +36,19 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 }; lastMinSize = NSSize { 0, 0 };
Window = nullptr;
lastMenu = nullptr; lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window setHasShadow:true];
} }
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
@ -68,7 +79,7 @@ NSWindow *WindowBaseImpl::GetNSWindow() {
return Window; return Window;
} }
NSView *WindowBaseImpl::GetNSView() { AvnView *WindowBaseImpl::GetNSView() {
return View; return View;
} }
@ -88,9 +99,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
CreateNSWindow(isDialog); [Window setContentSize:lastSize];
InitialiseNSWindow();
if(hasPosition) if(hasPosition)
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
@ -100,6 +110,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
} }
UpdateStyle(); UpdateStyle();
[Window invalidateShadow];
if (ShouldTakeFocusOnShow() && activate) { if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window]; [Window orderFront:Window];
@ -143,8 +155,6 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool { @autoreleasepool {
if (Window != nullptr) { if (Window != nullptr) {
[Window orderOut:Window]; [Window orderOut:Window];
[GetWindowProtocol() restoreParentWindow];
} }
return S_OK; return S_OK;
@ -293,8 +303,7 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
if (!_shown) { if (!_shown) {
BaseEvents->Resized(AvnSize{x, y}, reason); BaseEvents->Resized(AvnSize{x, y}, reason);
} }
else if(Window != nullptr) {
if(Window != nullptr) {
[Window setContentSize:lastSize]; [Window setContentSize:lastSize];
[Window invalidateShadow]; [Window invalidateShadow];
} }
@ -558,6 +567,8 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
CleanNSWindow(); CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
[Window setHidesOnDeactivate:false];
} }
} else { } else {
if (![Window isKindOfClass:[AvnWindow class]]) { if (![Window isKindOfClass:[AvnWindow class]]) {
@ -568,37 +579,6 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
} }
} }
void WindowBaseImpl::OnInitialiseNSWindow()
{
}
void WindowBaseImpl::InitialiseNSWindow() {
if(Window != nullptr) {
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentSize:lastSize];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window invalidateShadow];
if (lastMenu != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
OnInitialiseNSWindow();
}
}
id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() { id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
if(Window == nullptr) if(Window == nullptr)
{ {
@ -608,6 +588,11 @@ id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
return (id <AvnWindowProtocol>) Window; return (id <AvnWindowProtocol>) Window;
} }
void WindowBaseImpl::BringToFront()
{
// do nothing.
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{ {
@autoreleasepool @autoreleasepool

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

@ -8,10 +8,12 @@
#import "WindowBaseImpl.h" #import "WindowBaseImpl.h"
#include "IWindowStateChanged.h" #include "IWindowStateChanged.h"
#include <list>
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{ {
private: private:
bool _isEnabled;
bool _canResize; bool _canResize;
bool _fullScreenActive; bool _fullScreenActive;
SystemDecorations _decorations; SystemDecorations _decorations;
@ -22,6 +24,8 @@ private:
bool _transitioningWindowState; bool _transitioningWindowState;
bool _isClientAreaExtended; bool _isClientAreaExtended;
bool _isDialog; bool _isDialog;
WindowImpl* _parent;
std::list<WindowImpl*> _children;
AvnExtendClientAreaChromeHints _extendClientHints; AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
@ -89,12 +93,15 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog() override; virtual bool IsDialog() override;
virtual void OnInitialiseNSWindow() override; virtual void BringToFront () override;
bool CanBecomeKeyWindow ();
protected: protected:
virtual NSWindowStyleMask GetStyle() override; virtual NSWindowStyleMask GetStyle() override;
private: private:
void OnInitialiseNSWindow();
NSString *_lastTitle; NSString *_lastTitle;
}; };

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

@ -10,6 +10,8 @@
#include "WindowProtocol.h" #include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
_isEnabled = true;
_children = std::list<WindowImpl*>();
_isClientAreaExtended = false; _isClientAreaExtended = false;
_extendClientHints = AvnDefaultChrome; _extendClientHints = AvnDefaultChrome;
_fullScreenActive = false; _fullScreenActive = false;
@ -20,7 +22,10 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_lastWindowState = Normal; _lastWindowState = Normal;
_actualWindowState = Normal; _actualWindowState = Normal;
_lastTitle = @""; _lastTitle = @"";
_parent = nullptr;
WindowEvents = events; WindowEvents = events;
OnInitialiseNSWindow();
} }
void WindowImpl::HideOrShowTrafficLights() { void WindowImpl::HideOrShowTrafficLights() {
@ -28,28 +33,17 @@ void WindowImpl::HideOrShowTrafficLights() {
return; return;
} }
for (id subview in Window.contentView.superview.subviews) { bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) { [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
if ([button isKindOfClass:[NSButton class]]) { [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
if (_isClientAreaExtended) { [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
[button setHidden:!wantsChrome];
} else {
[button setHidden:(_decorations != SystemDecorationsFull)];
}
[button setWantsLayer:true];
}
}
}
}
} }
void WindowImpl::OnInitialiseNSWindow(){ void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true]; [GetWindowProtocol() setCanBecomeKeyWindow:true];
[Window disableCursorRects]; [Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed]; [Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
@ -81,7 +75,9 @@ HRESULT WindowImpl::SetEnabled(bool enable) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
_isEnabled = enable;
[GetWindowProtocol() setEnabled:enable]; [GetWindowProtocol() setEnabled:enable];
UpdateStyle();
return S_OK; return S_OK;
} }
} }
@ -90,26 +86,68 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
if (parent == nullptr) if(_parent != nullptr)
return E_POINTER; {
_parent->_children.remove(this);
_parent->BringToFront();
}
auto cparent = dynamic_cast<WindowImpl *>(parent); auto cparent = dynamic_cast<WindowImpl *>(parent);
if (cparent == nullptr)
return E_INVALIDARG; _parent = cparent;
// If one tries to show a child window with a minimized parent window, then the parent window will be if(_parent != nullptr && Window != nullptr){
// restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive // If one tries to show a child window with a minimized parent window, then the parent window will be
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation. // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
if (cparent->WindowState() == Minimized) // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
cparent->SetWindowState(Normal); if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
cparent->_children.push_back(this);
UpdateStyle();
}
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; return S_OK;
[cparent->Window addChildWindow:Window ordered:NSWindowAbove]; }
}
UpdateStyle(); void WindowImpl::BringToFront()
{
if(Window != nullptr)
{
if(IsDialog())
{
Activate();
}
else
{
[Window orderFront:nullptr];
}
[Window invalidateShadow];
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
(*iterator)->BringToFront();
}
}
}
return S_OK; bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
if((*iterator)->IsDialog())
{
return false;
}
} }
return true;
} }
void WindowImpl::StartStateTransition() { void WindowImpl::StartStateTransition() {
@ -523,7 +561,12 @@ bool WindowImpl::IsDialog() {
} }
NSWindowStyleMask WindowImpl::GetStyle() { NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless; unsigned long s = NSWindowStyleMaskBorderless;
if(_actualWindowState == FullScreen)
{
s |= NSWindowStyleMaskFullScreen;
}
switch (_decorations) { switch (_decorations) {
case SystemDecorationsNone: case SystemDecorationsNone:
@ -535,15 +578,15 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break; break;
case SystemDecorationsFull: case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless; s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (_canResize) { if (_canResize && _isEnabled) {
s = s | NSWindowStyleMaskResizable; s = s | NSWindowStyleMaskResizable;
} }
break; break;
} }
if ([Window parentWindow] == nullptr) { if (!IsDialog()) {
s |= NSWindowStyleMaskMiniaturizable; s |= NSWindowStyleMaskMiniaturizable;
} }

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

@ -11,7 +11,6 @@
@protocol AvnWindowProtocol @protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents; -(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable; -(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly; -(void) showAppMenuOnly;

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

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

15
readme.md

@ -70,11 +70,15 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
Avalonia is licenced under the [MIT licence](licence.md). Avalonia is licenced under the [MIT licence](licence.md).
## Support Avalonia ## Donate
**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx Donating to the project is a fantastic way to thank our valued contributors for their hard work. Your donations are shared among our community and awarded for significant contributions.
If you need support see Commercial Support section below.
Donate with BTC or use [Open Collective](https://opencollective.com/avalonia).
This will be shared with the community and awarded for significant contributions. **BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
### Backers ### Backers
@ -98,6 +102,11 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
<a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a> <a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a>
## Commercial Support
We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)*
## .NET Foundation ## .NET Foundation
This project is supported by the [.NET Foundation](https://dotnetfoundation.org). This project is supported by the [.NET Foundation](https://dotnetfoundation.org).

35
samples/ControlCatalog.Android/EmbedSample.Android.cs

@ -0,0 +1,35 @@
using System;
using Avalonia.Platform;
using Avalonia.Android;
using ControlCatalog.Pages;
namespace ControlCatalog.Android;
public class EmbedSampleAndroid : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
var parentContext = (parent as AndroidViewControlHandle)?.View.Context
?? global::Android.App.Application.Context;
if (isSecond)
{
var webView = new global::Android.Webkit.WebView(parentContext);
webView.LoadUrl("https://www.android.com/");
return new AndroidViewControlHandle(webView);
}
else
{
var button = new global::Android.Widget.Button(parentContext) { Text = "Hello world" };
var clickCount = 0;
button.Click += (sender, args) =>
{
clickCount++;
button.Text = $"Click count {clickCount}";
};
return new AndroidViewControlHandle(button);
}
}
}

6
samples/ControlCatalog.Android/MainActivity.cs

@ -10,7 +10,11 @@ namespace ControlCatalog.Android
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{ {
return base.CustomizeAppBuilder(builder); return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
} }
} }
} }

14
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -4,6 +4,7 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'"> <PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -12,6 +13,16 @@
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion> <NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" Link="NativeControls\Gtk\Gtk.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="NativeControls\Gtk\nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
@ -20,6 +31,8 @@
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RunNativeAotCompilation)' == 'true'"> <ItemGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -32,6 +45,7 @@
<PropertyGroup> <PropertyGroup>
<!-- For Microsoft.CodeAnalysis --> <!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />

35
samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs

@ -0,0 +1,35 @@
using System.IO;
using System.Diagnostics;
using Avalonia.Platform;
using Avalonia.Controls.Platform;
using System;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore;
public class EmbedSampleGtk : INativeDemoControl
{
private Process _mplayer;
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
if (isSecond)
{
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
if (chooser != null)
return chooser;
}
var control = createDefault();
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
"..",
"nodes.mp4"));
_mplayer = Process.Start(new ProcessStartInfo("mplayer",
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
{
UseShellExecute = false,
});
return control;
}
}

58
samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs

@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Platform.Interop;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.NativeDialogs.Gtk;
using static Avalonia.X11.NativeDialogs.Glib;
namespace ControlCatalog.NetCore;
internal class GtkHelper
{
private static Task<bool> s_gtkTask;
class FileChooser : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _widget;
public FileChooser(IntPtr widget, IntPtr xid)
{
_widget = widget;
Handle = xid;
}
public IntPtr Handle { get; }
public string HandleDescriptor => "XID";
public void Destroy()
{
RunOnGlibThread(() =>
{
gtk_widget_destroy(_widget);
return 0;
}).Wait();
}
}
public static INativeControlHostDestroyableControlHandle CreateGtkFileChooser(IntPtr parentXid)
{
if (s_gtkTask == null)
s_gtkTask = StartGtk();
if (!s_gtkTask.Result)
return null;
return RunOnGlibThread(() =>
{
using (var title = new Utf8Buffer("Embedded"))
{
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
IntPtr.Zero);
gtk_widget_realize(widget);
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
gtk_window_present(widget);
return new FileChooser(widget, xid);
}
}).Result;
}
}

0
samples/interop/NativeEmbedSample/nodes-license.md → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md

0
samples/interop/NativeEmbedSample/nodes.mp4 → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4

29
samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Platform;
using Avalonia.Threading;
using ControlCatalog.Pages;
using MonoMac.Foundation;
using MonoMac.WebKit;
namespace ControlCatalog.NetCore;
public class EmbedSampleMac : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
// Note: We are using MonoMac for example purposes
// It shouldn't be used in production apps
MacHelper.EnsureInitialized();
var webView = new WebView();
Dispatcher.UIThread.Post(() =>
{
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
isSecond ? "https://bing.com" : "https://google.com/")));
});
return new MacOSViewHandle(webView);
}
}

38
samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Controls.Platform;
using MonoMac.AppKit;
namespace ControlCatalog.NetCore;
internal class MacHelper
{
private static bool _isInitialized;
public static void EnsureInitialized()
{
if (_isInitialized)
return;
_isInitialized = true;
NSApplication.Init();
}
}
internal class MacOSViewHandle : INativeControlHostDestroyableControlHandle
{
private NSView _view;
public MacOSViewHandle(NSView view)
{
_view = view;
}
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
public string HandleDescriptor => "NSView";
public void Destroy()
{
_view.Dispose();
_view = null;
}
}

45
samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs

@ -0,0 +1,45 @@
using System;
using System.Text;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore;
public class EmbedSampleWin : INativeDemoControl
{
private const string RichText =
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
{\*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
}";
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
WinApi.LoadLibrary("Msftedit.dll");
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
@"Rich Edit",
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
var text = RichText.Replace("<PREFIX>", isSecond ? "\\qr " : "");
var bytes = Encoding.UTF8.GetBytes(text);
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
return new Win32WindowControlHandle(handle, "HWND");
}
}
internal class Win32WindowControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
{
public Win32WindowControlHandle(IntPtr handle, string descriptor) : base(handle, descriptor)
{
}
public void Destroy()
{
_ = WinApi.DestroyWindow(Handle);
}
}

73
samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs

@ -0,0 +1,73 @@
using System;
using System.Runtime.InteropServices;
namespace ControlCatalog.NetCore;
internal unsafe class WinApi
{
public enum CommonControls : uint
{
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
ICC_UPDOWN_CLASS = 0x00000010, // updown
ICC_PROGRESS_CLASS = 0x00000020, // progress
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
ICC_ANIMATE_CLASS = 0x00000080, // animate
ICC_WIN95_CLASSES = 0x000000FF,
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
ICC_USEREX_CLASSES = 0x00000200, // comboex
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
ICC_INTERNET_CLASSES = 0x00000800,
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
ICC_STANDARD_CLASSES = 0x00004000,
ICC_LINK_CLASS = 0x00008000
}
[StructLayout(LayoutKind.Sequential)]
public struct INITCOMMONCONTROLSEX
{
public int dwSize;
public uint dwICC;
}
[DllImport("Comctl32.dll")]
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[StructLayout(LayoutKind.Sequential)]
public struct SETTEXTEX
{
public uint Flags;
public uint Codepage;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
}

8
samples/ControlCatalog.NetCore/Program.cs

@ -7,11 +7,12 @@ using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Dialogs;
using Avalonia.Headless; using Avalonia.Headless;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Threading; using Avalonia.Threading;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore namespace ControlCatalog.NetCore
{ {
static class Program static class Program
@ -123,6 +124,11 @@ namespace ControlCatalog.NetCore
{ {
StartupScreenIndex = 1, StartupScreenIndex = 1,
}); });
EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
: OperatingSystem.IsLinux() ? new EmbedSampleGtk()
: null;
}) })
.LogToTrace(); .LogToTrace();

28
samples/ControlCatalog.NetCore/app.manifest

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="ControlCatalog.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

4
samples/ControlCatalog.Web/App.razor.cs

@ -7,6 +7,10 @@ public partial class App
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
WebAppBuilder.Configure<ControlCatalog.App>() WebAppBuilder.Configure<ControlCatalog.App>()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
})
.SetupWithSingleViewLifetime(); .SetupWithSingleViewLifetime();
base.OnParametersSet(); base.OnParametersSet();

34
samples/ControlCatalog.Web/EmbedSample.Browser.cs

@ -0,0 +1,34 @@
using System;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Web.Blazor;
using ControlCatalog.Pages;
using Microsoft.JSInterop;
namespace ControlCatalog.Web;
public class EmbedSampleWeb : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>();
if (isSecond)
{
var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe");
iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70");
return new JSObjectControlHandle(iframe);
}
else
{
// window.createAppButton source is defined in "app.js" file.
var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton");
return new JSObjectControlHandle(button);
}
}
}

70
samples/ControlCatalog.Web/Shared/MainLayout.razor.css

@ -1,70 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

44
samples/ControlCatalog.Web/wwwroot/css/app.css

@ -44,47 +44,13 @@ a, .btn-link {
z-index: 1000; z-index: 1000;
} }
#blazor-error-ui .dismiss { #blazor-error-ui .dismiss {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
right: 0.75rem; right: 0.75rem;
top: 0.5rem; top: 0.5rem;
}
.canvas-container {
opacity:1;
background-color:#ccc;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
z-index:500;
}
canvas
{
opacity:1;
background-color:#ccc;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
z-index:500;
} }
#app, .page { #app, .page {
height: 100%; height: 100%;
} }
.overlay{
opacity:0.0;
background-color:#ccc;
position:fixed;
width:100vw;
height:100vh;
top:0px;
left:0px;
z-index:1000;
}

11
samples/ControlCatalog.Web/wwwroot/js/app.js

@ -1 +1,10 @@
 window.createAppButton = function () {
var button = document.createElement('button');
button.innerText = 'Hello world';
var clickCount = 0;
button.onclick = () => {
clickCount++;
button.innerText = 'Click count ' + clickCount;
};
return button;
}

9
samples/ControlCatalog.iOS/AppDelegate.cs

@ -13,6 +13,13 @@ namespace ControlCatalog
[Register("AppDelegate")] [Register("AppDelegate")]
public partial class AppDelegate : AvaloniaAppDelegate<App> public partial class AppDelegate : AvaloniaAppDelegate<App>
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleIOS();
});
}
} }
} }

38
samples/ControlCatalog.iOS/EmbedSample.iOS.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Platform;
using CoreGraphics;
using Foundation;
using UIKit;
using WebKit;
using Avalonia.iOS;
using ControlCatalog.Pages;
namespace ControlCatalog;
public class EmbedSampleIOS : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
if (isSecond)
{
var webView = new WKWebView(CGRect.Empty, new WKWebViewConfiguration());
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://www.apple.com/")));
return new UIViewControlHandle(webView);
}
else
{
var button = new UIButton();
var clickCount = 0;
button.SetTitle("Hello world", UIControlState.Normal);
button.BackgroundColor = UIColor.Blue;
button.AddTarget((_, _) =>
{
clickCount++;
button.SetTitle($"Click count {clickCount}", UIControlState.Normal);
}, UIControlEvent.TouchDown);
return new UIViewControlHandle(button);
}
}
}

15
samples/ControlCatalog/ControlCatalog.csproj

@ -14,6 +14,9 @@
<AvaloniaResource Include="Assets\*" /> <AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="Assets\Fonts\*" /> <AvaloniaResource Include="Assets\Fonts\*" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Pages\NativeEmbedPage.xaml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@ -32,5 +35,17 @@
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AvaloniaResource Update="Pages\NativeEmbedPage.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Pages\NativeEmbedPage.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
</Project> </Project>

10
samples/ControlCatalog/Converter/MathSubtractConverter.cs

@ -6,12 +6,16 @@ namespace ControlCatalog.Converter;
public class MathSubtractConverter : IValueConverter public class MathSubtractConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
return (double)value - (double)parameter; if (value is double dv && parameter is double dp)
{
return dv - dp;
}
return double.NaN;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

10
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -15,7 +15,7 @@ namespace ControlCatalog
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge) void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
{ {
var ctl = this.FindControl<Control>(name); var ctl = this.Get<Control>(name);
ctl.Cursor = new Cursor(cursor); ctl.Cursor = new Cursor(cursor);
ctl.PointerPressed += (i, e) => ctl.PointerPressed += (i, e) =>
{ {
@ -26,7 +26,7 @@ namespace ControlCatalog
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
this.FindControl<Control>("TitleBar").PointerPressed += (i, e) => this.Get<Control>("TitleBar").PointerPressed += (i, e) =>
{ {
PlatformImpl?.BeginMoveDrag(e); PlatformImpl?.BeginMoveDrag(e);
}; };
@ -38,12 +38,12 @@ namespace ControlCatalog
SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast); SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest); SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);
SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast); SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast);
this.FindControl<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; }; this.Get<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; };
this.FindControl<Button>("MaximizeButton").Click += delegate this.Get<Button>("MaximizeButton").Click += delegate
{ {
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}; };
this.FindControl<Button>("CloseButton").Click += delegate this.Get<Button>("CloseButton").Click += delegate
{ {
Close(); Close();
}; };

7
samples/ControlCatalog/MainView.xaml

@ -2,8 +2,8 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples" xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:models="clr-namespace:ControlCatalog.Models"
xmlns:models="clr-namespace:ControlCatalog.Models"> xmlns:pages="clr-namespace:ControlCatalog.Pages">
<Grid> <Grid>
<Grid.Styles> <Grid.Styles>
<Style Selector="TextBlock.h2"> <Style Selector="TextBlock.h2">
@ -157,6 +157,9 @@
<TabItem Header="Viewbox"> <TabItem Header="Viewbox">
<pages:ViewboxPage /> <pages:ViewboxPage />
</TabItem> </TabItem>
<TabItem Header="Native Embed">
<pages:NativeEmbedPage />
</TabItem>
<TabItem Header="Window Customizations"> <TabItem Header="Window Customizations">
<pages:WindowCustomizationsPage /> <pages:WindowCustomizationsPage />
</TabItem> </TabItem>

16
samples/ControlCatalog/MainView.xaml.cs

@ -18,9 +18,9 @@ namespace ControlCatalog
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
var sideBar = this.FindControl<TabControl>("Sidebar"); var sideBar = this.Get<TabControl>("Sidebar");
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop) if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
{ {
IList tabItems = ((IList)sideBar.Items); IList tabItems = ((IList)sideBar.Items);
tabItems.Add(new TabItem() tabItems.Add(new TabItem()
@ -36,7 +36,7 @@ namespace ControlCatalog
} }
var themes = this.Find<ComboBox>("Themes"); var themes = this.Get<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) => themes.SelectionChanged += (sender, e) =>
{ {
if (themes.SelectedItem is CatalogTheme theme) if (themes.SelectedItem is CatalogTheme theme)
@ -80,7 +80,7 @@ namespace ControlCatalog
} }
}; };
var flowDirections = this.Find<ComboBox>("FlowDirection"); var flowDirections = this.Get<ComboBox>("FlowDirection");
flowDirections.SelectionChanged += (sender, e) => flowDirections.SelectionChanged += (sender, e) =>
{ {
if (flowDirections.SelectedItem is FlowDirection flowDirection) if (flowDirections.SelectedItem is FlowDirection flowDirection)
@ -89,7 +89,7 @@ namespace ControlCatalog
} }
}; };
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Get<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) => decorations.SelectionChanged += (sender, e) =>
{ {
if (VisualRoot is Window window if (VisualRoot is Window window
@ -99,8 +99,8 @@ namespace ControlCatalog
} }
}; };
var transparencyLevels = this.Find<ComboBox>("TransparencyLevels"); var transparencyLevels = this.Get<ComboBox>("TransparencyLevels");
IDisposable backgroundSetter = null, paneBackgroundSetter = null; IDisposable? backgroundSetter = null, paneBackgroundSetter = null;
transparencyLevels.SelectionChanged += (sender, e) => transparencyLevels.SelectionChanged += (sender, e) =>
{ {
backgroundSetter?.Dispose(); backgroundSetter?.Dispose();
@ -118,7 +118,7 @@ namespace ControlCatalog
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Get<ComboBox>("Decorations");
if (VisualRoot is Window window) if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations; decorations.SelectedIndex = (int)window.SystemDecorations;
} }

8
samples/ControlCatalog/MainWindow.xaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog
public class MainWindow : Window public class MainWindow : Window
{ {
private WindowNotificationManager _notificationArea; private WindowNotificationManager _notificationArea;
private NativeMenu _recentMenu; private NativeMenu? _recentMenu;
public MainWindow() public MainWindow()
{ {
@ -28,9 +28,7 @@ namespace ControlCatalog
}; };
DataContext = new MainWindowViewModel(_notificationArea); DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; _recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
} }
public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit"; public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";
@ -41,7 +39,7 @@ namespace ControlCatalog
public void OnOpenClicked(object sender, EventArgs args) public void OnOpenClicked(object sender, EventArgs args)
{ {
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1))); _recentMenu?.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
} }
public void OnCloseClicked(object sender, EventArgs args) public void OnCloseClicked(object sender, EventArgs args)

4
samples/ControlCatalog/Models/Countries.cs

@ -237,7 +237,7 @@ namespace ControlCatalog.Models
yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84); yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84);
} }
static IReadOnlyList<Country> _all; static IReadOnlyList<Country>? _all;
public static IReadOnlyList<Country> All public static IReadOnlyList<Country> All
{ {
@ -253,4 +253,4 @@ namespace ControlCatalog.Models
} }
} }
} }

4
samples/ControlCatalog/Models/GDPValueConverter.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.Models
{ {
public class GDPValueConverter : IValueConverter public class GDPValueConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value is int gdp) if (value is int gdp)
{ {
@ -29,7 +29,7 @@ namespace ControlCatalog.Models
return value; return value;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

16
samples/ControlCatalog/Models/Person.cs

@ -13,8 +13,8 @@ namespace ControlCatalog.Models
{ {
public class Person : INotifyDataErrorInfo, INotifyPropertyChanged public class Person : INotifyDataErrorInfo, INotifyPropertyChanged
{ {
string _firstName; string _firstName = string.Empty;
string _lastName; string _lastName = string.Empty;
bool _isBanned; bool _isBanned;
private int _age; private int _age;
@ -76,7 +76,7 @@ namespace ControlCatalog.Models
Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>(); Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
void SetError(string propertyName, string error) void SetError(string propertyName, string? error)
{ {
if (string.IsNullOrEmpty(error)) if (string.IsNullOrEmpty(error))
{ {
@ -88,11 +88,11 @@ namespace ControlCatalog.Models
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
{ {
errorList.Clear(); errorList.Clear();
errorList.Add(error); errorList.Add(error!);
} }
else else
{ {
var errors = new List<string> { error }; var errors = new List<string> { error! };
_errorLookup.Add(propertyName, errors); _errorLookup.Add(propertyName, errors);
} }
@ -102,8 +102,8 @@ namespace ControlCatalog.Models
public bool HasErrors => _errorLookup.Count > 0; public bool HasErrors => _errorLookup.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
void OnErrorsChanged(string propertyName) void OnErrorsChanged(string propertyName)
{ {
@ -114,7 +114,7 @@ namespace ControlCatalog.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
public IEnumerable GetErrors(string propertyName) public IEnumerable? GetErrors(string propertyName)
{ {
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
return errorList; return errorList;

33
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -126,13 +126,13 @@ namespace ControlCatalog.Pages
binding.Bindings.Add(new Binding("Name")); binding.Bindings.Add(new Binding("Name"));
binding.Bindings.Add(new Binding("Abbreviation")); binding.Bindings.Add(new Binding("Abbreviation"));
var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox"); var multibindingBox = this.Get<AutoCompleteBox>("MultiBindingBox");
multibindingBox.ValueMemberBinding = binding; multibindingBox.ValueMemberBinding = binding;
var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox"); var asyncBox = this.Get<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync; asyncBox.AsyncPopulator = PopulateAsync;
var customAutocompleteBox = this.FindControl<AutoCompleteBox>("CustomAutocompleteBox"); var customAutocompleteBox = this.Get<AutoCompleteBox>("CustomAutocompleteBox");
customAutocompleteBox.Items = Sentences.SelectMany(x => x); customAutocompleteBox.Items = Sentences.SelectMany(x => x);
customAutocompleteBox.TextFilter = LastWordContains; customAutocompleteBox.TextFilter = LastWordContains;
customAutocompleteBox.TextSelector = AppendWord; customAutocompleteBox.TextSelector = AppendWord;
@ -144,11 +144,12 @@ namespace ControlCatalog.Pages
.OfType<AutoCompleteBox>(); .OfType<AutoCompleteBox>();
} }
private bool StringContains(string str, string query) private bool StringContains(string str, string? query)
{ {
if (query == null) return false;
return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken) private async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
{ {
await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken); await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
@ -157,14 +158,14 @@ namespace ControlCatalog.Pages
.ToList(); .ToList();
} }
private bool LastWordContains(string searchText, string item) private bool LastWordContains(string? searchText, string? item)
{ {
var words = searchText.Split(' '); var words = searchText?.Split(' ') ?? Array.Empty<string>();
var options = Sentences.Select(x => x.First).ToArray(); var options = Sentences.Select(x => x.First).ToArray();
for (var i = 0; i < words.Length; ++i) for (var i = 0; i < words.Length; ++i)
{ {
var word = words[i]; var word = words[i];
for (var j = 0; j < options.Length; ++j) for (var j = 0; word is { } && j < options.Length; ++j)
{ {
var option = options[j]; var option = options[j];
if (option == null) if (option == null)
@ -183,14 +184,18 @@ namespace ControlCatalog.Pages
return options.Any(x => x != null && x.Value == item); return options.Any(x => x != null && x.Value == item);
} }
private string AppendWord(string text, string item) private string AppendWord(string? text, string? item)
{ {
string[] parts = text.Split(' '); if (item is { })
if (parts.Length == 0) {
return item; string[] parts = text?.Split(' ') ?? Array.Empty<string>();
if (parts.Length == 0)
return item;
parts[parts.Length - 1] = item; parts[parts.Length - 1] = item;
return string.Join(" ", parts); return string.Join(" ", parts);
}
return string.Empty;
} }
private void InitializeComponent() private void InitializeComponent()

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

@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
var spinner = (ButtonSpinner)sender; var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content; var txtBox = (TextBlock)spinner.Content;
int value = Array.IndexOf(_mountains, txtBox.Text); int value = Array.IndexOf(_mountains, txtBox?.Text);
if (e.Direction == SpinDirection.Increase) if (e.Direction == SpinDirection.Increase)
value++; value++;
else else

4
samples/ControlCatalog/Pages/ButtonsPage.xaml.cs

@ -11,7 +11,7 @@ namespace ControlCatalog.Pages
{ {
InitializeComponent(); InitializeComponent();
this.FindControl<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick; this.Get<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
} }
private void InitializeComponent() private void InitializeComponent()
@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
public void OnRepeatButtonClick(object sender, object args) public void OnRepeatButtonClick(object sender, object args)
{ {
repeatButtonClickCount++; repeatButtonClickCount++;
var textBlock = this.FindControl<TextBlock>("RepeatButtonTextBlock"); var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");
textBlock.Text = $"Repeat Button: {repeatButtonClickCount}"; textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
} }
} }

12
samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs

@ -10,11 +10,11 @@ namespace ControlCatalog.Pages
{ {
InitializeComponent(); InitializeComponent();
var dp1 = this.FindControl<CalendarDatePicker>("DatePicker1"); var dp1 = this.Get<CalendarDatePicker>("DatePicker1");
var dp2 = this.FindControl<CalendarDatePicker>("DatePicker2"); var dp2 = this.Get<CalendarDatePicker>("DatePicker2");
var dp3 = this.FindControl<CalendarDatePicker>("DatePicker3"); var dp3 = this.Get<CalendarDatePicker>("DatePicker3");
var dp4 = this.FindControl<CalendarDatePicker>("DatePicker4"); var dp4 = this.Get<CalendarDatePicker>("DatePicker4");
var dp5 = this.FindControl<CalendarDatePicker>("DatePicker5"); var dp5 = this.Get<CalendarDatePicker>("DatePicker5");
dp1.SelectedDate = DateTime.Today; dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10); dp2.SelectedDate = DateTime.Today.AddDays(10);
@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
dp4.TemplateApplied += (s, e) => dp4.TemplateApplied += (s, e) =>
{ {
dp4.BlackoutDates.AddDatesInPast(); dp4.BlackoutDates?.AddDatesInPast();
}; };
} }

4
samples/ControlCatalog/Pages/CalendarPage.xaml.cs

@ -11,11 +11,11 @@ namespace ControlCatalog.Pages
this.InitializeComponent(); this.InitializeComponent();
var today = DateTime.Today; var today = DateTime.Today;
var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar"); var cal1 = this.Get<Calendar>("DisplayDatesCalendar");
cal1.DisplayDateStart = today.AddDays(-25); cal1.DisplayDateStart = today.AddDays(-25);
cal1.DisplayDateEnd = today.AddDays(25); cal1.DisplayDateEnd = today.AddDays(25);
var cal2 = this.FindControl<Calendar>("BlackoutDatesCalendar"); var cal2 = this.Get<Calendar>("BlackoutDatesCalendar");
cal2.BlackoutDates.AddDatesInPast(); cal2.BlackoutDates.AddDatesInPast();
cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6))); cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6)));
} }

11
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -16,6 +16,11 @@ namespace ControlCatalog.Pages
public CarouselPage() public CarouselPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_carousel = this.Get<Carousel>("carousel");
_left = this.Get<Button>("left");
_right = this.Get<Button>("right");
_transition = this.Get<ComboBox>("transition");
_orientation = this.Get<ComboBox>("orientation");
_left.Click += (s, e) => _carousel.Previous(); _left.Click += (s, e) => _carousel.Previous();
_right.Click += (s, e) => _carousel.Next(); _right.Click += (s, e) => _carousel.Next();
_transition.SelectionChanged += TransitionChanged; _transition.SelectionChanged += TransitionChanged;
@ -25,11 +30,7 @@ namespace ControlCatalog.Pages
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
_carousel = this.FindControl<Carousel>("carousel");
_left = this.FindControl<Button>("left");
_right = this.FindControl<Button>("right");
_transition = this.FindControl<ComboBox>("transition");
_orientation = this.FindControl<ComboBox>("orientation");
} }
private void TransitionChanged(object sender, SelectionChangedEventArgs e) private void TransitionChanged(object sender, SelectionChangedEventArgs e)

22
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs

@ -18,29 +18,29 @@ namespace ControlCatalog.Pages
DataContext = new ContextPageViewModel(); DataContext = new ContextPageViewModel();
_textBox = this.FindControl<TextBox>("TextBox"); _textBox = this.Get<TextBox>("TextBox");
var cutButton = this.FindControl<Button>("CutButton"); var cutButton = this.Get<Button>("CutButton");
cutButton.Click += CloseFlyout; cutButton.Click += CloseFlyout;
var copyButton = this.FindControl<Button>("CopyButton"); var copyButton = this.Get<Button>("CopyButton");
copyButton.Click += CloseFlyout; copyButton.Click += CloseFlyout;
var pasteButton = this.FindControl<Button>("PasteButton"); var pasteButton = this.Get<Button>("PasteButton");
pasteButton.Click += CloseFlyout; pasteButton.Click += CloseFlyout;
var clearButton = this.FindControl<Button>("ClearButton"); var clearButton = this.Get<Button>("ClearButton");
clearButton.Click += CloseFlyout; clearButton.Click += CloseFlyout;
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder"); var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder"); var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing; cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening; cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening;
} }
private ContextPageViewModel _model; private ContextPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)
@ -55,7 +55,7 @@ namespace ControlCatalog.Pages
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox.IsChecked ?? false; e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
private void ContextFlyoutPage_Opening(object sender, EventArgs e) private void ContextFlyoutPage_Opening(object sender, EventArgs e)
@ -63,13 +63,13 @@ namespace ControlCatalog.Pages
if (e is CancelEventArgs cancelArgs) if (e is CancelEventArgs cancelArgs)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
} }
private void CloseFlyout(object sender, RoutedEventArgs e) private void CloseFlyout(object sender, RoutedEventArgs e)
{ {
_textBox.ContextFlyout.Hide(); _textBox.ContextFlyout?.Hide();
} }
public void CustomContextRequested(object sender, ContextRequestedEventArgs e) public void CustomContextRequested(object sender, ContextRequestedEventArgs e)

6
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@ -15,15 +15,15 @@ namespace ControlCatalog.Pages
this.InitializeComponent(); this.InitializeComponent();
DataContext = new ContextPageViewModel(); DataContext = new ContextPageViewModel();
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder"); var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder"); var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing; cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening; cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening;
} }
private ContextPageViewModel _model; private ContextPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)

8
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -21,7 +21,7 @@ namespace ControlCatalog.Pages
var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer()); var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer());
var collectionView1 = new DataGridCollectionView(Countries.All); var collectionView1 = new DataGridCollectionView(Countries.All);
collectionView1.SortDescriptions.Add(dataGridSortDescription); collectionView1.SortDescriptions.Add(dataGridSortDescription);
var dg1 = this.FindControl<DataGrid>("dataGrid1"); var dg1 = this.Get<DataGrid>("dataGrid1");
dg1.IsReadOnly = true; dg1.IsReadOnly = true;
dg1.LoadingRow += Dg1_LoadingRow; dg1.LoadingRow += Dg1_LoadingRow;
dg1.Sorting += (s, a) => dg1.Sorting += (s, a) =>
@ -37,7 +37,7 @@ namespace ControlCatalog.Pages
}; };
dg1.Items = collectionView1; dg1.Items = collectionView1;
var dg2 = this.FindControl<DataGrid>("dataGridGrouping"); var dg2 = this.Get<DataGrid>("dataGridGrouping");
dg2.IsReadOnly = true; dg2.IsReadOnly = true;
var collectionView2 = new DataGridCollectionView(Countries.All); var collectionView2 = new DataGridCollectionView(Countries.All);
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
dg2.Items = collectionView2; dg2.Items = collectionView2;
var dg3 = this.FindControl<DataGrid>("dataGridEdit"); var dg3 = this.Get<DataGrid>("dataGridEdit");
dg3.IsReadOnly = false; dg3.IsReadOnly = false;
var items = new List<Person> var items = new List<Person>
@ -58,7 +58,7 @@ namespace ControlCatalog.Pages
dg3.Items = collectionView3; dg3.Items = collectionView3;
var addButton = this.FindControl<Button>("btnAdd"); var addButton = this.Get<Button>("btnAdd");
addButton.Click += (a, b) => collectionView3.AddNew(); addButton.Click += (a, b) => collectionView3.AddNew();
} }

4
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@ -9,12 +9,12 @@ namespace ControlCatalog.Pages
public DateTimePickerPage() public DateTimePickerPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " + this.Get<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
"for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " + "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
"These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"Order of month, day, and year is dynamically set based on user date settings"; "Order of month, day, and year is dynamically set based on user date settings";
this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";

2
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -20,7 +20,7 @@
Text="Window dialogs" /> Text="Window dialogs" />
<Button Name="DecoratedWindow">Decorated _window</Button> <Button Name="DecoratedWindow">Decorated _window</Button>
<Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button> <Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
<Button Name="Dialog">_Dialog</Button> <Button Name="Dialog" ToolTip.Tip="Shows a dialog">_Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button> <Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
<Button Name="OwnedWindow">Own_ed window</Button> <Button Name="OwnedWindow">Own_ed window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button> <Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>

62
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -16,14 +16,14 @@ namespace ControlCatalog.Pages
{ {
this.InitializeComponent(); this.InitializeComponent();
var results = this.FindControl<ItemsPresenter>("PickerLastResults"); var results = this.Get<ItemsPresenter>("PickerLastResults");
var resultsVisible = this.FindControl<TextBlock>("PickerLastResultsVisible"); var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
string lastSelectedDirectory = null; string? lastSelectedDirectory = null;
List<FileDialogFilter>? GetFilters() List<FileDialogFilter>? GetFilters()
{ {
if (this.FindControl<CheckBox>("UseFilters").IsChecked != true) if (this.Get<CheckBox>("UseFilters").IsChecked != true)
return null; return null;
return new List<FileDialogFilter> return new List<FileDialogFilter>
{ {
@ -39,20 +39,24 @@ namespace ControlCatalog.Pages
}; };
} }
this.FindControl<Button>("OpenFile").Click += async delegate this.Get<Button>("OpenFile").Click += async delegate
{ {
// Almost guaranteed to exist
var fullPath = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
var initialFileName = fullPath == null ? null : System.IO.Path.GetFileName(fullPath);
var initialDirectory = fullPath == null ? null : System.IO.Path.GetDirectoryName(fullPath);
var result = await new OpenFileDialog() var result = await new OpenFileDialog()
{ {
Title = "Open file", Title = "Open file",
Filters = GetFilters(), Filters = GetFilters(),
Directory = lastSelectedDirectory, Directory = initialDirectory,
// Almost guaranteed to exist InitialFileName = initialFileName
InitialFileName = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName
}.ShowAsync(GetWindow()); }.ShowAsync(GetWindow());
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("OpenMultipleFiles").Click += async delegate this.Get<Button>("OpenMultipleFiles").Click += async delegate
{ {
var result = await new OpenFileDialog() var result = await new OpenFileDialog()
{ {
@ -64,7 +68,7 @@ namespace ControlCatalog.Pages
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("SaveFile").Click += async delegate this.Get<Button>("SaveFile").Click += async delegate
{ {
var result = await new SaveFileDialog() var result = await new SaveFileDialog()
{ {
@ -76,18 +80,23 @@ namespace ControlCatalog.Pages
results.Items = new[] { result }; results.Items = new[] { result };
resultsVisible.IsVisible = result != null; resultsVisible.IsVisible = result != null;
}; };
this.FindControl<Button>("SelectFolder").Click += async delegate this.Get<Button>("SelectFolder").Click += async delegate
{ {
var result = await new OpenFolderDialog() var result = await new OpenFolderDialog()
{ {
Title = "Select folder", Title = "Select folder",
Directory = lastSelectedDirectory, Directory = lastSelectedDirectory,
}.ShowAsync(GetWindow()); }.ShowAsync(GetWindow());
lastSelectedDirectory = result;
if (!string.IsNullOrEmpty(result))
{
lastSelectedDirectory = result;
}
results.Items = new [] { result }; results.Items = new [] { result };
resultsVisible.IsVisible = result != null; resultsVisible.IsVisible = result != null;
}; };
this.FindControl<Button>("OpenBoth").Click += async delegate this.Get<Button>("OpenBoth").Click += async delegate
{ {
var result = await new OpenFileDialog() var result = await new OpenFileDialog()
{ {
@ -101,35 +110,35 @@ namespace ControlCatalog.Pages
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("DecoratedWindow").Click += delegate this.Get<Button>("DecoratedWindow").Click += delegate
{ {
new DecoratedWindow().Show(); new DecoratedWindow().Show();
}; };
this.FindControl<Button>("DecoratedWindowDialog").Click += delegate this.Get<Button>("DecoratedWindowDialog").Click += delegate
{ {
new DecoratedWindow().ShowDialog(GetWindow()); new DecoratedWindow().ShowDialog(GetWindow());
}; };
this.FindControl<Button>("Dialog").Click += delegate this.Get<Button>("Dialog").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Height = 200; window.Height = 200;
window.ShowDialog(GetWindow()); window.ShowDialog(GetWindow());
}; };
this.FindControl<Button>("DialogNoTaskbar").Click += delegate this.Get<Button>("DialogNoTaskbar").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Height = 200; window.Height = 200;
window.ShowInTaskbar = false; window.ShowInTaskbar = false;
window.ShowDialog(GetWindow()); window.ShowDialog(GetWindow());
}; };
this.FindControl<Button>("OwnedWindow").Click += delegate this.Get<Button>("OwnedWindow").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Show(GetWindow()); window.Show(GetWindow());
}; };
this.FindControl<Button>("OwnedWindowNoTaskbar").Click += delegate this.Get<Button>("OwnedWindowNoTaskbar").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
@ -142,6 +151,7 @@ namespace ControlCatalog.Pages
private Window CreateSampleWindow() private Window CreateSampleWindow()
{ {
Button button; Button button;
Button dialogButton;
var window = new Window var window = new Window
{ {
@ -158,6 +168,12 @@ namespace ControlCatalog.Pages
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
Content = "Click to close", Content = "Click to close",
IsDefault = true IsDefault = true
}),
(dialogButton = new Button
{
HorizontalAlignment = HorizontalAlignment.Center,
Content = "Dialog",
IsDefault = false
}) })
} }
}, },
@ -165,11 +181,17 @@ namespace ControlCatalog.Pages
}; };
button.Click += (_, __) => window.Close(); button.Click += (_, __) => window.Close();
dialogButton.Click += (_, __) =>
{
var dialog = CreateSampleWindow();
dialog.Height = 200;
dialog.ShowDialog(window);
};
return window; return window;
} }
Window GetWindow() => (Window)this.VisualRoot; Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner");
private void InitializeComponent() private void InitializeComponent()
{ {

6
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.Pages
public DragAndDropPage() public DragAndDropPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_DropState = this.Find<TextBlock>("DropState"); _DropState = this.Get<TextBlock>("DropState");
int textCount = 0; int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text, SetupDnd("Text", d => d.Set(DataFormats.Text,
@ -26,8 +26,8 @@ namespace ControlCatalog.Pages
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
{ {
var dragMe = this.Find<Border>("DragMe" + suffix); var dragMe = this.Get<Border>("DragMe" + suffix);
var dragState = this.Find<TextBlock>("DragState"+suffix); var dragState = this.Get<TextBlock>("DragState"+suffix);
async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{ {

8
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@ -35,7 +35,7 @@ namespace ControlCatalog.Pages
private void SetXamlTexts() private void SetXamlTexts()
{ {
var bfxt = this.FindControl<TextBlock>("ButtonFlyoutXamlText"); var bfxt = this.Get<TextBlock>("ButtonFlyoutXamlText");
bfxt.Text = "<Button Content=\"Click me!\">\n" + bfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" + " <Button.Flyout>\n" +
" <Flyout>\n" + " <Flyout>\n" +
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
" </Flyout>\n" + " </Flyout>\n" +
" </Button.Flyout>\n</Button>"; " </Button.Flyout>\n</Button>";
var mfxt = this.FindControl<TextBlock>("MenuFlyoutXamlText"); var mfxt = this.Get<TextBlock>("MenuFlyoutXamlText");
mfxt.Text = "<Button Content=\"Click me!\">\n" + mfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" + " <Button.Flyout>\n" +
" <MenuFlyout>\n" + " <MenuFlyout>\n" +
@ -54,7 +54,7 @@ namespace ControlCatalog.Pages
" </MenuFlyout>\n" + " </MenuFlyout>\n" +
" </Button.Flyout>\n</Button>"; " </Button.Flyout>\n</Button>";
var afxt = this.FindControl<TextBlock>("AttachedFlyoutXamlText"); var afxt = this.Get<TextBlock>("AttachedFlyoutXamlText");
afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" + afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" +
" <FlyoutBase.AttachedFlyout>\n" + " <FlyoutBase.AttachedFlyout>\n" +
" <Flyout>\n" + " <Flyout>\n" +
@ -66,7 +66,7 @@ namespace ControlCatalog.Pages
"\n\n In DoubleTapped handler:\n" + "\n\n In DoubleTapped handler:\n" +
"FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);"; "FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);";
var sfxt = this.FindControl<TextBlock>("SharedFlyoutXamlText"); var sfxt = this.Get<TextBlock>("SharedFlyoutXamlText");
sfxt.Text = "Declare a flyout in Resources:\n" + sfxt.Text = "Declare a flyout in Resources:\n" +
"<Window.Resources>\n" + "<Window.Resources>\n" +
" <Flyout x:Key=\"SharedFlyout\">\n" + " <Flyout x:Key=\"SharedFlyout\">\n" +

13
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -17,9 +17,9 @@ namespace ControlCatalog.Pages
public ImagePage() public ImagePage()
{ {
InitializeComponent(); InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage"); _bitmapImage = this.Get<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage"); _drawingImage = this.Get<Image>("drawingImage");
_croppedImage = this.FindControl<Image>("croppedImage"); _croppedImage = this.Get<Image>("croppedImage");
} }
private void InitializeComponent() private void InitializeComponent()
@ -50,8 +50,11 @@ namespace ControlCatalog.Pages
if (_croppedImage != null) if (_croppedImage != null)
{ {
var comboxBox = (ComboBox)sender; var comboxBox = (ComboBox)sender;
var croppedBitmap = _croppedImage.Source as CroppedBitmap; if (_croppedImage.Source is CroppedBitmap croppedBitmap)
croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex); {
croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
}
} }
} }

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

@ -24,11 +24,11 @@ namespace ControlCatalog.Pages
public ItemsRepeaterPage() public ItemsRepeaterPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_repeater = this.FindControl<ItemsRepeater>("repeater"); _repeater = this.Get<ItemsRepeater>("repeater");
_scroller = this.FindControl<ScrollViewer>("scroller"); _scroller = this.Get<ScrollViewer>("scroller");
_scrollToLast = this.FindControl<Button>("scrollToLast"); _scrollToLast = this.Get<Button>("scrollToLast");
_scrollToRandom = this.FindControl<Button>("scrollToRandom"); _scrollToRandom = this.Get<Button>("scrollToRandom");
_scrollToSelected = this.FindControl<Button>("scrollToSelected"); _scrollToSelected = this.Get<Button>("scrollToSelected");
_repeater.PointerPressed += RepeaterClick; _repeater.PointerPressed += RepeaterClick;
_repeater.KeyDown += RepeaterOnKeyDown; _repeater.KeyDown += RepeaterOnKeyDown;
_scrollToLast.Click += scrollToLast_Click; _scrollToLast.Click += scrollToLast_Click;
@ -44,8 +44,10 @@ namespace ControlCatalog.Pages
public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e) public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
{ {
var item = (ItemsRepeaterPageViewModel.Item)e.DataContext; if (e.DataContext is ItemsRepeaterPageViewModel.Item item)
e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd"; {
e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
}
} }
private void LayoutChanged(object sender, SelectionChangedEventArgs e) private void LayoutChanged(object sender, SelectionChangedEventArgs e)
@ -115,7 +117,7 @@ namespace ControlCatalog.Pages
private void ScrollTo(int index) private void ScrollTo(int index)
{ {
System.Diagnostics.Debug.WriteLine("Scroll to " + index); System.Diagnostics.Debug.WriteLine("Scroll to " + index);
var layoutManager = ((TopLevel)VisualRoot).LayoutManager; var layoutManager = ((TopLevel)VisualRoot!).LayoutManager;
var element = _repeater.GetOrCreateElement(index); var element = _repeater.GetOrCreateElement(index);
layoutManager.ExecuteLayoutPass(); layoutManager.ExecuteLayoutPass();
element.BringIntoView(); element.BringIntoView();
@ -123,9 +125,11 @@ 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 ItemsRepeaterPageViewModel.Item; if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
_viewModel.SelectedItem = item; {
_selectedIndex = _viewModel.Items.IndexOf(item); _viewModel.SelectedItem = item;
_selectedIndex = _viewModel.Items.IndexOf(item);
}
} }
private void RepeaterOnKeyDown(object sender, KeyEventArgs e) private void RepeaterOnKeyDown(object sender, KeyEventArgs e)

2
samples/ControlCatalog/Pages/LabelsPage.axaml.cs

@ -7,7 +7,7 @@ namespace ControlCatalog.Pages
{ {
public class LabelsPage : UserControl public class LabelsPage : UserControl
{ {
private Person _person; private Person? _person;
public LabelsPage() public LabelsPage()
{ {

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

@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private MenuPageViewModel _model; private MenuPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)

68
samples/ControlCatalog/Pages/NativeEmbedPage.xaml

@ -0,0 +1,68 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:ControlCatalog.Pages"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.NativeEmbedPage">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Test">
<MenuItem Header="SubMenu">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock VerticalAlignment="Center">Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*"
RowDefinitions="*,5,*">
<Grid.Styles>
<Style Selector="DockPanel#FirstPanel:not(.mobile), DockPanel#SecondPanel:not(.mobile)">
<Setter Property="Grid.RowSpan" Value="3" />
</Style>
<Style Selector="DockPanel#SecondPanel:not(.mobile)">
<Setter Property="Grid.Column" Value="2" />
</Style>
<Style Selector="DockPanel#FirstPanel.mobile, DockPanel#SecondPanel.mobile">
<Setter Property="Grid.ColumnSpan" Value="3" />
</Style>
<Style Selector="DockPanel#SecondPanel.mobile">
<Setter Property="Grid.Row" Value="2" />
</Style>
</Grid.Styles>
<DockPanel x:Name="FirstPanel">
<CheckBox x:Name="firstVisible" DockPanel.Dock="Top"
IsChecked="True" Content="Visible" />
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
</DockPanel>
<GridSplitter Grid.Row="0" Grid.RowSpan="3" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Height="5" VerticalAlignment="Stretch" />
<DockPanel x:Name="SecondPanel">
<CheckBox x:Name="secondVisible" DockPanel.Dock="Top"
IsChecked="True" Content="Visible" />
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
</DockPanel>
</Grid>
</DockPanel>
</UserControl>

86
samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Interactivity;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Markup.Xaml;
using Avalonia;
namespace ControlCatalog.Pages
{
public class NativeEmbedPage : UserControl
{
public NativeEmbedPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public async void ShowPopupDelay(object sender, RoutedEventArgs args)
{
await Task.Delay(3000);
ShowPopup(sender, args);
}
public void ShowPopup(object sender, RoutedEventArgs args)
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty)
{
var isMobile = change.GetNewValue<Rect>().Width < 1200;
this.Find<DockPanel>("FirstPanel")!.Classes.Set("mobile", isMobile);
this.Find<DockPanel>("SecondPanel")!.Classes.Set("mobile", isMobile);
}
}
}
public class EmbedSample : NativeControlHost
{
public static INativeDemoControl? Implementation { get; set; }
static EmbedSample()
{
}
public bool IsSecond { get; set; }
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
return Implementation?.CreateControl(IsSecond, parent, () => base.CreateNativeControlCore(parent))
?? base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
base.DestroyNativeControlCore(control);
}
}
public interface INativeDemoControl
{
/// <param name="isSecond">Used to specify which control should be displayed as a demo</param>
/// <param name="parent"></param>
/// <param name="createDefault"></param>
IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault);
}
}

2
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -65,7 +65,7 @@
Margin="2" HorizontalAlignment="Center"/> Margin="2" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock> <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center" <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding DecimalValue}" VerticalAlignment="Center"
Margin="2" HorizontalAlignment="Center"/> Margin="2" HorizontalAlignment="Center"/>

10
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -28,16 +28,16 @@ namespace ControlCatalog.Pages
public class NumbersPageViewModel : ViewModelBase public class NumbersPageViewModel : ViewModelBase
{ {
private IList<FormatObject> _formats; private IList<FormatObject>? _formats;
private FormatObject _selectedFormat; private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations; private IList<Location>? _spinnerLocations;
private double _doubleValue; private double _doubleValue;
private decimal _decimalValue; private decimal _decimalValue;
public NumbersPageViewModel() public NumbersPageViewModel()
{ {
SelectedFormat = Formats.FirstOrDefault(); _selectedFormat = Formats.FirstOrDefault();
} }
public double DoubleValue public double DoubleValue
@ -103,7 +103,7 @@ namespace ControlCatalog.Pages
public class FormatObject public class FormatObject
{ {
public string Value { get; set; } public string? Value { get; set; }
public string Name { get; set; } public string? Name { get; set; }
} }
} }

2
samples/ControlCatalog/Pages/ScreenPage.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
Window w = (Window)VisualRoot; Window w = (Window)VisualRoot!;
w.PositionChanged += (sender, args) => InvalidateVisual(); w.PositionChanged += (sender, args) => InvalidateVisual();
} }

12
samples/ControlCatalog/Pages/TabControlPage.xaml.cs

@ -52,7 +52,7 @@ namespace ControlCatalog.Pages
private IBitmap LoadBitmap(string uri) private IBitmap LoadBitmap(string uri)
{ {
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>(); var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
return new Bitmap(assets.Open(new Uri(uri))); return new Bitmap(assets.Open(new Uri(uri)));
} }
@ -60,7 +60,7 @@ namespace ControlCatalog.Pages
{ {
private Dock _tabPlacement; private Dock _tabPlacement;
public TabItemViewModel[] Tabs { get; set; } public TabItemViewModel[]? Tabs { get; set; }
public Dock TabPlacement public Dock TabPlacement
{ {
@ -71,10 +71,10 @@ namespace ControlCatalog.Pages
private class TabItemViewModel private class TabItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public string Text { get; set; } public string? Text { get; set; }
public IBitmap Image { get; set; } public IBitmap? Image { get; set; }
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
} }
} }
} }

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

@ -38,7 +38,7 @@ namespace ControlCatalog.Pages
private class TabStripItemViewModel private class TabStripItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
} }
} }

2
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
{ {
ExitCommand = MiniCommand.Create(() => ExitCommand = MiniCommand.Create(() =>
{ {
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{ {
lifetime.Shutdown(); lifetime.Shutdown();
} }

2
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -9,7 +9,7 @@ namespace ControlCatalog.ViewModels
{ {
public class ContextPageViewModel public class ContextPageViewModel
{ {
public Control View { get; set; } public Control? View { get; set; }
public ContextPageViewModel() public ContextPageViewModel()
{ {
OpenCommand = MiniCommand.CreateFromTask(Open); OpenCommand = MiniCommand.CreateFromTask(Open);

2
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
.Select(x => new StandardCursorModel(x)) .Select(x => new StandardCursorModel(x))
.ToList(); .ToList();
var loader = AvaloniaLocator.Current.GetService<IAssetLoader>(); var loader = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
var bitmap = new Bitmap(s); var bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));

19
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.ViewModels
public ItemsRepeaterPageViewModel() public ItemsRepeaterPageViewModel()
{ {
Items = CreateItems(); _items = CreateItems();
} }
public ObservableCollection<Item> Items public ObservableCollection<Item> Items
@ -23,12 +23,12 @@ namespace ControlCatalog.ViewModels
set => this.RaiseAndSetIfChanged(ref _items, value); set => this.RaiseAndSetIfChanged(ref _items, value);
} }
public Item 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(index + 1) { Text = $"New Item {_newItemIndex++}" }); Items.Insert(index + 1, new Item(index + 1, $"New Item {_newItemIndex++}"));
} }
public void RemoveItem() public void RemoveItem()
@ -66,19 +66,20 @@ namespace ControlCatalog.ViewModels
_newGenerationIndex++; _newGenerationIndex++;
return new ObservableCollection<Item>( return new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => new Item(i) Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
{
Text = $"Item {i.ToString()} {suffix}"
}));
} }
public class Item : ViewModelBase public class Item : ViewModelBase
{ {
private double _height = double.NaN; private double _height = double.NaN;
public Item(int index) => Index = index; public Item(int index, string text)
{
Index = index;
Text = text;
}
public int Index { get; } public int Index { get; }
public string Text { get; set; } public string Text { get; }
public double Height public double Height
{ {

13
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -16,11 +16,11 @@ namespace ControlCatalog.ViewModels
private bool _isMenuItemChecked = true; private bool _isMenuItemChecked = true;
private WindowState _windowState; private WindowState _windowState;
private WindowState[] _windowStates; private WindowState[] _windowStates = Array.Empty<WindowState>();
private int _transparencyLevel; private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
private bool _extendClientAreaEnabled; private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled; private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled; private bool _preferSystemChromeEnabled;
private double _titleBarHeight; private double _titleBarHeight;
@ -47,14 +47,15 @@ namespace ControlCatalog.ViewModels
{ {
var dialog = new AboutAvaloniaDialog(); var dialog = new AboutAvaloniaDialog();
var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; if ((App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow is { } mainWindow)
{
await dialog.ShowDialog(mainWindow); await dialog.ShowDialog(mainWindow);
}
}); });
ExitCommand = MiniCommand.Create(() => ExitCommand = MiniCommand.Create(() =>
{ {
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); (App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
}); });
ToggleMenuItemCheckedCommand = MiniCommand.Create(() => ToggleMenuItemCheckedCommand = MiniCommand.Create(() =>

8
samples/ControlCatalog/ViewModels/MenuItemViewModel.cs

@ -5,9 +5,9 @@ namespace ControlCatalog.ViewModels
{ {
public class MenuItemViewModel public class MenuItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public ICommand Command { get; set; } public ICommand? Command { get; set; }
public object CommandParameter { get; set; } public object? CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; } public IList<MenuItemViewModel>? Items { get; set; }
} }
} }

2
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
{ {
public class MenuPageViewModel public class MenuPageViewModel
{ {
public Control View { get; set; } public Control? View { get; set; }
public MenuPageViewModel() public MenuPageViewModel()
{ {
OpenCommand = MiniCommand.CreateFromTask(Open); OpenCommand = MiniCommand.CreateFromTask(Open);

4
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

@ -19,8 +19,8 @@ namespace ControlCatalog.ViewModels
}); });
} }
public string Title { get; set; } public string? Title { get; set; }
public string Message { get; set; } public string? Message { get; set; }
public MiniCommand YesCommand { get; } public MiniCommand YesCommand { get; }

16
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels
{ {
public TransitioningContentControlPageViewModel() public TransitioningContentControlPageViewModel()
{ {
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>(); var assetLoader = AvaloniaLocator.Current?.GetService<IAssetLoader>()!;
var images = new string[] var images = new string[]
{ {
@ -36,8 +36,8 @@ namespace ControlCatalog.ViewModels
SetupTransitions(); SetupTransitions();
SelectedTransition = PageTransitions[1]; _SelectedTransition = PageTransitions[1];
SelectedImage = Images[0]; _SelectedImage = Images[0];
} }
public List<PageTransition> PageTransitions { get; } = new List<PageTransition>(); public List<PageTransition> PageTransitions { get; } = new List<PageTransition>();
@ -45,12 +45,12 @@ namespace ControlCatalog.ViewModels
public List<Bitmap> Images { get; } = new List<Bitmap>(); public List<Bitmap> Images { get; } = new List<Bitmap>();
private Bitmap? _SelectedImage; private Bitmap _SelectedImage;
/// <summary> /// <summary>
/// Gets or Sets the selected image /// Gets or Sets the selected image
/// </summary> /// </summary>
public Bitmap? SelectedImage public Bitmap SelectedImage
{ {
get { return _SelectedImage; } get { return _SelectedImage; }
set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); } set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); }
@ -160,12 +160,12 @@ namespace ControlCatalog.ViewModels
public string DisplayTitle { get; } public string DisplayTitle { get; }
private IPageTransition _Transition; private IPageTransition? _Transition;
/// <summary> /// <summary>
/// Gets or sets the transition /// Gets or sets the transition
/// </summary> /// </summary>
public IPageTransition Transition public IPageTransition? Transition
{ {
get { return _Transition; } get { return _Transition; }
set { this.RaiseAndSetIfChanged(ref _Transition, value); } set { this.RaiseAndSetIfChanged(ref _Transition, value); }
@ -201,7 +201,7 @@ namespace ControlCatalog.ViewModels
/// </summary> /// </summary>
public TimeSpan Duration { get; set; } public TimeSpan Duration { get; set; }
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {

4
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -92,7 +92,7 @@ namespace ControlCatalog.ViewModels
public class Node public class Node
{ {
private ObservableCollection<Node> _children; private ObservableCollection<Node>? _children;
private int _childIndex = 10; private int _childIndex = 10;
public Node() public Node()
@ -106,7 +106,7 @@ namespace ControlCatalog.ViewModels
Header = parent.Header + ' ' + index; Header = parent.Header + ' ' + index;
} }
public Node Parent { get; } public Node? Parent { get; }
public string Header { get; } public string Header { get; }
public bool AreChildrenInitialized => _children != null; public bool AreChildrenInitialized => _children != null;
public ObservableCollection<Node> Children => _children ??= CreateChildren(); public ObservableCollection<Node> Children => _children ??= CreateChildren();

8
samples/interop/NativeEmbedSample/App.xaml

@ -1,8 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
</Application.Styles>
</Application>

22
samples/interop/NativeEmbedSample/App.xaml.cs

@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
desktopLifetime.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
}
}

121
samples/interop/NativeEmbedSample/EmbedSample.cs

@ -1,121 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.WebKit;
using Encoding = SharpDX.Text.Encoding;
namespace NativeEmbedSample
{
public class EmbedSample : NativeControlHost
{
public bool IsSecond { get; set; }
private Process _mplayer;
IPlatformHandle CreateLinux(IPlatformHandle parent)
{
if (IsSecond)
{
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
if (chooser != null)
return chooser;
}
var control = base.CreateNativeControlCore(parent);
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
"..",
"nodes.mp4"));
_mplayer = Process.Start(new ProcessStartInfo("mplayer",
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
{
UseShellExecute = false,
});
return control;
}
void DestroyLinux(IPlatformHandle handle)
{
_mplayer?.Kill();
_mplayer = null;
base.DestroyNativeControlCore(handle);
}
private const string RichText =
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
{\*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
}";
IPlatformHandle CreateWin32(IPlatformHandle parent)
{
WinApi.LoadLibrary("Msftedit.dll");
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
@"Rich Edit",
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : "");
var bytes = Encoding.UTF8.GetBytes(text);
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
return new PlatformHandle(handle, "HWND");
}
void DestroyWin32(IPlatformHandle handle)
{
WinApi.DestroyWindow(handle.Handle);
}
IPlatformHandle CreateOSX(IPlatformHandle parent)
{
// Note: We are using MonoMac for example purposes
// It shouldn't be used in production apps
MacHelper.EnsureInitialized();
var webView = new WebView();
Dispatcher.UIThread.Post(() =>
{
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
IsSecond ? "https://bing.com": "https://google.com/")));
});
return new MacOSViewHandle(webView);
}
void DestroyOSX(IPlatformHandle handle)
{
((MacOSViewHandle)handle).Dispose();
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return CreateLinux(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return CreateWin32(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return CreateOSX(parent);
return base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
DestroyLinux(control);
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
DestroyWin32(control);
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
DestroyOSX(control);
else
base.DestroyNativeControlCore(control);
}
}
}

58
samples/interop/NativeEmbedSample/GtkHelper.cs

@ -1,58 +0,0 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.NativeDialogs.Gtk;
using static Avalonia.X11.NativeDialogs.Glib;
namespace NativeEmbedSample
{
public class GtkHelper
{
private static Task<bool> s_gtkTask;
class FileChooser : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _widget;
public FileChooser(IntPtr widget, IntPtr xid)
{
_widget = widget;
Handle = xid;
}
public IntPtr Handle { get; }
public string HandleDescriptor => "XID";
public void Destroy()
{
RunOnGlibThread(() =>
{
gtk_widget_destroy(_widget);
return 0;
}).Wait();
}
}
public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid)
{
if (s_gtkTask == null)
s_gtkTask = StartGtk();
if (!s_gtkTask.Result)
return null;
return RunOnGlibThread(() =>
{
using (var title = new Utf8Buffer("Embedded"))
{
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
IntPtr.Zero);
gtk_widget_realize(widget);
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
gtk_window_present(widget);
return new FileChooser(widget, xid);
}
}).Result;
}
}
}

39
samples/interop/NativeEmbedSample/MacHelper.cs

@ -1,39 +0,0 @@
using System;
using Avalonia.Platform;
using MonoMac.AppKit;
namespace NativeEmbedSample
{
public class MacHelper
{
private static bool _isInitialized;
public static void EnsureInitialized()
{
if (_isInitialized)
return;
_isInitialized = true;
NSApplication.Init();
}
}
class MacOSViewHandle : IPlatformHandle, IDisposable
{
private NSView _view;
public MacOSViewHandle(NSView view)
{
_view = view;
}
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
public string HandleDescriptor => "NSView";
public void Dispose()
{
_view.Dispose();
_view = null;
}
}
}

52
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -1,52 +0,0 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Width="1024" Height="800"
Title="Native embedding sample"
xmlns:local="clr-namespace:NativeEmbedSample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.MainWindow">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Test">
<MenuItem Header="SubMenu">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock>Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="firstVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
</DockPanel>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<DockPanel Grid.Column="2">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="secondVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
</DockPanel>
</Grid>
</DockPanel>
</Window>

36
samples/interop/NativeEmbedSample/MainWindow.xaml.cs

@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class MainWindow : Window
{
public MainWindow()
{
AvaloniaXamlLoader.Load(this);
this.AttachDevTools();
}
public async void ShowPopupDelay(object sender, RoutedEventArgs args)
{
await Task.Delay(3000);
ShowPopup(sender, args);
}
public void ShowPopup(object sender, RoutedEventArgs args)
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}
}
}

31
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<None Remove="nodes.mp4" />
<Content Include="nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
</ItemGroup>
<Import Project="..\..\..\build\SampleApp.props" />
<Import Project="..\..\..\build\BuildTargets.targets" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project>

17
samples/interop/NativeEmbedSample/Program.cs

@ -1,17 +0,0 @@
using Avalonia;
namespace NativeEmbedSample
{
class Program
{
static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.With(new AvaloniaNativePlatformOptions()
{
})
.UsePlatformDetect();
}
}

74
samples/interop/NativeEmbedSample/WinApi.cs

@ -1,74 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace NativeEmbedSample
{
public unsafe class WinApi
{
public enum CommonControls : uint
{
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
ICC_UPDOWN_CLASS = 0x00000010, // updown
ICC_PROGRESS_CLASS = 0x00000020, // progress
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
ICC_ANIMATE_CLASS = 0x00000080, // animate
ICC_WIN95_CLASSES = 0x000000FF,
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
ICC_USEREX_CLASSES = 0x00000200, // comboex
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
ICC_INTERNET_CLASSES = 0x00000800,
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
ICC_STANDARD_CLASSES = 0x00004000,
ICC_LINK_CLASS = 0x00008000
}
[StructLayout(LayoutKind.Sequential)]
public struct INITCOMMONCONTROLSEX
{
public int dwSize;
public uint dwICC;
}
[DllImport("Comctl32.dll")]
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[StructLayout(LayoutKind.Sequential)]
public struct SETTEXTEX
{
public uint Flags;
public uint Codepage;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
}
}

32
src/Android/Avalonia.Android/AndroidViewControlHandle.cs

@ -0,0 +1,32 @@
#nullable enable
using System;
using Android.Views;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android
{
public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle
{
internal const string AndroidDescriptor = "JavaObjectHandle";
public AndroidViewControlHandle(View view)
{
View = view;
}
public View View { get; }
public string HandleDescriptor => AndroidDescriptor;
IntPtr IPlatformHandle.Handle => View.Handle;
public void Destroy()
{
View?.Dispose();
}
}
}

9
src/Android/Avalonia.Android/AvaloniaView.cs

@ -15,13 +15,12 @@ namespace Avalonia.Android
private EmbeddableControlRoot _root; private EmbeddableControlRoot _root;
private readonly ViewImpl _view; private readonly ViewImpl _view;
private IDisposable? _timerSubscription; private IDisposable _timerSubscription;
public AvaloniaView(Context context) : base(context) public AvaloniaView(Context context) : base(context)
{ {
_view = new ViewImpl(context); _view = new ViewImpl(this);
AddView(_view.View); AddView(_view.View);
} }
internal void Prepare () internal void Prepare ()
@ -30,6 +29,8 @@ namespace Avalonia.Android
_root.Prepare(); _root.Prepare();
} }
internal TopLevelImpl TopLevelImpl => _view;
public object Content public object Content
{ {
get { return _root.Content; } get { return _root.Content; }
@ -73,7 +74,7 @@ namespace Avalonia.Android
class ViewImpl : TopLevelImpl class ViewImpl : TopLevelImpl
{ {
public ViewImpl(Context context) : base(context) public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
{ {
View.Focusable = true; View.Focusable = true;
View.FocusChange += ViewImpl_FocusChange; View.FocusChange += ViewImpl_FocusChange;

139
src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs

@ -0,0 +1,139 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Android.Views;
using Android.Widget;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android.Platform
{
internal class AndroidNativeControlHostImpl : INativeControlHostImpl
{
private readonly AvaloniaView _avaloniaView;
public AndroidNativeControlHostImpl(AvaloniaView avaloniaView)
{
_avaloniaView = avaloniaView;
}
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
{
return new AndroidViewControlHandle(new FrameLayout(_avaloniaView.Context!));
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
{
var parent = new AndroidViewControlHandle(_avaloniaView);
AndroidNativeControlAttachment? attachment = null;
try
{
var child = create(parent);
// It has to be assigned to the variable before property setter is called so we dispose it on exception
#pragma warning disable IDE0017 // Simplify object initialization
attachment = new AndroidNativeControlAttachment(child);
#pragma warning restore IDE0017 // Simplify object initialization
attachment.AttachedTo = this;
return attachment;
}
catch
{
attachment?.Dispose();
throw;
}
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
{
return new AndroidNativeControlAttachment(handle)
{
AttachedTo = this
};
}
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor;
private class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment
{
private View? _view;
private AndroidNativeControlHostImpl? _attachedTo;
public AndroidNativeControlAttachment(IPlatformHandle child)
{
_view = (child as AndroidViewControlHandle)?.View
?? Java.Lang.Object.GetObject<View>(child.Handle, global::Android.Runtime.JniHandleOwnership.DoNotTransfer);
}
[MemberNotNull(nameof(_view))]
private void CheckDisposed()
{
if (_view == null)
throw new ObjectDisposedException(nameof(AndroidNativeControlAttachment));
}
public void Dispose()
{
if (_view != null && _attachedTo?._avaloniaView is ViewGroup parent)
{
parent.RemoveView(_view);
}
_attachedTo = null;
_view?.Dispose();
_view = null;
}
public INativeControlHostImpl? AttachedTo
{
get => _attachedTo;
set
{
CheckDisposed();
var oldAttachedTo = _attachedTo;
_attachedTo = (AndroidNativeControlHostImpl?)value;
if (_attachedTo == null)
{
oldAttachedTo?._avaloniaView.RemoveView(_view);
}
else
{
_attachedTo._avaloniaView.AddView(_view);
}
}
}
public bool IsCompatibleWith(INativeControlHostImpl host) => host is AndroidNativeControlHostImpl;
public void HideWithSize(Size size)
{
CheckDisposed();
if (_attachedTo == null)
return;
size *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
_view.Visibility = ViewStates.Gone;
_view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
_view.RequestLayout();
}
public void ShowInBounds(Rect bounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
bounds *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
_view.Visibility = ViewStates.Visible;
_view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height))
{
LeftMargin = (int)bounds.X,
TopMargin = (int)bounds.Y
};
_view.RequestLayout();
}
}
}
}

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

@ -20,7 +20,7 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
{ {
private readonly IGlPlatformSurface _gl; private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer; private readonly IFramebufferPlatformSurface _framebuffer;
@ -30,9 +30,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly ITextInputMethodImpl _textInputMethod; private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view; private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false) public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
{ {
_view = new ViewImpl(context, this, placeOnTop); _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view); _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
@ -44,6 +44,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -222,6 +224,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public ITextInputMethodImpl TextInputMethod => _textInputMethod; public ITextInputMethodImpl TextInputMethod => _textInputMethod;
public INativeControlHostImpl NativeControlHost { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -30,7 +30,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return DispatchKeyEventInternal(e, out callBase); return DispatchKeyEventInternal(e, out callBase);
} }
string? UnicodeTextInput(KeyEvent keyEvent) string UnicodeTextInput(KeyEvent keyEvent)
{ {
return keyEvent.Action == KeyEventActions.Multiple return keyEvent.Action == KeyEventActions.Multiple
&& keyEvent.RepeatCount == 0 && keyEvent.RepeatCount == 0

9
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -33,7 +33,14 @@ namespace Avalonia.Data.Converters
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)
{ {
return new MethodToCommandConverter(d); if (d.Method.IsPrivate == false)
{
return new MethodToCommandConverter(d);
}
else
{
return new BindingNotification(new InvalidCastException("You can't bind to private methods!"), BindingErrorType.Error);
}
} }
if (TypeUtilities.TryConvert(targetType, value, culture, out var result)) if (TypeUtilities.TryConvert(targetType, value, culture, out var result))

76
src/Avalonia.Base/Matrix.cs

@ -9,14 +9,14 @@ namespace Avalonia
/// <summary> /// <summary>
/// A 3x3 matrix. /// A 3x3 matrix.
/// </summary> /// </summary>
/// <remakrs>Matrix layout: /// <remarks>Matrix layout:
/// | 1st col | 2nd col | 3r col | /// | 1st col | 2nd col | 3r col |
/// 1st row | scaleX | skrewY | persX | /// 1st row | scaleX | skewY | perspX |
/// 2nd row | skrewX | scaleY | persY | /// 2nd row | skewX | scaleY | perspY |
/// 3rd row | transX | transY | persZ | /// 3rd row | transX | transY | perspZ |
/// ///
/// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and perspp0/tranX are swapped). /// Note: Skia.SkMatrix uses a transposed layout (where for example skewX/skewY and persp0/transX are swapped).
/// </remakrs> /// </remarks>
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
@ -36,18 +36,18 @@ namespace Avalonia
/// Initializes a new instance of the <see cref="Matrix"/> struct (equivalent to a 2x3 Matrix without perspective). /// Initializes a new instance of the <see cref="Matrix"/> struct (equivalent to a 2x3 Matrix without perspective).
/// </summary> /// </summary>
/// <param name="scaleX">The first element of the first row.</param> /// <param name="scaleX">The first element of the first row.</param>
/// <param name="skrewY">The second element of the first row.</param> /// <param name="skewY">The second element of the first row.</param>
/// <param name="skrewX">The first element of the second row.</param> /// <param name="skewX">The first element of the second row.</param>
/// <param name="scaleY">The second element of the second row.</param> /// <param name="scaleY">The second element of the second row.</param>
/// <param name="offsetX">The first element of the third row.</param> /// <param name="offsetX">The first element of the third row.</param>
/// <param name="offsetY">The second element of the third row.</param> /// <param name="offsetY">The second element of the third row.</param>
public Matrix( public Matrix(
double scaleX, double scaleX,
double skrewY, double skewY,
double skrewX, double skewX,
double scaleY, double scaleY,
double offsetX, double offsetX,
double offsetY) : this( scaleX, skrewY, 0, skrewX, scaleY, 0, offsetX, offsetY, 1) double offsetY) : this( scaleX, skewY, 0, skewX, scaleY, 0, offsetX, offsetY, 1)
{ {
} }
@ -57,34 +57,34 @@ namespace Avalonia
/// Initializes a new instance of the <see cref="Matrix"/> struct. /// Initializes a new instance of the <see cref="Matrix"/> struct.
/// </summary> /// </summary>
/// <param name="scaleX">The first element of the first row.</param> /// <param name="scaleX">The first element of the first row.</param>
/// <param name="skrewY">The second element of the first row.</param> /// <param name="skewY">The second element of the first row.</param>
/// <param name="persX">The third element of the first row.</param> /// <param name="perspX">The third element of the first row.</param>
/// <param name="skrewX">The first element of the second row.</param> /// <param name="skewX">The first element of the second row.</param>
/// <param name="scaleY">The second element of the second row.</param> /// <param name="scaleY">The second element of the second row.</param>
/// <param name="persY">The third element of the second row.</param> /// <param name="perspY">The third element of the second row.</param>
/// <param name="offsetX">The first element of the third row.</param> /// <param name="offsetX">The first element of the third row.</param>
/// <param name="offsetY">The second element of the third row.</param> /// <param name="offsetY">The second element of the third row.</param>
/// <param name="persZ">The third element of the third row.</param> /// <param name="perspZ">The third element of the third row.</param>
public Matrix( public Matrix(
double scaleX, double scaleX,
double skrewY, double skewY,
double persX, double perspX,
double skrewX, double skewX,
double scaleY, double scaleY,
double persY, double perspY,
double offsetX, double offsetX,
double offsetY, double offsetY,
double persZ) double perspZ)
{ {
_m11 = scaleX; _m11 = scaleX;
_m12 = skrewY; _m12 = skewY;
_m13 = persX; _m13 = perspX;
_m21 = skrewX; _m21 = skewX;
_m22 = scaleY; _m22 = scaleY;
_m23 = persY; _m23 = perspY;
_m31 = offsetX; _m31 = offsetX;
_m32 = offsetY; _m32 = offsetY;
_m33 = persZ; _m33 = perspZ;
} }
/// <summary> /// <summary>
@ -111,17 +111,17 @@ namespace Avalonia
public double M11 => _m11; public double M11 => _m11;
/// <summary> /// <summary>
/// The second element of the first row (skrewY). /// The second element of the first row (skewY).
/// </summary> /// </summary>
public double M12 => _m12; public double M12 => _m12;
/// <summary> /// <summary>
/// The third element of the first row (persX: input x-axis perspective factor). /// The third element of the first row (perspX: input x-axis perspective factor).
/// </summary> /// </summary>
public double M13 => _m13; public double M13 => _m13;
/// <summary> /// <summary>
/// The first element of the second row (skrewX). /// The first element of the second row (skewX).
/// </summary> /// </summary>
public double M21 => _m21; public double M21 => _m21;
@ -131,7 +131,7 @@ namespace Avalonia
public double M22 => _m22; public double M22 => _m22;
/// <summary> /// <summary>
/// The third element of the second row (persY: input y-axis perspective factor). /// The third element of the second row (perspY: input y-axis perspective factor).
/// </summary> /// </summary>
public double M23 => _m23; public double M23 => _m23;
@ -146,7 +146,7 @@ namespace Avalonia
public double M32 => _m32; public double M32 => _m32;
/// <summary> /// <summary>
/// The third element of the third row (persZ: perspective scale factor). /// The third element of the third row (perspZ: perspective scale factor).
/// </summary> /// </summary>
public double M33 => _m33; public double M33 => _m33;
@ -450,13 +450,13 @@ namespace Avalonia
inverted = new Matrix( inverted = new Matrix(
(_m22 * _m33 - _m32 * _m23) * invdet, (_m22 * _m33 - _m32 * _m23) * invdet,
(_m13 * _m31 - _m12 * _m33) * invdet, (_m13 * _m32 - _m12 * _m33) * invdet,
(_m12 * _m23 - _m13 * _m22) * invdet, (_m12 * _m23 - _m13 * _m22) * invdet,
(_m23 * _m31 - _m21 * _m33) * invdet, (_m23 * _m31 - _m21 * _m33) * invdet,
(_m11 * _m33 - _m13 * _m31) * invdet, (_m11 * _m33 - _m13 * _m31) * invdet,
(_m21 * _m13 - _m11 * _m23) * invdet, (_m21 * _m13 - _m11 * _m23) * invdet,
(_m21 * _m32 - _m31 * _m22) * invdet, (_m21 * _m32 - _m31 * _m22) * invdet,
(_m21 * _m12 - _m11 * _m32) * invdet, (_m31 * _m12 - _m11 * _m32) * invdet,
(_m11 * _m22 - _m21 * _m12) * invdet (_m11 * _m22 - _m21 * _m12) * invdet
); );
@ -481,7 +481,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Parses a <see cref="Matrix"/> string. /// Parses a <see cref="Matrix"/> string.
/// </summary> /// </summary>
/// <param name="s">Six or nine comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY[, persX, persY, persZ]) that describe the new <see cref="Matrix"/></param> /// <param name="s">Six or nine comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY[, perspX, perspY, perspZ]) that describe the new <see cref="Matrix"/></param>
/// <returns>The <see cref="Matrix"/>.</returns> /// <returns>The <see cref="Matrix"/>.</returns>
public static Matrix Parse(string s) public static Matrix Parse(string s)
{ {
@ -497,11 +497,11 @@ namespace Avalonia
var v4 = tokenizer.ReadDouble(); var v4 = tokenizer.ReadDouble();
var v5 = tokenizer.ReadDouble(); var v5 = tokenizer.ReadDouble();
var v6 = tokenizer.ReadDouble(); var v6 = tokenizer.ReadDouble();
var pers = tokenizer.TryReadDouble(out var v7); var persp = tokenizer.TryReadDouble(out var v7);
pers = pers && tokenizer.TryReadDouble(out v8); persp = persp && tokenizer.TryReadDouble(out v8);
pers = pers && tokenizer.TryReadDouble(out v9); persp = persp && tokenizer.TryReadDouble(out v9);
if (pers) if (persp)
return new Matrix(v1, v2, v7, v3, v4, v8, v5, v6, v9); return new Matrix(v1, v2, v7, v3, v4, v8, v5, v6, v9);
else else
return new Matrix(v1, v2, v3, v4, v5, v6); return new Matrix(v1, v2, v3, v4, v5, v6);

3
src/Avalonia.Base/Media/GeometryDrawing.cs

@ -68,7 +68,8 @@ namespace Avalonia.Media
public override Rect GetBounds() public override Rect GetBounds()
{ {
return Geometry?.GetRenderBounds(s_boundsPen) ?? Rect.Empty; IPen pen = Pen ?? s_boundsPen;
return Geometry?.GetRenderBounds(pen) ?? Rect.Empty;
} }
} }
} }

11
src/Avalonia.Base/Media/TextFormatting/TextBounds.cs

@ -10,20 +10,27 @@ namespace Avalonia.Media.TextFormatting
/// <summary> /// <summary>
/// Constructing TextBounds object /// Constructing TextBounds object
/// </summary> /// </summary>
internal TextBounds(Rect bounds, FlowDirection flowDirection) internal TextBounds(Rect bounds, FlowDirection flowDirection, IList<TextRunBounds> runBounds)
{ {
Rectangle = bounds; Rectangle = bounds;
FlowDirection = flowDirection; FlowDirection = flowDirection;
TextRunBounds = runBounds;
} }
/// <summary> /// <summary>
/// Bounds rectangle /// Bounds rectangle
/// </summary> /// </summary>
public Rect Rectangle { get; } public Rect Rectangle { get; internal set; }
/// <summary> /// <summary>
/// Text flow direction inside the boundary rectangle /// Text flow direction inside the boundary rectangle
/// </summary> /// </summary>
public FlowDirection FlowDirection { get; } public FlowDirection FlowDirection { get; }
/// <summary>
/// Get a list of run bounding rectangles
/// </summary>
/// <returns>Array of text run bounds</returns>
public IList<TextRunBounds> TextRunBounds { get; }
} }
} }

29
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -230,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
{ {
//Current line isn't covered. //Current line isn't covered.
if (textLine.FirstTextSourceIndex + textLine.Length <= start) if (textLine.FirstTextSourceIndex + textLine.Length < start)
{ {
currentY += textLine.Height; currentY += textLine.Height;
@ -239,18 +239,27 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length); var textBounds = textLine.GetTextBounds(start, length);
foreach (var bounds in textBounds) if(textBounds.Count > 0)
{ {
Rect? last = result.Count > 0 ? result[result.Count - 1] : null; foreach (var bounds in textBounds)
if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
{ {
result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width); Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
{
result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
}
else
{
result.Add(bounds.Rectangle.WithY(currentY));
}
foreach (var runBounds in bounds.TextRunBounds)
{
start += runBounds.Length;
length -= runBounds.Length;
}
} }
else
{
result.Add(bounds.Rectangle.WithY(currentY));
}
} }
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length) if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)

376
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -184,6 +184,10 @@ namespace Avalonia.Media.TextFormatting
{ {
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
break; break;
} }
default: default:
@ -215,9 +219,11 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{ {
var characterIndex = characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0); var isTrailingHit = characterHit.TrailingLength > 0;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start; var currentDistance = Start;
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var remainingLength = characterIndex - FirstTextSourceIndex;
GlyphRun? lastRun = null; GlyphRun? lastRun = null;
@ -242,8 +248,10 @@ namespace Avalonia.Media.TextFormatting
} }
//Look for a hit in within the current run //Look for a hit in within the current run
if (characterIndex >= textRun.Text.Start && characterIndex <= textRun.Text.Start + textRun.Text.Length) if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
{ {
characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
var distance = currentRun.GetDistanceFromCharacterHit(characterHit); var distance = currentRun.GetDistanceFromCharacterHit(characterHit);
return currentDistance + distance; return currentDistance + distance;
@ -254,28 +262,27 @@ namespace Avalonia.Media.TextFormatting
{ {
if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
{ {
if (characterIndex <= textRun.Text.Start) if (characterIndex <= currentPosition)
{ {
return currentDistance; return currentDistance;
} }
} }
else else
{ {
if (characterIndex == textRun.Text.Start) if (characterIndex == currentPosition)
{ {
return currentDistance; return currentDistance;
} }
} }
if (characterIndex == textRun.Text.Start + textRun.Text.Length && if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
characterHit.TrailingLength > 0)
{ {
return currentDistance + currentRun.Size.Width; return currentDistance + currentRun.Size.Width;
} }
} }
else else
{ {
if (characterIndex == textRun.Text.Start) if (characterIndex == currentPosition)
{ {
return currentDistance + currentRun.Size.Width; return currentDistance + currentRun.Size.Width;
} }
@ -286,20 +293,24 @@ namespace Avalonia.Media.TextFormatting
if (nextRun != null) if (nextRun != null)
{ {
if (characterHit.FirstCharacterIndex == textRun.Text.End && if (nextRun.ShapedBuffer.IsLeftToRight)
nextRun.ShapedBuffer.IsLeftToRight)
{ {
return currentDistance; if (characterIndex == currentPosition + textRun.Text.Length)
{
return currentDistance;
}
} }
else
if (characterIndex > textRun.Text.End && nextRun.Text.End < textRun.Text.End)
{ {
return currentDistance; if (currentPosition + nextRun.Text.Length == characterIndex)
{
return currentDistance;
}
} }
} }
else else
{ {
if (characterIndex > textRun.Text.End) if (characterIndex > currentPosition + textRun.Text.Length)
{ {
return currentDistance; return currentDistance;
} }
@ -329,6 +340,12 @@ namespace Avalonia.Media.TextFormatting
//No hit hit found so we add the full width //No hit hit found so we add the full width
currentDistance += textRun.Size.Width; currentDistance += textRun.Size.Width;
currentPosition += textRun.TextSourceLength; currentPosition += textRun.TextSourceLength;
remainingLength -= textRun.TextSourceLength;
if (remainingLength <= 0)
{
break;
}
} }
return currentDistance; return currentDistance;
@ -394,210 +411,299 @@ namespace Avalonia.Media.TextFormatting
return GetPreviousCaretCharacterHit(characterHit); return GetPreviousCaretCharacterHit(characterHit);
} }
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength) private IReadOnlyList<TextBounds> GetTextBoundsLeftToRight(int firstTextSourceIndex, int textLength)
{ {
if (firstTextSourceCharacterIndex + textLength <= FirstTextSourceIndex) var characterIndex = firstTextSourceIndex + textLength;
{
return Array.Empty<TextBounds>();
}
var result = new List<TextBounds>(TextRuns.Count); var result = new List<TextBounds>(TextRuns.Count);
var lastDirection = _flowDirection; var lastDirection = FlowDirection.LeftToRight;
var currentDirection = lastDirection; var currentDirection = lastDirection;
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var currentRect = Rect.Empty; var remainingLength = textLength;
var startX = Start; var startX = Start;
double currentWidth = 0;
var currentRect = Rect.Empty;
//A portion of the line is covered.
for (var index = 0; index < TextRuns.Count; index++) for (var index = 0; index < TextRuns.Count; index++)
{ {
var currentRun = TextRuns[index] as DrawableTextRun; if (TextRuns[index] is not DrawableTextRun currentRun)
if (currentRun is null)
{ {
continue; continue;
} }
TextRun? nextRun = null; if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
if (index + 1 < TextRuns.Count)
{ {
nextRun = TextRuns[index + 1]; startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
} }
if (nextRun != null) var characterLength = 0;
var endX = startX;
if (currentRun is ShapedTextCharacters currentShapedRun)
{ {
switch (nextRun) var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
{
case ShapedTextCharacters when currentRun is ShapedTextCharacters:
{
if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
{
goto skip;
}
if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength) currentPosition += offset;
{
goto skip;
}
if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex) var startIndex = currentRun.Text.Start + offset;
{
goto skip;
}
if (currentRun.Text.End < firstTextSourceCharacterIndex) var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
{ currentShapedRun.ShapedBuffer.IsLeftToRight ?
goto skip; new CharacterHit(startIndex + remainingLength) :
} new CharacterHit(startIndex));
goto noop; endX += endOffset;
}
default: var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
{ currentShapedRun.ShapedBuffer.IsLeftToRight ?
goto noop; new CharacterHit(startIndex) :
} new CharacterHit(startIndex + remainingLength));
}
startX += startOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
skip: currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
FlowDirection.LeftToRight :
FlowDirection.RightToLeft;
}
else
{
if (currentPosition < firstTextSourceIndex)
{ {
startX += currentRun.Size.Width; startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
} }
continue; if (currentPosition + currentRun.TextSourceLength <= characterIndex)
noop:
{ {
endX += currentRun.Size.Width;
characterLength = currentRun.TextSourceLength;
} }
} }
var endX = startX; if (endX < startX)
var endOffset = 0d; {
(endX, startX) = (startX, endX);
}
switch (currentRun) //Lines that only contain a linebreak need to be covered here
if(characterLength == 0)
{ {
case ShapedTextCharacters shapedRun: characterLength = NewLineLength;
{ }
endOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
shapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(firstTextSourceCharacterIndex + textLength) :
new CharacterHit(firstTextSourceCharacterIndex));
endX += endOffset; var runwidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
var startOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit( if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
shapedRun.ShapedBuffer.IsLeftToRight ? {
new CharacterHit(firstTextSourceCharacterIndex) : currentRect = currentRect.WithWidth(currentWidth + runwidth);
new CharacterHit(firstTextSourceCharacterIndex + textLength));
startX += startOffset; var textBounds = result[result.Count - 1];
var characterHit = shapedRun.GlyphRun.IsLeftToRight ? textBounds.Rectangle = currentRect;
shapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) :
shapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; textBounds.TextRunBounds.Add(currentRunBounds);
}
else
{
currentRect = currentRunBounds.Rectangle;
currentDirection = shapedRun.ShapedBuffer.IsLeftToRight ? result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
FlowDirection.LeftToRight : }
FlowDirection.RightToLeft;
if (nextRun is ShapedTextCharacters nextShaped) currentWidth += runwidth;
{ currentPosition += characterLength;
if (shapedRun.ShapedBuffer.IsLeftToRight == nextShaped.ShapedBuffer.IsLeftToRight)
{
endOffset = nextShaped.GlyphRun.GetDistanceFromCharacterHit(
nextShaped.ShapedBuffer.IsLeftToRight ?
new CharacterHit(firstTextSourceCharacterIndex + textLength) :
new CharacterHit(firstTextSourceCharacterIndex));
index++; if (currentDirection == FlowDirection.LeftToRight)
{
if (currentPosition > characterIndex)
{
break;
}
}
else
{
if (currentPosition <= firstTextSourceIndex)
{
break;
}
}
endX += endOffset; startX = endX;
lastDirection = currentDirection;
remainingLength -= characterLength;
currentRun = nextShaped; if (remainingLength <= 0)
{
break;
}
}
if (nextShaped.ShapedBuffer.IsLeftToRight) return result;
{ }
characterHit = nextShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; private IReadOnlyList<TextBounds> GetTextBoundsRightToLeft(int firstTextSourceIndex, int textLength)
} {
} var characterIndex = firstTextSourceIndex + textLength;
}
break; var result = new List<TextBounds>(TextRuns.Count);
} var lastDirection = FlowDirection.LeftToRight;
default: var currentDirection = lastDirection;
{
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex + textLength)
{
endX += currentRun.Size.Width;
}
if (currentPosition < firstTextSourceCharacterIndex) var currentPosition = FirstTextSourceIndex;
{ var remainingLength = textLength;
startX += currentRun.Size.Width;
}
currentPosition += currentRun.TextSourceLength; var startX = Start + WidthIncludingTrailingWhitespace;
double currentWidth = 0;
var currentRect = Rect.Empty;
break; for (var index = TextRuns.Count - 1; index >= 0; index--)
} {
if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
} }
if (endX < startX) if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{ {
(endX, startX) = (startX, endX); startX -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
} }
var width = endX - startX; var characterLength = 0;
var endX = startX;
if (!MathUtilities.IsZero(width)) if (currentRun is ShapedTextCharacters currentShapedRun)
{ {
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
{
currentRect = currentRect.WithWidth(currentRect.Width + width); currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
endX += endOffset - currentShapedRun.Size.Width;
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
var textBounds = new TextBounds(currentRect, currentDirection); startX += startOffset - currentShapedRun.Size.Width;
result[result.Count - 1] = textBounds; var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
FlowDirection.LeftToRight :
FlowDirection.RightToLeft;
}
else
{
if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
endX -= currentRun.Size.Width;
} }
else
if (currentPosition < firstTextSourceIndex)
{ {
startX -= currentRun.Size.Width;
characterLength = currentRun.TextSourceLength;
}
}
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
currentRect = new Rect(startX, 0, width, Height); //Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
result.Add(new TextBounds(currentRect, currentDirection)); var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
} if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds);
}
else
{
currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
} }
currentWidth += runWidth;
currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight) if (currentDirection == FlowDirection.LeftToRight)
{ {
if (currentPosition > firstTextSourceCharacterIndex + textLength) if (currentPosition > characterIndex)
{ {
break; break;
} }
} }
else else
{ {
if (currentPosition <= firstTextSourceCharacterIndex) if (currentPosition <= firstTextSourceIndex)
{ {
break; break;
} }
endX += currentRun.Size.Width - endOffset;
} }
lastDirection = currentDirection; lastDirection = currentDirection;
startX = endX; remainingLength -= characterLength;
if (remainingLength <= 0)
{
break;
}
} }
return result; return result;
} }
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
{
if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
{
return GetTextBoundsLeftToRight(firstTextSourceIndex, textLength);
}
return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength);
}
public TextLineImpl FinalizeLine() public TextLineImpl FinalizeLine()
{ {
_textLineMetrics = CreateLineMetrics(); _textLineMetrics = CreateLineMetrics();

39
src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

@ -0,0 +1,39 @@
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// The bounding rectangle of text run
/// </summary>
public sealed class TextRunBounds
{
/// <summary>
/// Constructing TextRunBounds
/// </summary>
internal TextRunBounds(Rect bounds, int firstCharacterIndex, int length, TextRun textRun)
{
Rectangle = bounds;
TextSourceCharacterIndex = firstCharacterIndex;
Length = length;
TextRun = textRun;
}
/// <summary>
/// First text source character index of text run
/// </summary>
public int TextSourceCharacterIndex { get; }
/// <summary>
/// character length of bounded text run
/// </summary>
public int Length { get; }
/// <summary>
/// Text run bounding rectangle
/// </summary>
public Rect Rectangle { get; }
/// <summary>
/// text run
/// </summary>
public TextRun TextRun { get; }
}
}

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

Loading…
Cancel
Save