Browse Source

Merge remote-tracking branch 'origin/master' into refactor/pixelsize

pull/1889/head
Dan Walmsley 8 years ago
parent
commit
af661085d1
  1. 2
      .travis.yml
  2. 87
      Avalonia.sln
  3. 89
      azure-pipelines.yml
  4. 132
      build.cake
  5. 5
      build/MonoMac.props
  6. 2
      build/ReactiveUI.props
  7. 4
      build/SharedVersion.props
  8. 1
      dirs.proj
  9. 38
      packages.cake
  10. 8
      parameters.cake
  11. 6
      readme.md
  12. 9
      samples/ControlCatalog/App.xaml
  13. 2
      samples/ControlCatalog/Pages/ButtonPage.xaml
  14. 61
      samples/ControlCatalog/Pages/MenuPage.xaml
  15. 97
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  16. 7
      samples/RenderDemo/Pages/AnimationsPage.xaml
  17. 16
      samples/RenderDemo/Pages/AnimationsPage.xaml.cs
  18. 26
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  19. 1
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  20. 4
      src/Android/Avalonia.Android/AndroidPlatform.cs
  21. 28
      src/Avalonia.Animation/Animatable.cs
  22. 15
      src/Avalonia.Animation/Animation.cs
  23. 61
      src/Avalonia.Animation/AnimationInstance`1.cs
  24. 118
      src/Avalonia.Animation/Animator`1.cs
  25. 30
      src/Avalonia.Animation/Clock.cs
  26. 72
      src/Avalonia.Animation/ClockBase.cs
  27. 65
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  28. 5
      src/Avalonia.Animation/DoubleAnimator.cs
  29. 5
      src/Avalonia.Animation/FillMode.cs
  30. 11
      src/Avalonia.Animation/IAnimation.cs
  31. 3
      src/Avalonia.Animation/IAnimationSetter.cs
  32. 7
      src/Avalonia.Animation/IAnimator.cs
  33. 11
      src/Avalonia.Animation/IClock.cs
  34. 10
      src/Avalonia.Animation/IGlobalClock.cs
  35. 2
      src/Avalonia.Animation/ITransition.cs
  36. 5
      src/Avalonia.Animation/KeyFrame.cs
  37. 3
      src/Avalonia.Animation/KeyFramePair`1.cs
  38. 5
      src/Avalonia.Animation/PlayState.cs
  39. 5
      src/Avalonia.Animation/PlaybackDirection.cs
  40. 54
      src/Avalonia.Animation/Timing.cs
  41. 27
      src/Avalonia.Animation/TransitionInstance.cs
  42. 7
      src/Avalonia.Animation/Transition`1.cs
  43. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  44. 18
      src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs
  45. 4
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  46. 7
      src/Avalonia.Base/PriorityBindingEntry.cs
  47. 14
      src/Avalonia.Base/PriorityLevel.cs
  48. 10
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  49. 5
      src/Avalonia.Base/Reactive/ObservableEx.cs
  50. 24
      src/Avalonia.Base/Utilities/MathUtilities.cs
  51. 2
      src/Avalonia.Controls/AppBuilderBase.cs
  52. 16
      src/Avalonia.Controls/Application.cs
  53. 6
      src/Avalonia.Controls/Border.cs
  54. 2
      src/Avalonia.Controls/DrawingPresenter.cs
  55. 4
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  56. 1
      src/Avalonia.Controls/Image.cs
  57. 38
      src/Avalonia.Controls/MenuItem.cs
  58. 1
      src/Avalonia.Controls/Panel.cs
  59. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  60. 11
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  61. 8
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  62. 24
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  63. 8
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  64. 8
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  65. 7
      src/Avalonia.Controls/ProgressBar.cs
  66. 26
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  67. 161
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  68. 4
      src/Avalonia.Controls/TextBlock.cs
  69. 383
      src/Avalonia.Controls/TextBox.cs
  70. 1
      src/Avalonia.Controls/TopLevel.cs
  71. 156
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  72. 2
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  73. 6
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  74. 10
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  75. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  76. 8
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  77. 1
      src/Avalonia.Diagnostics/DevTools.xaml
  78. 23
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  79. 38
      src/Avalonia.Diagnostics/Models/EventChainLink.cs
  80. 5
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  81. 61
      src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs
  82. 98
      src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs
  83. 78
      src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs
  84. 60
      src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs
  85. 80
      src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs
  86. 53
      src/Avalonia.Diagnostics/Views/EventsView.xaml
  87. 32
      src/Avalonia.Diagnostics/Views/EventsView.xaml.cs
  88. 4
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  89. 2
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  90. 18
      src/Avalonia.Input/Key.cs
  91. 11
      src/Avalonia.Input/KeyGesture.cs
  92. 98
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  93. 4
      src/Avalonia.Native.OSX/.gitignore
  94. 328
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  95. 7
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  96. 91
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  97. 12
      src/Avalonia.Native.OSX/KeyTransform.h
  98. 241
      src/Avalonia.Native.OSX/KeyTransform.mm
  99. 51
      src/Avalonia.Native.OSX/Screens.mm
  100. 262
      src/Avalonia.Native.OSX/SystemDialogs.mm

2
.travis.yml

@ -11,6 +11,8 @@ mono:
- 5.2.0
dotnet: 2.1.200
script:
- sudo apt-get update
- sudo apt-get install castxml
- ./build.sh --target "Travis" --configuration "Release"
notifications:
email: false

87
Avalonia.sln

@ -80,13 +80,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skia", "Skia", "{3743B0F2-C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Android", "src\Android\Avalonia.Android\Avalonia.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Android", "src\Android\Avalonia.Android\Avalonia.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.AndroidTestApplication", "src\Android\Avalonia.AndroidTestApplication\Avalonia.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.iOS", "src\iOS\Avalonia.iOS\Avalonia.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.iOS", "src\iOS\Avalonia.iOS\Avalonia.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.iOSTestApplication", "src\iOS\Avalonia.iOSTestApplication\Avalonia.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}"
EndProject
@ -164,7 +164,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
EndProject
@ -178,14 +178,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Native", "src\Avalonia.Native\Avalonia.Native.csproj", "{12A91A62-C064-42CA-9A8C-A1272F354388}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1591,30 +1591,6 @@ Global
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|Any CPU
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1663,6 +1639,54 @@ Global
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.Build.0 = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.ActiveCfg = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhone.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhone.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|Any CPU.Build.0 = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.ActiveCfg = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.Build.0 = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1713,7 +1737,6 @@ Global
{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection

89
azure-pipelines.yml

@ -0,0 +1,89 @@
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
steps:
- task: CmdLine@2
inputs:
script: |
sudo apt-get update
sudo apt-get install castxml
- task: CmdLine@2
inputs:
script: |
dotnet tool install -g Cake.Tool --version 0.30.0
- script: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
dotnet cake build.cake -target="Azure-Linux" -configuration="Release"
- job: macOS
pool:
vmImage: 'xcode9-macos10.13'
steps:
- task: DotNetCoreInstaller@0
inputs:
version: '2.1.403'
- task: Xcode@5
inputs:
actions: 'build'
scheme: ''
sdk: 'macosx10.13'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: 'default' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./'
- task: CmdLine@2
inputs:
script: brew install castxml
- task: CmdLine@2
inputs:
script: |
dotnet tool install -g Cake.Tool --version 0.30.0
- script: |
export COREHOST_TRACE=0
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
which dotnet
dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
dotnet cake build.cake -target="Azure-OSX" -configuration="Release"
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/'
artifactName: 'Avalonia.Native.OSX'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/bin'
artifactName: 'BinariesOSX'
- job: Windows
pool:
vmImage: 'vs2017-win2016'
steps:
- task: CmdLine@2
inputs:
script: |
dotnet tool install -g Cake.Tool --version 0.30.0
- task: CmdLine@2
inputs:
script: |
set PATH=%PATH%;%USERPROFILE%\.dotnet\tools
dotnet cake build.cake -target="Azure-Windows" -configuration="Release"
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
artifactName: 'NuGet'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
artifactName: 'Samples'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/bin'
artifactName: 'BinariesWindows'

132
build.cake

@ -1,15 +1,9 @@
///////////////////////////////////////////////////////////////////////////////
// ADDINS
///////////////////////////////////////////////////////////////////////////////
#addin "nuget:?package=NuGet.Core&version=2.14.0"
#tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
///////////////////////////////////////////////////////////////////////////////
// TOOLS
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=NuGet.CommandLine&version=4.7.1"
#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3"
#tool "nuget:?package=xunit.runner.console&version=2.3.1"
#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559"
@ -21,7 +15,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NuGet;
///////////////////////////////////////////////////////////////////////////////
// SCRIPTS
@ -71,6 +64,7 @@ Setup<AvaloniaBuildData>(context =>
Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix);
Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows);
Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor);
Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure);
Information("IsPullRequest: " + parameters.IsPullRequest);
Information("IsMainRepo: " + parameters.IsMainRepo);
Information("IsMasterBranch: " + parameters.IsMasterBranch);
@ -170,6 +164,7 @@ Task("Run-Unit-Tests-Impl")
RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data.Parameters, false);
if (data.Parameters.IsRunningOnWindows)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false);
@ -245,107 +240,6 @@ Task("Create-NuGet-Packages-Impl")
}
});
Task("Publish-MyGet-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMasterBranch)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMyGetRelease)
.Does<AvaloniaBuildData>(data =>
{
var apiKey = EnvironmentVariable("MYGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
{
throw new InvalidOperationException("Could not resolve MyGet API key.");
}
var apiUrl = EnvironmentVariable("MYGET_API_URL");
if(string.IsNullOrEmpty(apiUrl))
{
throw new InvalidOperationException("Could not resolve MyGet API url.");
}
foreach(var nupkg in data.Packages.NugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
Source = apiUrl,
ApiKey = apiKey
});
}
})
.OnError(exception =>
{
Information("Publish-MyGet Task failed, but continuing with next Task...");
});
Task("Publish-NuGet-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsNuGetRelease)
.Does<AvaloniaBuildData>(data =>
{
var apiKey = EnvironmentVariable("NUGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
{
throw new InvalidOperationException("Could not resolve NuGet API key.");
}
var apiUrl = EnvironmentVariable("NUGET_API_URL");
if(string.IsNullOrEmpty(apiUrl))
{
throw new InvalidOperationException("Could not resolve NuGet API url.");
}
foreach(var nupkg in data.Packages.NugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
ApiKey = apiKey,
Source = apiUrl
});
}
})
.OnError(exception =>
{
Information("Publish-NuGet Task failed, but continuing with next Task...");
});
Task("Inspect-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.Does(() =>
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
Information("Running code inspections");
var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"),
new ProcessSettings
{
Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln",
RedirectStandardOutput = true
});
Information("Analyzing report");
var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
var failBuild = false;
foreach(var xml in doc.Descendants("Issue"))
{
var typeId = xml.Attribute("TypeId").Value.ToString();
if(badIssues.Contains(typeId))
{
var file = xml.Attribute("File").Value.ToString().ToLower();
if(whitelist.Any(wh => file.StartsWith(wh)))
continue;
var line = xml.Attribute("Line").Value.ToString();
Error(typeId + " - " + file + " on line " + line);
failBuild = true;
}
}
if(failBuild)
throw new Exception("Issues found");
});
///////////////////////////////////////////////////////////////////////////////
// TARGETS
///////////////////////////////////////////////////////////////////////////////
@ -363,19 +257,29 @@ Task("Run-Tests")
Task("Package")
.IsDependentOn("Run-Tests")
.IsDependentOn("Inspect-Impl")
.IsDependentOn("Create-NuGet-Packages-Impl");
Task("AppVeyor")
.IsDependentOn("Package")
.IsDependentOn("Copy-Files-Impl")
.IsDependentOn("Zip-Files-Impl")
.IsDependentOn("Publish-MyGet-Impl")
.IsDependentOn("Publish-NuGet-Impl");
.IsDependentOn("Zip-Files-Impl");
Task("Travis")
.IsDependentOn("Run-Tests");
Task("Azure-Linux")
.IsDependentOn("Run-Tests");
Task("Azure-OSX")
.IsDependentOn("Run-Tests")
.IsDependentOn("Copy-Files-Impl")
.IsDependentOn("Zip-Files-Impl");
Task("Azure-Windows")
.IsDependentOn("Package")
.IsDependentOn("Copy-Files-Impl")
.IsDependentOn("Zip-Files-Impl");
///////////////////////////////////////////////////////////////////////////////
// EXECUTE
///////////////////////////////////////////////////////////////////////////////

5
build/MonoMac.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup>
</Project>

2
build/ReactiveUI.props

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

4
build/SharedVersion.props

@ -2,8 +2,8 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.6.2</Version>
<Copyright>Copyright 2016 &#169; The AvaloniaUI Project</Copyright>
<Version>0.7.0</Version>
<Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

1
dirs.proj

@ -5,6 +5,7 @@
<ProjectReference Include="tests/**/*.*proj" />
<ProjectReference Remove="**/*.shproj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="**/*.pbxproj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
<ProjectReference Remove="src/Android/**/*.*proj" />

38
packages.cake

@ -150,7 +150,7 @@ public class Packages
nuspec.Symbols = false;
nuspec.NoPackageAnalysis = true;
nuspec.Description = "The Avalonia UI framework";
nuspec.Copyright = "Copyright 2015";
nuspec.Copyright = "Copyright 2018";
nuspec.Tags = new [] { "Avalonia" };
});
@ -167,6 +167,7 @@ public class Packages
new [] { "./src/", "Avalonia.Logging.Serilog"},
new [] { "./src/", "Avalonia.Visuals"},
new [] { "./src/", "Avalonia.Styling"},
new [] { "./src/", "Avalonia.OpenGL"},
new [] { "./src/", "Avalonia.Themes.Default"},
new [] { "./src/Markup/", "Avalonia.Markup"},
new [] { "./src/Markup/", "Avalonia.Markup.Xaml"},
@ -407,20 +408,6 @@ public class Packages
BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
new NuGetPackSettings()
{
Id = "Avalonia.MonoMac",
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
}.Dep("MonoMac.NetStandard").ToArray(),
Files = new []
{
new NuSpecContent { Source = "netstandard2.0/Avalonia.MonoMac.dll", Target = "lib/netstandard2.0" },
},
BasePath = context.Directory("./src/OSX/Avalonia.MonoMac/bin/" + parameters.DirSuffix),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.Desktop
///////////////////////////////////////////////////////////////////////////////
@ -433,7 +420,7 @@ public class Packages
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.MonoMac", Version = parameters.Version }
new NuSpecDependency() { Id = "Avalonia.Native", Version = parameters.Version }
},
Files = new NuSpecContent[]
{
@ -459,7 +446,24 @@ public class Packages
},
BasePath = context.Directory("./src/Linux/"),
OutputDirectory = parameters.NugetRoot
}
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.Native
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()
{
Id = "Avalonia.Native",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Native.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Avalonia.Native/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
};
var nuspecNuGetSettingInterop = new NuGetPackSettings()

8
parameters.cake

@ -14,6 +14,7 @@ public class Parameters
public bool IsRunningOnUnix { get; private set; }
public bool IsRunningOnWindows { get; private set; }
public bool IsRunningOnAppVeyor { get; private set; }
public bool IsRunningOnAzure { get; private set; }
public bool IsPullRequest { get; private set; }
public bool IsMainRepo { get; private set; }
public bool IsMasterBranch { get; private set; }
@ -53,6 +54,8 @@ public class Parameters
IsRunningOnUnix = context.IsRunningOnUnix();
IsRunningOnWindows = context.IsRunningOnWindows();
IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor;
IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, buildSystem.AppVeyor.Environment.Repository.Name);
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch);
@ -85,6 +88,11 @@ public class Parameters
Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
}
}
else if (IsRunningOnAzure)
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
}
// DIRECTORIES
ArtifactsDir = (DirectoryPath)context.Directory("./artifacts");

6
readme.md

@ -2,9 +2,9 @@
# Avalonia
| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Open Collective |
|---|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
| Gitter Chat | Build Status | Windows Build Status | Linux/Mac Build Status | Open Collective |
|---|---|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
## About

9
samples/ControlCatalog/App.xaml

@ -14,6 +14,11 @@
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
<Style Selector="TextBlock.h3">
<Setter Property="Foreground" Value="#a2a2a2"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Application.Styles>
</Application>
</Application>

2
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -18,7 +18,7 @@
<Style>
<Style.Resources>
<SolidColorBrush x:Key="ThemeBorderMidBrush">Red</SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlDarkBrush">DarkRed</SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlHighBrush">DarkRed</SolidColorBrush>
</Style.Resources>
</Style>
</Button.Styles>

61
samples/ControlCatalog/Pages/MenuPage.xaml

@ -7,29 +7,46 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Defined in XAML</TextBlock>
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
<MenuItem Header="_Second">
<MenuItem Header="Second _Menu Item"/>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Second">
<MenuItem Header="Second _Menu Item"/>
</MenuItem>
</Menu>
</Menu>
</StackPanel>
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding MenuItems}">
<Menu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Menu.Styles>
</Menu>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>
</UserControl>

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

@ -1,5 +1,10 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace ControlCatalog.Pages
{
@ -8,6 +13,51 @@ namespace ControlCatalog.Pages
public MenuPage()
{
this.InitializeComponent();
var vm = new MenuPageViewModel();
vm.MenuItems = new[]
{
new MenuItemViewModel
{
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = vm.OpenCommand },
new MenuItemViewModel { Header = "Save", Command = vm.SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = vm.OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = vm.OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
}
},
new MenuItemViewModel
{
Header = "_Edit",
Items = new[]
{
new MenuItemViewModel { Header = "_Copy" },
new MenuItemViewModel { Header = "_Paste" },
}
}
};
DataContext = vm;
}
private void InitializeComponent()
@ -15,4 +65,51 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
}
public class MenuPageViewModel
{
public MenuPageViewModel()
{
OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save);
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<string, Unit> OpenRecentCommand { get; }
public async Task Open()
{
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync();
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
public class MenuItemViewModel
{
public string Header { get; set; }
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
}
}

7
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -107,9 +107,12 @@
</UserControl.Styles>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding ToggleGlobalPlayState}"/>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">
<Border Classes="Test Rect1" Background="DarkRed"/>
@ -120,4 +123,4 @@
</WrapPanel>
</StackPanel>
</Grid>
</UserControl>
</UserControl>

16
samples/RenderDemo/Pages/AnimationsPage.xaml.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using RenderDemo.ViewModels;
@ -23,5 +24,20 @@ namespace RenderDemo.Pages
{
AvaloniaXamlLoader.Load(this);
}
private void ToggleClock(object sender, RoutedEventArgs args)
{
var button = sender as Button;
var clock = button.Clock;
if (clock.PlayState == PlayState.Run)
{
clock.PlayState = PlayState.Pause;
}
else if (clock.PlayState == PlayState.Pause)
{
clock.PlayState = PlayState.Run;
}
}
}
}

26
samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@ -6,27 +6,15 @@ namespace RenderDemo.ViewModels
{
public class AnimationsPageViewModel : ReactiveObject
{
private string _playStateText = "Pause all animations";
private bool _isPlaying = true;
public AnimationsPageViewModel()
{
ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState());
}
private string _playStateText = "Pause animations on this page";
void TogglePlayState()
public void TogglePlayState()
{
switch (Animation.GlobalPlayState)
{
case PlayState.Run:
PlayStateText = "Resume all animations";
Animation.GlobalPlayState = PlayState.Pause;
break;
case PlayState.Pause:
PlayStateText = "Pause all animations";
Animation.GlobalPlayState = PlayState.Run;
break;
}
PlayStateText = _isPlaying
? "Resume animations on this page" : "Pause animations on this page";
_isPlaying = !_isPlaying;
}
public string PlayStateText
@ -34,7 +22,5 @@ namespace RenderDemo.ViewModels
get { return _playStateText; }
set { this.RaiseAndSetIfChanged(ref _playStateText, value); }
}
public ReactiveCommand ToggleGlobalPlayState { get; }
}
}

1
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -7,6 +7,7 @@ using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI;
namespace VirtualizationDemo.ViewModels

4
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -52,7 +52,9 @@ namespace Avalonia.Android
.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(Instance)
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
SkiaPlatform.Initialize();

28
src/Avalonia.Animation/Animatable.cs

@ -14,26 +14,14 @@ namespace Avalonia.Animation
/// Base class for all animatable objects.
/// </summary>
public class Animatable : AvaloniaObject
{
/// <summary>
/// Defines the <see cref="PlayState"/> property.
/// </summary>
public static readonly DirectProperty<Animatable, PlayState> PlayStateProperty =
AvaloniaProperty.RegisterDirect<Animatable, PlayState>(
nameof(PlayState),
o => o.PlayState,
(o, v) => o.PlayState = v);
private PlayState _playState = PlayState.Run;
{
public static readonly StyledProperty<IClock> ClockProperty =
AvaloniaProperty.Register<Animatable, IClock>(nameof(Clock), inherits: true);
/// <summary>
/// Gets or sets the state of the animation for this
/// control.
/// </summary>
public PlayState PlayState
public IClock Clock
{
get { return _playState; }
set { SetAndRaise(PlayStateProperty, ref _playState, value); }
get => GetValue(ClockProperty);
set => SetValue(ClockProperty, value);
}
/// <summary>
@ -69,9 +57,9 @@ namespace Avalonia.Animation
if (match != null)
{
match.Apply(this, e.OldValue, e.NewValue);
match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
}
}
}
}
}
}

15
src/Avalonia.Animation/Animation.cs

@ -17,11 +17,6 @@ namespace Avalonia.Animation
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation
{
/// <summary>
/// Gets or sets the animation play state for all animations
/// </summary>
public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
/// <summary>
/// Gets or sets the active time of this animation.
/// </summary>
@ -149,12 +144,12 @@ namespace Avalonia.Animation
}
/// <inheritdocs/>
public IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete)
public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
var (animators, subscriptions) = InterpretKeyframes(control);
if (animators.Count == 1)
{
subscriptions.Add(animators[0].Apply(this, control, match, onComplete));
subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete));
}
else
{
@ -168,7 +163,7 @@ namespace Avalonia.Animation
animatorOnComplete = () => tcs.SetResult(null);
completionTasks.Add(tcs.Task);
}
subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete));
subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete));
}
if (onComplete != null)
@ -180,7 +175,7 @@ namespace Avalonia.Animation
}
/// <inheritdocs/>
public Task RunAsync(Animatable control)
public Task RunAsync(Animatable control, IClock clock = null)
{
var run = new TaskCompletionSource<object>();
@ -188,7 +183,7 @@ namespace Avalonia.Animation
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;
subscriptions = this.Apply(control, Observable.Return(true), () =>
subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{
run.SetResult(null);
subscriptions?.Dispose();

61
src/Avalonia.Animation/AnimationInstance`1.cs

@ -8,7 +8,7 @@ using Avalonia.Reactive;
namespace Avalonia.Animation
{
/// <summary>
/// Handles interpolatoin and time-related functions
/// Handles interpolation and time-related functions
/// for keyframe animations.
/// </summary>
internal class AnimationInstance<T> : SingleSubscriberObservableBase<T>
@ -19,7 +19,6 @@ namespace Avalonia.Animation
private double _currentIteration;
private bool _isLooping;
private bool _gotFirstKFValue;
private bool _gotFirstFrameCount;
private bool _iterationDelay;
private FillMode _fillMode;
private PlaybackDirection _animationDirection;
@ -29,15 +28,14 @@ namespace Avalonia.Animation
private double _speedRatio;
private TimeSpan _delay;
private TimeSpan _duration;
private TimeSpan _firstFrameCount;
private TimeSpan _internalClock;
private TimeSpan? _previousClock;
private Easings.Easing _easeFunc;
private Action _onCompleteAction;
private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription;
private readonly IClock _baseClock;
private IClock _clock;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> Interpolator)
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
{
if (animation.SpeedRatio <= 0)
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
@ -73,17 +71,19 @@ namespace Avalonia.Animation
_fillMode = animation.FillMode;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
}
protected override void Unsubscribed()
{
_timerSubscription?.Dispose();
_clock.PlayState = PlayState.Stop;
}
protected override void Subscribed()
{
_timerSubscription = Timing.AnimationsTimer
.Subscribe(p => this.Step(p));
_clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(Step);
}
public void Step(TimeSpan frameTick)
@ -116,46 +116,21 @@ namespace Avalonia.Animation
PublishNext(_lastInterpValue);
}
private void DoPlayStatesAndTime(TimeSpan systemTime)
private void DoPlayStates()
{
if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop)
DoComplete();
if (!_previousClock.HasValue)
{
_previousClock = systemTime;
_internalClock = TimeSpan.Zero;
}
else
{
if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause)
{
_previousClock = systemTime;
return;
}
var delta = systemTime - _previousClock;
_internalClock += delta.Value;
_previousClock = systemTime;
}
if (!_gotFirstKFValue)
{
_firstKFValue = (T)_parent.First().Value;
_gotFirstKFValue = true;
}
if (!_gotFirstFrameCount)
{
_firstFrameCount = _internalClock;
_gotFirstFrameCount = true;
}
}
private void InternalStep(TimeSpan systemTime)
private void InternalStep(TimeSpan time)
{
DoPlayStatesAndTime(systemTime);
var time = _internalClock - _firstFrameCount;
DoPlayStates();
var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration;
@ -176,22 +151,18 @@ namespace Avalonia.Animation
}
//Calculate the current iteration number
_currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
_currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2;
}
else
{
_previousClock = systemTime;
return;
}
time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks);
time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks));
if (!_isLooping)
{
if (_currentIteration > _repeatCount)
DoComplete();
if (time > iterationEndpoint)
if ((_currentIteration > _repeatCount) || (time > iterationEndpoint))
DoComplete();
}
@ -225,4 +196,4 @@ namespace Avalonia.Animation
}
}
}
}
}

118
src/Avalonia.Animation/Animator`1.cs

@ -1,10 +1,14 @@
using System;
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
@ -17,7 +21,7 @@ namespace Avalonia.Animation
/// List of type-converted keyframes.
/// </summary>
private readonly List<AnimatorKeyFrame> _convertedKeyframes = new List<AnimatorKeyFrame>();
private bool _isVerifiedAndConverted;
/// <summary>
@ -28,21 +32,17 @@ namespace Avalonia.Animation
public Animator()
{
// Invalidate keyframes when changed.
this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
}
/// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> match, Action onComplete)
public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
if (!_isVerifiedAndConverted)
if (!_isVerifiedAndConverted)
VerifyConvertKeyFrames();
return match
.Where(p => p)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames(animation, control, onComplete);
});
var subject = new DisposeAnimationInstanceSubject<T>(this, animation, control, clock, onComplete);
return match.Subscribe(subject);
}
/// <summary>
@ -52,58 +52,84 @@ namespace Avalonia.Animation
/// (i.e., the normalized time between the selected keyframes, relative to the
/// time parameter).
/// </summary>
/// <param name="t">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
/// <param name="animationTime">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double animationTime)
{
AnimatorKeyFrame firstCue, lastCue ;
AnimatorKeyFrame firstKeyframe, lastKeyframe;
int kvCount = _convertedKeyframes.Count;
if (kvCount > 2)
{
if (t <= 0.0)
if (animationTime <= 0.0)
{
firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes[1];
firstKeyframe = _convertedKeyframes[0];
lastKeyframe = _convertedKeyframes[1];
}
else if (t >= 1.0)
else if (animationTime >= 1.0)
{
firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2];
lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1];
firstKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 2];
lastKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 1];
}
else
{
(double time, int index) maxval = (0.0d, 0);
for (int i = 0; i < _convertedKeyframes.Count; i++)
{
var comp = _convertedKeyframes[i].Cue.CueValue;
if (t >= comp)
{
maxval = (comp, i);
}
}
firstCue = _convertedKeyframes[maxval.index];
lastCue = _convertedKeyframes[maxval.index + 1];
int index = FindClosestBeforeKeyFrame(animationTime);
firstKeyframe = _convertedKeyframes[index];
lastKeyframe = _convertedKeyframes[index + 1];
}
}
else
{
firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes[1];
firstKeyframe = _convertedKeyframes[0];
lastKeyframe = _convertedKeyframes[1];
}
double t0 = firstCue.Cue.CueValue;
double t1 = lastCue.Cue.CueValue;
var intraframeTime = (t - t0) / (t1 - t0);
var firstFrameData = (firstCue.GetTypedValue<T>(), firstCue.isNeutral);
var lastFrameData = (lastCue.GetTypedValue<T>(), lastCue.isNeutral);
double t0 = firstKeyframe.Cue.CueValue;
double t1 = lastKeyframe.Cue.CueValue;
var intraframeTime = (animationTime - t0) / (t1 - t0);
var firstFrameData = (firstKeyframe.GetTypedValue<T>(), firstKeyframe.isNeutral);
var lastFrameData = (lastKeyframe.GetTypedValue<T>(), lastKeyframe.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
}
private int FindClosestBeforeKeyFrame(double time)
{
int FindClosestBeforeKeyFrame(int startIndex, int length)
{
if (length == 0 || length == 1)
{
return startIndex;
}
int middle = startIndex + (length / 2);
if (_convertedKeyframes[middle].Cue.CueValue < time)
{
return FindClosestBeforeKeyFrame(middle, length - middle);
}
else if (_convertedKeyframes[middle].Cue.CueValue > time)
{
return FindClosestBeforeKeyFrame(startIndex, middle - startIndex);
}
else
{
return middle;
}
}
return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count);
}
/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete)
{
var instance = new AnimationInstance<T>(animation, control, this, onComplete, DoInterpolation);
var instance = new AnimationInstance<T>(
animation,
control,
this,
clock ?? control.Clock ?? Clock.GlobalClock,
onComplete,
DoInterpolation);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}
@ -124,14 +150,6 @@ namespace Avalonia.Animation
AddNeutralKeyFramesIfNeeded();
var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue);
_convertedKeyframes.Clear();
foreach (AnimatorKeyFrame keyframe in copy)
{
_convertedKeyframes.Add(keyframe);
}
_isVerifiedAndConverted = true;
}
@ -161,7 +179,7 @@ namespace Avalonia.Animation
{
if (!hasStartKey)
{
_convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
_convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
}
if (!hasEndKey)
@ -170,4 +188,4 @@ namespace Avalonia.Animation
}
}
}
}
}

30
src/Avalonia.Animation/Clock.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
public class Clock : ClockBase
{
public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>();
private IDisposable _parentSubscription;
public Clock()
:this(GlobalClock)
{
}
public Clock(IClock parent)
{
_parentSubscription = parent.Subscribe(Pulse);
}
protected override void Stop()
{
_parentSubscription?.Dispose();
}
}
}

72
src/Avalonia.Animation/ClockBase.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
public class ClockBase : IClock
{
private ClockObservable _observable;
private IObservable<TimeSpan> _connectedObservable;
private TimeSpan? _previousTime;
private TimeSpan _internalTime;
protected ClockBase()
{
_observable = new ClockObservable();
_connectedObservable = _observable.Publish().RefCount();
}
protected bool HasSubscriptions => _observable.HasSubscriptions;
public PlayState PlayState { get; set; }
protected void Pulse(TimeSpan systemTime)
{
if (!_previousTime.HasValue)
{
_previousTime = systemTime;
_internalTime = TimeSpan.Zero;
}
else
{
if (PlayState == PlayState.Pause)
{
_previousTime = systemTime;
return;
}
var delta = systemTime - _previousTime;
_internalTime += delta.Value;
_previousTime = systemTime;
}
_observable.Pulse(_internalTime);
if (PlayState == PlayState.Stop)
{
Stop();
}
}
protected virtual void Stop()
{
}
public IDisposable Subscribe(IObserver<TimeSpan> observer)
{
return _connectedObservable.Subscribe(observer);
}
private class ClockObservable : LightweightObservableBase<TimeSpan>
{
public bool HasSubscriptions { get; private set; }
public void Pulse(TimeSpan time) => PublishNext(time);
protected override void Initialize() => HasSubscriptions = true;
protected override void Deinitialize() => HasSubscriptions = false;
}
}
}

65
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -0,0 +1,65 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
/// <summary>
/// Manages the lifetime of animation instances as determined by its selector state.
/// </summary>
internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable
{
private IDisposable _lastInstance;
private bool _lastMatch;
private Animator<T> _animator;
private Animation _animation;
private Animatable _control;
private Action _onComplete;
private IClock _clock;
public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock clock, Action onComplete)
{
this._animator = animator;
this._animation = animation;
this._control = control;
this._onComplete = onComplete;
this._clock = clock;
}
public void Dispose()
{
_lastInstance?.Dispose();
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
_lastInstance?.Dispose();
}
void IObserver<bool>.OnNext(bool matchVal)
{
if (matchVal != _lastMatch)
{
_lastInstance?.Dispose();
if (matchVal)
{
_lastInstance = _animator.Run(_animation, _control, _clock, _onComplete);
}
_lastMatch = matchVal;
}
}
}
}

5
src/Avalonia.Animation/DoubleAnimator.cs

@ -1,4 +1,7 @@
namespace Avalonia.Animation
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.

5
src/Avalonia.Animation/FillMode.cs

@ -1,4 +1,7 @@
namespace Avalonia.Animation
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
public enum FillMode
{

11
src/Avalonia.Animation/IAnimation.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Threading.Tasks;
@ -9,13 +12,13 @@ namespace Avalonia.Animation
public interface IAnimation
{
/// <summary>
/// Apply the animation to the specified control
/// Apply the animation to the specified control and run it when <paramref name="match" /> produces <c>true</c>.
/// </summary>
IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete = null);
IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete = null);
/// <summary>
/// Run the animation to the specified control
/// Run the animation on the specified control.
/// </summary>
Task RunAsync(Animatable control);
Task RunAsync(Animatable control, IClock clock);
}
}

3
src/Avalonia.Animation/IAnimationSetter.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
public interface IAnimationSetter

7
src/Avalonia.Animation/IAnimator.cs

@ -1,4 +1,7 @@
using System;
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Avalonia.Animation
@ -16,6 +19,6 @@ namespace Avalonia.Animation
/// <summary>
/// Applies the current KeyFrame group to the specified control.
/// </summary>
IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete);
IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete);
}
}

11
src/Avalonia.Animation/IClock.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
{
public interface IClock : IObservable<TimeSpan>
{
PlayState PlayState { get; set; }
}
}

10
src/Avalonia.Animation/IGlobalClock.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
{
public interface IGlobalClock : IClock
{
}
}

2
src/Avalonia.Animation/ITransition.cs

@ -13,7 +13,7 @@ namespace Avalonia.Animation
/// <summary>
/// Applies the transition to the specified <see cref="Animatable"/>.
/// </summary>
IDisposable Apply(Animatable control, object oldValue, object newValue);
IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue);
/// <summary>
/// Gets the property to be animated.

5
src/Avalonia.Animation/KeyFrame.cs

@ -1,4 +1,7 @@
using System;
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;

3
src/Avalonia.Animation/KeyFramePair`1.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>

5
src/Avalonia.Animation/PlayState.cs

@ -1,4 +1,7 @@
namespace Avalonia.Animation
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Determines the playback state of an animation.

5
src/Avalonia.Animation/PlaybackDirection.cs

@ -1,4 +1,7 @@
namespace Avalonia.Animation
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Determines the playback direction of an animation.

54
src/Avalonia.Animation/Timing.cs

@ -1,54 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Threading;
namespace Avalonia.Animation
{
/// <summary>
/// Provides global timing functions for animations.
/// </summary>
public static class Timing
{
/// <summary>
/// The number of frames per second.
/// </summary>
public const int FramesPerSecond = 60;
/// <summary>
/// The time span of each frame.
/// </summary>
internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
/// <summary>
/// Initializes static members of the <see cref="Timing"/> class.
/// </summary>
static Timing()
{
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
AnimationsTimer = globalTimer
.Select(_ => GetTickCount())
.Publish()
.RefCount();
}
internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount);
/// <summary>
/// Gets the animation timer.
/// </summary>
/// <remarks>
/// The animation timer triggers usually at 60 times per second or as
/// defined in <see cref="FramesPerSecond"/>.
/// The parameter passed to a subsciber is the current playstate of the animation.
/// </remarks>
internal static IObservable<TimeSpan> AnimationsTimer
{
get;
}
}
}

27
src/Avalonia.Animation/TransitionInstance.cs

@ -15,21 +15,22 @@ namespace Avalonia.Animation
/// </summary>
internal class TransitionInstance : SingleSubscriberObservableBase<double>
{
private IDisposable timerSubscription;
private TimeSpan startTime;
private TimeSpan duration;
private IDisposable _timerSubscription;
private TimeSpan _duration;
private readonly IClock _baseClock;
private IClock _clock;
public TransitionInstance(TimeSpan Duration)
public TransitionInstance(IClock clock, TimeSpan Duration)
{
duration = Duration;
_duration = Duration;
_baseClock = clock;
}
private void TimerTick(TimeSpan t)
{
var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks;
var interpVal = (double)t.Ticks / _duration.Ticks;
if (interpVal > 1d
|| interpVal < 0d)
if (interpVal > 1d || interpVal < 0d)
{
PublishCompleted();
return;
@ -40,15 +41,15 @@ namespace Avalonia.Animation
protected override void Unsubscribed()
{
timerSubscription?.Dispose();
_timerSubscription?.Dispose();
_clock.PlayState = PlayState.Stop;
}
protected override void Subscribed()
{
startTime = Timing.GetTickCount();
timerSubscription = Timing.AnimationsTimer
.Subscribe(t => TimerTick(t));
_clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(TimerTick);
PublishNext(0.0d);
}
}
}
}

7
src/Avalonia.Animation/Transition`1.cs

@ -14,7 +14,6 @@ namespace Avalonia.Animation
public abstract class Transition<T> : AvaloniaObject, ITransition
{
private AvaloniaProperty _prop;
private Easing _easing;
/// <summary>
/// Gets the duration of the animation.
@ -49,12 +48,10 @@ namespace Avalonia.Animation
public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
/// <inheritdocs/>
public virtual IDisposable Apply(Animatable control, object oldValue, object newValue)
public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue)
{
var transition = DoTransition(new TransitionInstance(Duration), (T)oldValue, (T)newValue);
var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue);
return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
}
}
}

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -3,6 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Avalonia.Base</AssemblyName>
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />

18
src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs

@ -0,0 +1,18 @@
using System;
namespace Avalonia.Platform.Interop
{
public interface IDynamicLibraryLoader
{
IntPtr LoadLibrary(string dll);
IntPtr GetProcAddress(IntPtr dll, string proc, bool optional);
}
public class DynamicLibraryLoaderException : Exception
{
public DynamicLibraryLoaderException(string message) : base(message)
{
}
}
}

4
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs → src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -2,9 +2,9 @@
using System.Runtime.InteropServices;
using System.Text;
namespace Avalonia.Gtk3.Interop
namespace Avalonia.Platform.Interop
{
class Utf8Buffer : SafeHandle
public class Utf8Buffer : SafeHandle
{
private GCHandle _gchandle;
private byte[] _data;

7
src/Avalonia.Base/PriorityBindingEntry.cs

@ -50,6 +50,11 @@ namespace Avalonia
get;
}
/// <summary>
/// Gets a value indicating whether the binding has completed.
/// </summary>
public bool HasCompleted { get; private set; }
/// <summary>
/// The current value of the binding.
/// </summary>
@ -129,6 +134,8 @@ namespace Avalonia
private void Completed()
{
HasCompleted = true;
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Completed(this);

14
src/Avalonia.Base/PriorityLevel.cs

@ -112,12 +112,16 @@ namespace Avalonia
return Disposable.Create(() =>
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
if (!entry.HasCompleted)
{
ActivateFirstBinding();
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
}

10
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@ -82,18 +82,10 @@ namespace Avalonia.Reactive
if (observers.Count == 0)
{
observers.TrimExcess();
Deinitialize();
}
else
{
return;
}
} else
{
return;
}
}
Deinitialize();
}
}

5
src/Avalonia.Base/Reactive/ObservableEx.cs

@ -21,7 +21,7 @@ namespace Avalonia.Reactive
{
return new SingleValueImpl<T>(value);
}
private class SingleValueImpl<T> : IObservable<T>
{
private T _value;
@ -30,7 +30,6 @@ namespace Avalonia.Reactive
{
_value = value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
observer.OnNext(_value);
@ -38,4 +37,4 @@ namespace Avalonia.Reactive
}
}
}
}
}

24
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Utilities
{
/// <summary>
@ -31,5 +30,28 @@ namespace Avalonia.Utilities
return val;
}
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
/// <param name="val">The value.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static int Clamp(int val, int min, int max)
{
if (val < min)
{
return min;
}
else if (val > max)
{
return max;
}
else
{
return val;
}
}
}
}

2
src/Avalonia.Controls/AppBuilderBase.cs

@ -272,10 +272,10 @@ namespace Avalonia.Controls
s_setupWasAlreadyCalled = true;
Instance.RegisterServices();
RuntimePlatformServicesInitializer();
WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
Instance.RegisterServices();
Instance.Initialize();
AfterSetupCallback(Self);
}

16
src/Avalonia.Controls/Application.cs

@ -4,12 +4,14 @@
using System;
using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Threading;
@ -120,11 +122,11 @@ namespace Avalonia
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourcesChanged;
_resources.ResourcesChanged -= ThisResourcesChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourcesChanged;
_resources.ResourcesChanged += ThisResourcesChanged;
if (hadResources || _resources.Count > 0)
{
@ -335,6 +337,16 @@ namespace Avalonia
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
var clock = new RenderLoopClock();
AvaloniaLocator.CurrentMutable
.Bind<IGlobalClock>().ToConstant(clock)
.GetService<IRenderLoop>()?.Add(clock);
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
}
}

6
src/Avalonia.Controls/Border.cs

@ -43,7 +43,11 @@ namespace Avalonia.Controls
/// </summary>
static Border()
{
AffectsRender<Border>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsRender<Border>(
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}

2
src/Avalonia.Controls/DrawingPresenter.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls
if (Drawing != null)
{
using (context.PushPreTransform(_transform))
using (context.PushClip(Bounds))
using (context.PushClip(new Rect(Bounds.Size)))
{
Drawing.Draw(context);
}

4
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
/// </summary>
public class ItemContainerGenerator : IItemContainerGenerator
{
private Dictionary<int, ItemContainerInfo> _containers = new Dictionary<int, ItemContainerInfo>();
private SortedDictionary<int, ItemContainerInfo> _containers = new SortedDictionary<int, ItemContainerInfo>();
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators
Recycled?.Invoke(this, e);
}
}
}
}

1
src/Avalonia.Controls/Image.cs

@ -26,6 +26,7 @@ namespace Avalonia.Controls
static Image()
{
AffectsRender<Image>(SourceProperty, StretchProperty);
AffectsMeasure<Image>(SourceProperty, StretchProperty);
}
/// <summary>

38
src/Avalonia.Controls/MenuItem.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@ -357,10 +358,21 @@ namespace Avalonia.Controls
{
base.OnTemplateApplied(e);
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
if (_popup != null)
{
_popup.Opened -= PopupOpened;
_popup.Closed -= PopupClosed;
_popup.DependencyResolver = null;
}
_popup = e.NameScope.Find<Popup>("PART_Popup");
if (_popup != null)
{
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
}
}
/// <summary>
@ -408,6 +420,24 @@ namespace Avalonia.Controls
IsEnabled = Command == null || Command.CanExecute(CommandParameter);
}
/// <summary>
/// Called when the <see cref="Header"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.NewValue is string newValue && newValue == "-")
{
PseudoClasses.Add(":separator");
Focusable = false;
}
else if (e.OldValue is string oldValue && oldValue == "-")
{
PseudoClasses.Remove(":separator");
Focusable = true;
}
}
/// <summary>
/// Called when the <see cref="Icon"/> property changes.
/// </summary>

1
src/Avalonia.Controls/Panel.cs

@ -30,6 +30,7 @@ namespace Avalonia.Controls
/// </summary>
static Panel()
{
AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
}

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

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

11
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@ -9,12 +9,15 @@ using Avalonia.Threading;
namespace Avalonia.Controls.Platform
{
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer
{
public InternalPlatformThreadingInterface()
{
TlsCurrentThreadIsLoopThread = true;
StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
StartTimer(
DispatcherPriority.Render,
new TimeSpan(0, 0, 0, 0, 66),
() => Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)));
}
private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
@ -105,7 +108,7 @@ namespace Avalonia.Controls.Platform
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action<DispatcherPriority?> Signaled;
public event EventHandler<EventArgs> Tick;
public event Action<TimeSpan> Tick;
}
}
}

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

@ -197,13 +197,6 @@ namespace Avalonia.Controls.Presenters
}
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_dataTemplate = null;
}
/// <summary>
/// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
/// </summary>
@ -268,6 +261,7 @@ namespace Avalonia.Controls.Presenters
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_dataTemplate = null;
_createdChild = false;
InvalidateMeasure();
}

24
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -76,9 +76,23 @@ namespace Avalonia.Controls.Presenters
var firstIndex = ItemCount - panel.Children.Count;
RecycleContainersForMove(firstIndex - FirstIndex);
panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
panel.Children[0].Bounds.Height :
panel.Children[0].Bounds.Width;
double pixelOffset;
var child = panel.Children[0];
if (child.IsArrangeValid)
{
pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
child.Bounds.Height :
child.Bounds.Width;
}
else
{
pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
child.DesiredSize.Height :
child.DesiredSize.Width;
}
panel.PixelOffset = pixelOffset;
}
}
}
@ -402,6 +416,10 @@ namespace Avalonia.Controls.Presenters
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
var selector = Owner.MemberSelector;
//validate delta it should never overflow last index or generate index < 0
delta = MathUtilities.Clamp(delta, -FirstIndex, ItemCount - FirstIndex - panel.Children.Count);
var sign = delta < 0 ? -1 : 1;
var count = Math.Min(Math.Abs(delta), panel.Children.Count);
var move = count < panel.Children.Count;

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

@ -6,11 +6,12 @@ using System.Collections.Specialized;
using System.Linq;
using Avalonia.VisualTree;
using Avalonia.Media;
using Avalonia.Rendering;
namespace Avalonia.Controls.Primitives
{
// TODO: Need to track position of adorned elements and move the adorner if they move.
public class AdornerLayer : Panel
public class AdornerLayer : Panel, ICustomSimpleHitTest
{
public static AttachedProperty<Visual> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
@ -137,6 +138,11 @@ namespace Avalonia.Controls.Primitives
}
}
public bool HitTest(Point point)
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
private class AdornedElementInfo
{
public IDisposable Subscription { get; set; }

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

@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public override void EndInit()
{
base.EndInit();
if (--_updateCount == 0)
{
UpdateFinished();
}
base.EndInit();
}
/// <summary>
@ -871,8 +871,8 @@ namespace Avalonia.Controls.Primitives
RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue);
}
added = e.OldItems;
removed = e.NewItems;
added = e.NewItems;
removed = e.OldItems;
break;
}

7
src/Avalonia.Controls/ProgressBar.cs

@ -37,7 +37,8 @@ namespace Avalonia.Controls
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
}
public bool IsIndeterminate
@ -114,9 +115,9 @@ namespace Avalonia.Controls
}
}
private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
private void UpdateIndicatorWhenPropChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateIndicator(Bounds.Size);
}
}
}
}

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

@ -11,11 +11,19 @@ namespace Avalonia.Controls.Remote
{
public class RemoteWidget : Control
{
public enum SizingMode
{
Local,
Remote
}
private readonly IAvaloniaRemoteTransportConnection _connection;
private FrameMessage _lastFrame;
private WriteableBitmap _bitmap;
public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
{
Mode = SizingMode.Local;
_connection = connection;
_connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg));
_connection.Send(new ClientSupportedPixelFormatsMessage
@ -28,6 +36,8 @@ namespace Avalonia.Controls.Remote
});
}
public SizingMode Mode { get; set; }
private void OnMessage(object msg)
{
if (msg is FrameMessage frame)
@ -44,13 +54,17 @@ namespace Avalonia.Controls.Remote
protected override void ArrangeCore(Rect finalRect)
{
_connection.Send(new ClientViewportAllocatedMessage
if (Mode == SizingMode.Local)
{
Width = finalRect.Width,
Height = finalRect.Height,
DpiX = 96,
DpiY = 96 //TODO: Somehow detect the actual DPI
});
_connection.Send(new ClientViewportAllocatedMessage
{
Width = finalRect.Width,
Height = finalRect.Height,
DpiX = 10 * 96,
DpiY = 10 * 96 //TODO: Somehow detect the actual DPI
});
}
base.ArrangeCore(finalRect);
}

161
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -4,11 +4,15 @@ using System.Runtime.InteropServices;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using InputModifiers = Avalonia.Input.InputModifiers;
using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
@ -31,6 +35,67 @@ namespace Avalonia.Controls.Remote.Server
{
_transport = transport;
_transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
{
switch (button)
{
case Avalonia.Remote.Protocol.Input.MouseButton.Left:
return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Middle:
return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Right:
return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
default:
return RawMouseEventType.Move;
}
}
private static InputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
{
var result = InputModifiers.None;
foreach(var modifier in modifiers)
{
switch (modifier)
{
case Avalonia.Remote.Protocol.Input.InputModifiers.Control:
result |= InputModifiers.Control;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Alt:
result |= InputModifiers.Alt;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Shift:
result |= InputModifiers.Shift;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Windows:
result |= InputModifiers.Windows;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton:
result |= InputModifiers.LeftMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton:
result |= InputModifiers.MiddleMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton:
result |= InputModifiers.RightMouseButton;
break;
}
}
return result;
}
protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
@ -45,6 +110,16 @@ namespace Avalonia.Controls.Remote.Server
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if(obj is ClientRenderInfoMessage renderInfo)
{
lock(_lock)
{
_dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
_invalidated = true;
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
{
lock (_lock)
@ -82,6 +157,84 @@ namespace Avalonia.Controls.Remote.Server
_pendingAllocation = allocated;
}
}
if(obj is PointerMovedEventMessage pointer)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
RawMouseEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is PointerPressedEventMessage pressed)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
GetAvaloniaEventType(pressed.Button, true),
new Point(pressed.X, pressed.Y),
GetAvaloniaInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input);
}
if (obj is PointerPressedEventMessage released)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
GetAvaloniaEventType(released.Button, false),
new Point(released.X, released.Y),
GetAvaloniaInputModifiers(released.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is ScrollEventMessage scroll)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseWheelEventArgs(
MouseDevice,
0,
InputRoot,
new Point(scroll.X, scroll.Y),
new Vector(scroll.DeltaX, scroll.DeltaY),
GetAvaloniaInputModifiers(scroll.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is KeyEventMessage key)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice,
0,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaInputModifiers(key.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is TextInputEventMessage text)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice,
0,
text.Text));
}, DispatcherPriority.Input);
}
}
}
@ -102,6 +255,12 @@ namespace Avalonia.Controls.Remote.Server
FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
{
var scalingX = _dpi.X / 96.0;
var scalingY = _dpi.Y / 96.0;
width = (int)(width * scalingX);
height = (int)(height * scalingY);
var fmt = format ?? ProtocolPixelFormat.Rgba8888;
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
var data = new byte[width * height * bpp];
@ -169,5 +328,7 @@ namespace Avalonia.Controls.Remote.Server
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; }
}
}

4
src/Avalonia.Controls/TextBlock.cs

@ -6,6 +6,7 @@ using System.Reactive;
using System.Reactive.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -65,7 +66,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<IBrush> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>(
nameof(Foreground),
new SolidColorBrush(0xff000000),
Brushes.Black,
inherits: true);
/// <summary>
@ -100,6 +101,7 @@ namespace Avalonia.Controls
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender<TextBlock>(
BackgroundProperty,
ForegroundProperty,
FontWeightProperty,
FontSizeProperty,

383
src/Avalonia.Controls/TextBox.cs

@ -212,7 +212,10 @@ namespace Avalonia.Controls
{
if (!_ignoreTextChanges)
{
CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
var caretIndex = CaretIndex;
SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0);
SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0);
CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0);
if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
{
@ -365,186 +368,242 @@ namespace Avalonia.Controls
string text = Text ?? string.Empty;
int caretIndex = CaretIndex;
bool movement = false;
bool selection = false;
bool handled = false;
var modifiers = e.Modifiers;
switch (e.Key)
{
case Key.A:
if (modifiers == InputModifiers.Control)
{
SelectAll();
handled = true;
}
break;
case Key.C:
if (modifiers == InputModifiers.Control)
{
if (!IsPasswordBox)
{
Copy();
}
handled = true;
}
break;
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
case Key.X:
if (modifiers == InputModifiers.Control)
{
if (!IsPasswordBox)
{
Copy();
DeleteSelection();
}
handled = true;
}
break;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
case Key.V:
if (modifiers == InputModifiers.Control)
{
Paste();
handled = true;
}
if (Match(keymap.SelectAll))
{
SelectAll();
handled = true;
}
else if (Match(keymap.Copy))
{
if (!IsPasswordBox)
{
Copy();
}
break;
handled = true;
}
else if (Match(keymap.Cut))
{
if (!IsPasswordBox)
{
Copy();
DeleteSelection();
}
case Key.Z:
if (modifiers == InputModifiers.Control)
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Y:
if (modifiers == InputModifiers.Control)
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Left:
MoveHorizontal(-1, modifiers);
movement = true;
break;
handled = true;
}
else if (Match(keymap.Paste))
{
case Key.Right:
MoveHorizontal(1, modifiers);
movement = true;
break;
Paste();
handled = true;
}
else if (Match(keymap.Undo))
{
case Key.Up:
movement = MoveVertical(-1, modifiers);
break;
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
case Key.Down:
movement = MoveVertical(1, modifiers);
break;
handled = true;
}
else if (Match(keymap.Redo))
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
case Key.Home:
MoveHome(modifiers);
movement = true;
break;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfDocument))
{
MoveHome(true);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocument))
{
MoveEnd(true);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLine))
{
MoveHome(false);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
MoveEnd(false);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
{
MoveHome(true);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
{
MoveEnd(true);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
{
MoveHome(false);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{
MoveEnd(false);
movement = true;
selection = true;
handled = true;
}
else
{
bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
switch (e.Key)
{
case Key.Left:
MoveHorizontal(-1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection();
break;
case Key.End:
MoveEnd(modifiers);
movement = true;
break;
case Key.Right:
MoveHorizontal(1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection();
break;
case Key.Back:
if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace(modifiers);
}
case Key.Up:
movement = MoveVertical(-1);
selection = DetectSelection();
break;
if (!DeleteSelection() && CaretIndex > 0)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex > 1 &&
text[CaretIndex - 1] == '\n' &&
text[CaretIndex - 2] == '\r')
case Key.Down:
movement = MoveVertical(1);
selection = DetectSelection();
break;
case Key.Back:
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlBackspace();
}
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + text.Substring(caretIndex));
CaretIndex -= removedCharacters;
SelectionStart = SelectionEnd = CaretIndex;
}
handled = true;
break;
if (!DeleteSelection() && CaretIndex > 0)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex > 1 &&
text[CaretIndex - 1] == '\n' &&
text[CaretIndex - 2] == '\r')
{
removedCharacters = 2;
}
case Key.Delete:
if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
{
SetSelectionForControlDelete(modifiers);
}
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
text.Substring(caretIndex));
CaretIndex -= removedCharacters;
SelectionStart = SelectionEnd = CaretIndex;
}
if (!DeleteSelection() && caretIndex < text.Length)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex < text.Length - 1 &&
text[caretIndex + 1] == '\n' &&
text[caretIndex] == '\r')
handled = true;
break;
case Key.Delete:
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlDelete();
}
SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
}
handled = true;
break;
if (!DeleteSelection() && caretIndex < text.Length)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex < text.Length - 1 &&
text[caretIndex + 1] == '\n' &&
text[caretIndex] == '\r')
{
removedCharacters = 2;
}
SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters));
}
case Key.Enter:
if (AcceptsReturn)
{
HandleTextInput(NewLine);
handled = true;
}
break;
break;
case Key.Enter:
if (AcceptsReturn)
{
HandleTextInput(NewLine);
handled = true;
}
case Key.Tab:
if (AcceptsTab)
{
HandleTextInput("\t");
handled = true;
}
else
{
base.OnKeyDown(e);
}
break;
break;
case Key.Tab:
if (AcceptsTab)
{
HandleTextInput("\t");
handled = true;
}
else
{
base.OnKeyDown(e);
}
default:
handled = false;
break;
break;
default:
handled = false;
break;
}
}
if (movement && ((modifiers & InputModifiers.Shift) != 0))
if (movement && selection)
{
SelectionEnd = CaretIndex;
}
@ -663,12 +722,12 @@ namespace Avalonia.Controls
return result;
}
private void MoveHorizontal(int direction, InputModifiers modifiers)
private void MoveHorizontal(int direction, bool wholeWord)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) == 0)
if (!wholeWord)
{
var index = caretIndex + direction;
@ -706,7 +765,7 @@ namespace Avalonia.Controls
}
}
private bool MoveVertical(int count, InputModifiers modifiers)
private bool MoveVertical(int count)
{
var formattedText = _presenter.FormattedText;
var lines = formattedText.GetLines().ToList();
@ -729,12 +788,12 @@ namespace Avalonia.Controls
}
}
private void MoveHome(InputModifiers modifiers)
private void MoveHome(bool document)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) != 0)
if (document)
{
caretIndex = 0;
}
@ -759,12 +818,12 @@ namespace Avalonia.Controls
CaretIndex = caretIndex;
}
private void MoveEnd(InputModifiers modifiers)
private void MoveEnd(bool document)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) != 0)
if (document)
{
caretIndex = text.Length;
}
@ -879,17 +938,17 @@ namespace Avalonia.Controls
}
}
private void SetSelectionForControlBackspace(InputModifiers modifiers)
private void SetSelectionForControlBackspace()
{
SelectionStart = CaretIndex;
MoveHorizontal(-1, modifiers);
MoveHorizontal(-1, true);
SelectionEnd = CaretIndex;
}
private void SetSelectionForControlDelete(InputModifiers modifiers)
private void SetSelectionForControlDelete()
{
SelectionStart = CaretIndex;
MoveHorizontal(1, modifiers);
MoveHorizontal(1, true);
SelectionEnd = CaretIndex;
}

1
src/Avalonia.Controls/TopLevel.cs

@ -96,7 +96,6 @@ namespace Avalonia.Controls
_applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
Renderer = impl.CreateRenderer(this);
impl.SetInputRoot(this);

156
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -88,24 +88,82 @@ namespace Avalonia.Controls.Utils
}
else
{
var borderThickness = borders.Left;
var cornerRadius = (float)radii.TopLeft;
var rect = new Rect(size);
var borderThickness = borders.Top;
var top = borderThickness * 0.5;
var cornerRadius = (float)Math.Max(0, radii.TopLeft - borderThickness - top);
if (background != null)
{
context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
var topLeft = new Point(borders.Left, borders.Top);
var bottomRight = new Point(size.Width - borders.Right, size.Height - borders.Bottom);
var innerRect = new Rect(topLeft, bottomRight);
context.FillRectangle(background, innerRect, cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
var topLeft = new Point(top, top);
var bottomRight = new Point(size.Width - top, size.Height - top);
var outerRect = new Rect(topLeft, bottomRight);
context.DrawRectangle(new Pen(borderBrush, borderThickness), outerRect, (float)radii.TopLeft);
}
}
}
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
{
context.BeginFigure(keypoints.TopLeft, true);
// Top
context.LineTo(keypoints.TopRight);
// TopRight corner
var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
}
// Right
context.LineTo(keypoints.RightBottom);
// BottomRight corner
radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
// Bottom
context.LineTo(keypoints.BottomLeft);
// BottomLeft corner
radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
// Left
context.LineTo(keypoints.LeftTop);
// TopLeft corner
radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
context.EndFigure(true);
}
private class BorderGeometryKeypoints
{
{
internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
{
var left = 0.5 * borderThickness.Left;
@ -135,25 +193,24 @@ namespace Avalonia.Controls.Utils
}
else
{
leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
}
}
var leftTopX = boundRect.TopLeft.X;
var topLeftY = boundRect.TopLeft.Y;
var leftTopX = boundRect.TopLeft.X;
var topLeftY = boundRect.TopLeft.Y;
var topRightY = boundRect.TopLeft.Y;
var rightTopX = boundRect.Width + boundRect.TopLeft.X;
var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
var rightTopX = boundRect.Width + boundRect.TopLeft.X;
var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
var leftBottomX = boundRect.TopLeft.X;
var leftBottomX = boundRect.TopLeft.X;
LeftTop = new Point(leftTopX, leftTopY);
TopLeft = new Point(topLeftX, topLeftY);
@ -164,7 +221,7 @@ namespace Avalonia.Controls.Utils
BottomLeft = new Point(bottomLeftX, bottomLeftY);
LeftBottom = new Point(leftBottomX, leftBottomY);
//Fix overlap
// Fix overlap
if (TopLeft.X > TopRight.X)
{
var scaledX = topLeftX / (topLeftX + topRightX) * boundRect.Width;
@ -194,66 +251,21 @@ namespace Avalonia.Controls.Utils
}
}
internal Point LeftTop { get; private set; }
internal Point TopLeft { get; private set; }
internal Point TopRight { get; private set; }
internal Point RightTop { get; private set; }
internal Point RightBottom { get; private set; }
internal Point BottomRight { get; private set; }
internal Point BottomLeft { get; private set; }
internal Point LeftBottom { get; private set; }
}
internal Point LeftTop { get; }
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
{
context.BeginFigure(keypoints.TopLeft, true);
internal Point TopLeft { get; }
//Top
context.LineTo(keypoints.TopRight);
internal Point TopRight { get; }
//TopRight corner
var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
}
internal Point RightTop { get; }
//Right
context.LineTo(keypoints.RightBottom);
internal Point RightBottom { get; }
//BottomRight corner
radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
//Bottom
context.LineTo(keypoints.BottomLeft);
//BottomLeft corner
radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
internal Point BottomRight { get; }
//Left
context.LineTo(keypoints.LeftTop);
//TopLeft corner
radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
internal Point BottomLeft { get; }
context.EndFigure(true);
internal Point LeftBottom { get; }
}
}
}

2
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -18,7 +18,7 @@ namespace Avalonia.DesignerSupport
Control control;
using (PlatformManager.DesignerMode())
{
var loader = new AvaloniaXamlLoader();
var loader = new AvaloniaXamlLoader() {IsDesignMode = true};
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));

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

@ -53,10 +53,12 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(threading)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>()
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>();
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}

10
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -15,6 +15,8 @@ namespace Avalonia.DesignerSupport.Remote
{
private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
private static ClientRenderInfoMessage s_renderInfoMessage;
private static IAvaloniaRemoteTransportConnection s_transport;
class CommandLineArgs
{
@ -161,7 +163,8 @@ namespace Avalonia.DesignerSupport.Remote
PreviewerWindowingPlatform.PreFlightMessages = new List<object>
{
s_supportedPixelFormats,
s_viewportAllocatedMessage
s_viewportAllocatedMessage,
s_renderInfoMessage
};
}
@ -173,6 +176,11 @@ namespace Avalonia.DesignerSupport.Remote
s_supportedPixelFormats = formats;
RebuildPreFlight();
}
if (obj is ClientRenderInfoMessage renderInfo)
{
s_renderInfoMessage = renderInfo;
RebuildPreFlight();
}
if (obj is ClientViewportAllocatedMessage viewport)
{
s_viewportAllocatedMessage = viewport;

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

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reactive.Disposables;
@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; }
public Size MaxClientSize { get; }
public Size ClientSize { get; }
public double Scaling { get; }
public double Scaling { get; } = 1.0;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }

8
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -2,6 +2,9 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="Views\EventsView.xaml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
@ -17,4 +20,9 @@
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Rx.props" />
<ItemGroup>
<EmbeddedResource Update="Views\EventsView.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

1
src/Avalonia.Diagnostics/DevTools.xaml

@ -3,6 +3,7 @@
<TabStrip SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">
<TabStripItem Content="Logical Tree"/>
<TabStripItem Content="Visual Tree"/>
<TabStripItem Content="Events"/>
</TabStrip>
<ContentControl Content="{Binding Content}" Grid.Row="1"/>

23
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -10,6 +10,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia
@ -28,6 +29,7 @@ namespace Avalonia.Diagnostics
public class DevTools : UserControl
{
private static Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
private static HashSet<IRenderRoot> s_visualTreeRoots = new HashSet<IRenderRoot>();
private IDisposable _keySubscription;
public DevTools(IControl root)
@ -79,6 +81,7 @@ namespace Avalonia.Diagnostics
devToolsWindow.Closed += devTools.DevToolsClosed;
s_open.Add(control, devToolsWindow);
MarkAsDevTool(devToolsWindow);
devToolsWindow.Show();
}
}
@ -89,6 +92,7 @@ namespace Avalonia.Diagnostics
var devToolsWindow = (Window)sender;
var devTools = (DevTools)devToolsWindow.Content;
s_open.Remove((TopLevel)devTools.Root);
RemoveDevTool(devToolsWindow);
_keySubscription.Dispose();
devToolsWindow.Closed -= DevToolsClosed;
}
@ -116,5 +120,24 @@ namespace Avalonia.Diagnostics
}
}
}
/// <summary>
/// Marks a visual as part of the DevTools, so it can be excluded from event tracking.
/// </summary>
/// <param name="visual">The visual whose root is to be marked.</param>
public static void MarkAsDevTool(IVisual visual)
{
s_visualTreeRoots.Add(visual.GetVisualRoot());
}
public static void RemoveDevTool(IVisual visual)
{
s_visualTreeRoots.Remove(visual.GetVisualRoot());
}
public static bool BelongsToDevTool(IVisual visual)
{
return s_visualTreeRoots.Contains(visual.GetVisualRoot());
}
}
}

38
src/Avalonia.Diagnostics/Models/EventChainLink.cs

@ -0,0 +1,38 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics.Models
{
internal class EventChainLink
{
public EventChainLink(object handler, bool handled, RoutingStrategies route)
{
Contract.Requires<ArgumentNullException>(handler != null);
this.Handler = handler;
this.Handled = handled;
this.Route = route;
}
public object Handler { get; }
public string HandlerName
{
get
{
if (Handler is INamed named && !string.IsNullOrEmpty(named.Name))
{
return named.Name + " (" + Handler.GetType().Name + ")";
}
return Handler.GetType().Name;
}
}
public bool Handled { get; }
public RoutingStrategies Route { get; }
}
}

5
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@ -14,6 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels
private int _selectedTab;
private TreePageViewModel _logicalTree;
private TreePageViewModel _visualTree;
private EventsViewModel _eventsView;
private string _focusedControl;
private string _pointerOverElement;
@ -21,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
_eventsView = new EventsViewModel(root);
UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += (s, e) =>
@ -57,6 +59,9 @@ namespace Avalonia.Diagnostics.ViewModels
case 1:
Content = _visualTree;
break;
case 2:
Content = _eventsView;
break;
}
RaisePropertyChanged();

61
src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs

@ -0,0 +1,61 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics.ViewModels
{
internal class EventOwnerTreeNode : EventTreeNodeBase
{
private static readonly RoutedEvent[] s_defaultEvents = new RoutedEvent[]
{
Button.ClickEvent,
InputElement.KeyDownEvent,
InputElement.KeyUpEvent,
InputElement.TextInputEvent,
InputElement.PointerReleasedEvent,
InputElement.PointerPressedEvent,
};
public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsViewModel vm)
: base(null, type.Name)
{
this.Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name)
.Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) }));
this.IsExpanded = true;
}
public override bool? IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
if (_updateChildren && value != null)
{
foreach (var child in Children)
{
try
{
child._updateParent = false;
child.IsEnabled = value;
}
finally
{
child._updateParent = true;
}
}
}
}
}
}
}
}

98
src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs

@ -0,0 +1,98 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Diagnostics.Models;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal class EventTreeNode : EventTreeNodeBase
{
private RoutedEvent _event;
private EventsViewModel _parentViewModel;
private bool _isRegistered;
private FiredEvent _currentEvent;
public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsViewModel vm)
: base(parent, @event.Name)
{
Contract.Requires<ArgumentNullException>(@event != null);
Contract.Requires<ArgumentNullException>(vm != null);
this._event = @event;
this._parentViewModel = vm;
}
public override bool? IsEnabled
{
get => base.IsEnabled;
set
{
if (base.IsEnabled != value)
{
base.IsEnabled = value;
UpdateTracker();
if (Parent != null && _updateParent)
{
try
{
Parent._updateChildren = false;
Parent.UpdateChecked();
}
finally
{
Parent._updateChildren = true;
}
}
}
}
}
private void UpdateTracker()
{
if (IsEnabled.GetValueOrDefault() && !_isRegistered)
{
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true);
_isRegistered = true;
}
}
private void HandleEvent(object sender, RoutedEventArgs e)
{
if (!_isRegistered || IsEnabled == false)
return;
if (sender is IVisual v && DevTools.BelongsToDevTool(v))
return;
var s = sender;
var handled = e.Handled;
var route = e.Route;
Action handler = delegate
{
if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e))
{
_currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route));
_parentViewModel.RecordedEvents.Add(_currentEvent);
while (_parentViewModel.RecordedEvents.Count > 100)
_parentViewModel.RecordedEvents.RemoveAt(0);
}
else
{
_currentEvent.AddToChain(new EventChainLink(s, handled, route));
}
};
if (!Dispatcher.UIThread.CheckAccess())
Dispatcher.UIThread.Post(handler);
else
handler();
}
}
}

78
src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs

@ -0,0 +1,78 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class EventTreeNodeBase : ViewModelBase
{
internal bool _updateChildren = true;
internal bool _updateParent = true;
private bool _isExpanded;
private bool? _isEnabled = false;
public EventTreeNodeBase(EventTreeNodeBase parent, string text)
{
this.Parent = parent;
this.Text = text;
}
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children
{
get;
protected set;
}
public bool IsExpanded
{
get { return _isExpanded; }
set { RaiseAndSetIfChanged(ref _isExpanded, value); }
}
public virtual bool? IsEnabled
{
get { return _isEnabled; }
set { RaiseAndSetIfChanged(ref _isEnabled, value); }
}
public EventTreeNodeBase Parent
{
get;
}
public string Text
{
get;
private set;
}
internal void UpdateChecked()
{
IsEnabled = GetValue();
bool? GetValue()
{
if (Children == null)
return false;
bool? value = false;
for (int i = 0; i < Children.Count; i++)
{
if (i == 0)
{
value = Children[i].IsEnabled;
continue;
}
if (value != Children[i].IsEnabled)
{
value = null;
break;
}
}
return value;
}
}
}
}

60
src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs

@ -0,0 +1,60 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Interactivity;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{
internal class EventsViewModel : ViewModelBase
{
private readonly IControl _root;
private FiredEvent _selectedEvent;
public EventsViewModel(IControl root)
{
this._root = root;
this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered()
.GroupBy(e => e.OwnerType)
.OrderBy(e => e.Key.Name)
.Select(g => new EventOwnerTreeNode(g.Key, g, this))
.ToArray();
}
public EventTreeNodeBase[] Nodes { get; }
public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>();
public FiredEvent SelectedEvent
{
get => _selectedEvent;
set => RaiseAndSetIfChanged(ref _selectedEvent, value);
}
private void Clear()
{
RecordedEvents.Clear();
}
}
internal class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Brushes.LightGreen : Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

80
src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs

@ -0,0 +1,80 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.ObjectModel;
using Avalonia.Diagnostics.Models;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics.ViewModels
{
internal class FiredEvent : ViewModelBase
{
private RoutedEventArgs _eventArgs;
private EventChainLink _handledBy;
public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator)
{
Contract.Requires<ArgumentNullException>(eventArgs != null);
Contract.Requires<ArgumentNullException>(originator != null);
this._eventArgs = eventArgs;
this.Originator = originator;
AddToChain(originator);
}
public bool IsPartOfSameEventChain(RoutedEventArgs e)
{
return e == _eventArgs;
}
public RoutedEvent Event => _eventArgs.RoutedEvent;
public bool IsHandled => HandledBy?.Handled == true;
public ObservableCollection<EventChainLink> EventChain { get; } = new ObservableCollection<EventChainLink>();
public string DisplayText
{
get
{
if (IsHandled)
{
return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine +
$"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}";
}
return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}";
}
}
public EventChainLink Originator { get; }
public EventChainLink HandledBy
{
get { return _handledBy; }
set
{
if (_handledBy != value)
{
_handledBy = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(IsHandled));
RaisePropertyChanged(nameof(DisplayText));
}
}
}
public void AddToChain(object handler, bool handled, RoutingStrategies route)
{
AddToChain(new EventChainLink(handler, handled, route));
}
public void AddToChain(EventChainLink link)
{
EventChain.Add(link);
if (HandledBy == null && link.Handled)
HandledBy = link;
}
}
}

53
src/Avalonia.Diagnostics/Views/EventsView.xaml

@ -0,0 +1,53 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
<UserControl.Resources>
<vm:BoolToBrushConverter x:Key="boolToBrush" />
</UserControl.Resources>
<Grid ColumnDefinitions="*,4,3*">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" Grid.RowSpan="2">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:EventTreeNodeBase"
ItemsSource="{Binding Children}">
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsEnabled, Mode=TwoWay}" />
</TreeDataTemplate>
</TreeView.DataTemplates>
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
</TreeView>
<GridSplitter Width="4" Grid.Column="1" />
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2">
<ListBox Name="eventsList" Items="{Binding RecordedEvents}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Background="{Binding IsHandled, Converter={StaticResource boolToBrush}}" Text="{Binding DisplayText}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Height="4" Grid.Row="1" />
<DockPanel Grid.Row="2" LastChildFill="True">
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" />
<ListBox Items="{Binding SelectedEvent.EventChain}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="{Binding Handled, Converter={StaticResource boolToBrush}}">
<TextBlock Text="{Binding Route}" />
<TextBlock Text=": " />
<TextBlock Text="{Binding HandlerName}" />
<TextBlock Text=" handled: " />
<TextBlock Text="{Binding Handled}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3">
<Button Content="Clear" Margin="3" Command="{Binding Clear}" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

32
src/Avalonia.Diagnostics/Views/EventsView.xaml.cs

@ -0,0 +1,32 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using Avalonia.Controls;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Markup.Xaml;
namespace Avalonia.Diagnostics.Views
{
public class EventsView : UserControl
{
private ListBox _events;
public EventsView()
{
this.InitializeComponent();
_events = this.FindControl<ListBox>("events");
}
private void RecordedEvents_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
_events.ScrollIntoView(_events.Items.OfType<FiredEvent>().LastOrDefault());
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

4
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@ -43,7 +43,7 @@ namespace Avalonia
if (os == OperatingSystemType.WinNT)
LoadWin32();
else if(os==OperatingSystemType.OSX)
LoadMonoMac();
LoadAvaloniaNative();
else
LoadGtk3();
this.UseSkia();
@ -51,7 +51,7 @@ namespace Avalonia
return this;
}
void LoadMonoMac() => this.UseMonoMac();
void LoadAvaloniaNative() => this.UseAvaloniaNative();
void LoadWin32() => this.UseWin32();
void LoadGtk3() => this.UseGtk3();
}

2
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -8,8 +8,8 @@
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\Avalonia.Native\Avalonia.Native.csproj" />
<ProjectReference Include="..\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
</ItemGroup>

18
src/Avalonia.Input/Key.cs

@ -1020,5 +1020,23 @@ namespace Avalonia.Input
/// The key is used with another key to create a single combined character.
/// </summary>
DeadCharProcessed = 172,
/// <summary>
/// OSX Platform-specific Fn+Left key
/// </summary>
FnLeftArrow = 10001,
/// <summary>
/// OSX Platform-specific Fn+Right key
/// </summary>
FnRightArrow = 10002,
/// <summary>
/// OSX Platform-specific Fn+Up key
/// </summary>
FnUpArrow = 10003,
/// <summary>
/// OSX Platform-specific Fn+Down key
/// </summary>
FnDownArrow = 10004,
}
}

11
src/Avalonia.Input/KeyGesture.cs

@ -6,6 +6,17 @@ namespace Avalonia.Input
{
public sealed class KeyGesture : IEquatable<KeyGesture>
{
public KeyGesture()
{
}
public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
{
Key = key;
Modifiers = modifiers;
}
public bool Equals(KeyGesture other)
{
if (ReferenceEquals(null, other)) return false;

98
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@ -0,0 +1,98 @@
using System.Collections.Generic;
namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
{
}
public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
InputModifiers selectionModifiers = InputModifiers.Shift,
InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
WholeWordTextActionModifiers = wholeWordTextActionModifiers;
Copy = new List<KeyGesture>
{
new KeyGesture(Key.C, commandModifiers)
};
Cut = new List<KeyGesture>
{
new KeyGesture(Key.X, commandModifiers)
};
Paste = new List<KeyGesture>
{
new KeyGesture(Key.V, commandModifiers)
};
Undo = new List<KeyGesture>
{
new KeyGesture(Key.Z, commandModifiers)
};
Redo = new List<KeyGesture>
{
new KeyGesture(Key.Y, commandModifiers),
new KeyGesture(Key.Z, commandModifiers | selectionModifiers)
};
SelectAll = new List<KeyGesture>
{
new KeyGesture(Key.A, commandModifiers)
};
MoveCursorToTheStartOfLine = new List<KeyGesture>
{
new KeyGesture(Key.Home)
};
MoveCursorToTheEndOfLine = new List<KeyGesture>
{
new KeyGesture(Key.End)
};
MoveCursorToTheStartOfDocument = new List<KeyGesture>
{
new KeyGesture(Key.Home, commandModifiers)
};
MoveCursorToTheEndOfDocument = new List<KeyGesture>
{
new KeyGesture(Key.End, commandModifiers)
};
MoveCursorToTheStartOfLineWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.Home, selectionModifiers)
};
MoveCursorToTheEndOfLineWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.End, selectionModifiers)
};
MoveCursorToTheStartOfDocumentWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.Home, commandModifiers | selectionModifiers)
};
MoveCursorToTheEndOfDocumentWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.End, commandModifiers | selectionModifiers)
};
}
public InputModifiers CommandModifiers { get; set; }
public InputModifiers WholeWordTextActionModifiers { get; set; }
public InputModifiers SelectionModifiers { get; set; }
public List<KeyGesture> Copy { get; set; }
public List<KeyGesture> Cut { get; set; }
public List<KeyGesture> Paste { get; set; }
public List<KeyGesture> Undo { get; set; }
public List<KeyGesture> Redo { get; set; }
public List<KeyGesture> SelectAll { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfLine { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfLine { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfDocument { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocument { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfLineWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
}
}

4
src/Avalonia.Native.OSX/.gitignore

@ -0,0 +1,4 @@
build
Avalonia.Native.OSX.xcodeproj/xcuserdata
Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcuserdata

328
src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -0,0 +1,328 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB573DC4217605E400D389A2 /* gl.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB573DC3217605E400D389A2 /* gl.mm */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../Avalonia.Native/headers; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB573DC3217605E400D389A2 /* gl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = gl.mm; sourceTree = "<group>"; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; };
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
AB7A61EC2147C814003C5833 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup;
children = (
AB1E522B217613570091CD71 /* OpenGL.framework */,
AB661C1D2148230F00291242 /* AppKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
37A4E71A2178846A00EACBCD /* headers */,
AB573DC3217605E400D389A2 /* gl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
37E2330E21583241000CB7E2 /* KeyTransform.mm */,
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
);
sourceTree = "<group>";
};
AB7A61F02147C815003C5833 /* Products */ = {
isa = PBXGroup;
children = (
AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
AB7A61ED2147C814003C5833 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
AB7A61EE2147C814003C5833 /* Avalonia.Native.OSX */ = {
isa = PBXNativeTarget;
buildConfigurationList = AB7A61F82147C815003C5833 /* Build configuration list for PBXNativeTarget "Avalonia.Native.OSX" */;
buildPhases = (
AB7A61EB2147C814003C5833 /* Sources */,
AB7A61EC2147C814003C5833 /* Frameworks */,
AB7A61ED2147C814003C5833 /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = Avalonia.Native.OSX;
productName = Avalonia.Native.OSX;
productReference = AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */;
productType = "com.apple.product-type.library.dynamic";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
AB7A61E72147C814003C5833 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = Avalonia;
TargetAttributes = {
AB7A61EE2147C814003C5833 = {
CreatedOnToolsVersion = 8.3.2;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = AB7A61EA2147C814003C5833 /* Build configuration list for PBXProject "Avalonia.Native.OSX" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = AB7A61E62147C814003C5833;
productRefGroup = AB7A61F02147C815003C5833 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
AB7A61EE2147C814003C5833 /* Avalonia.Native.OSX */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
AB7A61EB2147C814003C5833 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB573DC4217605E400D389A2 /* gl.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
AB7A61F62147C815003C5833 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
AB7A61F72147C815003C5833 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
name = Release;
};
AB7A61F92147C815003C5833 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = ../Avalonia.Native/headers;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
AB7A61FA2147C815003C5833 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = ../Avalonia.Native/headers;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
AB7A61EA2147C814003C5833 /* Build configuration list for PBXProject "Avalonia.Native.OSX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AB7A61F62147C815003C5833 /* Debug */,
AB7A61F72147C815003C5833 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AB7A61F82147C815003C5833 /* Build configuration list for PBXNativeTarget "Avalonia.Native.OSX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AB7A61F92147C815003C5833 /* Debug */,
AB7A61FA2147C815003C5833 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = AB7A61E72147C814003C5833 /* Project object */;
}

7
src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Avalonia.Native.OSX.xcodeproj">
</FileRef>
</Workspace>

91
src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AB7A61EE2147C814003C5833"
BuildableName = "libAvalonia.Native.OSX.dylib"
BlueprintName = "Avalonia.Native.OSX"
ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "$PROJECT_DIR/../../samples/ControlCatalog"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
FilePath = "/usr/local/share/dotnet/dotnet">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AB7A61EE2147C814003C5833"
BuildableName = "libAvalonia.Native.OSX.dylib"
BlueprintName = "Avalonia.Native.OSX"
ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
<CommandLineArgument
argument = "bin/Debug/netcoreapp2.0/ControlCatalog.dll"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AB7A61EE2147C814003C5833"
BuildableName = "libAvalonia.Native.OSX.dylib"
BlueprintName = "Avalonia.Native.OSX"
ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

12
src/Avalonia.Native.OSX/KeyTransform.h

@ -0,0 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
#ifndef keytransform_h
#define keytransform_h
#include "common.h"
#include "key.h"
#include <map>
extern std::map<int, AvnKey> s_KeyMap;
#endif

241
src/Avalonia.Native.OSX/KeyTransform.mm

@ -0,0 +1,241 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
#include "KeyTransform.h"
const int kVK_ANSI_A = 0x00;
const int kVK_ANSI_S = 0x01;
const int kVK_ANSI_D = 0x02;
const int kVK_ANSI_F = 0x03;
const int kVK_ANSI_H = 0x04;
const int kVK_ANSI_G = 0x05;
const int kVK_ANSI_Z = 0x06;
const int kVK_ANSI_X = 0x07;
const int kVK_ANSI_C = 0x08;
const int kVK_ANSI_V = 0x09;
const int kVK_ANSI_B = 0x0B;
const int kVK_ANSI_Q = 0x0C;
const int kVK_ANSI_W = 0x0D;
const int kVK_ANSI_E = 0x0E;
const int kVK_ANSI_R = 0x0F;
const int kVK_ANSI_Y = 0x10;
const int kVK_ANSI_T = 0x11;
const int kVK_ANSI_1 = 0x12;
const int kVK_ANSI_2 = 0x13;
const int kVK_ANSI_3 = 0x14;
const int kVK_ANSI_4 = 0x15;
const int kVK_ANSI_6 = 0x16;
const int kVK_ANSI_5 = 0x17;
//const int kVK_ANSI_Equal = 0x18;
const int kVK_ANSI_9 = 0x19;
const int kVK_ANSI_7 = 0x1A;
const int kVK_ANSI_Minus = 0x1B;
const int kVK_ANSI_8 = 0x1C;
const int kVK_ANSI_0 = 0x1D;
const int kVK_ANSI_RightBracket = 0x1E;
const int kVK_ANSI_O = 0x1F;
const int kVK_ANSI_U = 0x20;
const int kVK_ANSI_LeftBracket = 0x21;
const int kVK_ANSI_I = 0x22;
const int kVK_ANSI_P = 0x23;
const int kVK_ANSI_L = 0x25;
const int kVK_ANSI_J = 0x26;
const int kVK_ANSI_Quote = 0x27;
const int kVK_ANSI_K = 0x28;
const int kVK_ANSI_Semicolon = 0x29;
const int kVK_ANSI_Backslash = 0x2A;
const int kVK_ANSI_Comma = 0x2B;
//const int kVK_ANSI_Slash = 0x2C;
const int kVK_ANSI_N = 0x2D;
const int kVK_ANSI_M = 0x2E;
const int kVK_ANSI_Period = 0x2F;
//const int kVK_ANSI_Grave = 0x32;
const int kVK_ANSI_KeypadDecimal = 0x41;
const int kVK_ANSI_KeypadMultiply = 0x43;
const int kVK_ANSI_KeypadPlus = 0x45;
const int kVK_ANSI_KeypadClear = 0x47;
const int kVK_ANSI_KeypadDivide = 0x4B;
const int kVK_ANSI_KeypadEnter = 0x4C;
const int kVK_ANSI_KeypadMinus = 0x4E;
//const int kVK_ANSI_KeypadEquals = 0x51;
const int kVK_ANSI_Keypad0 = 0x52;
const int kVK_ANSI_Keypad1 = 0x53;
const int kVK_ANSI_Keypad2 = 0x54;
const int kVK_ANSI_Keypad3 = 0x55;
const int kVK_ANSI_Keypad4 = 0x56;
const int kVK_ANSI_Keypad5 = 0x57;
const int kVK_ANSI_Keypad6 = 0x58;
const int kVK_ANSI_Keypad7 = 0x59;
const int kVK_ANSI_Keypad8 = 0x5B;
const int kVK_ANSI_Keypad9 = 0x5C;
const int kVK_Return = 0x24;
const int kVK_Tab = 0x30;
const int kVK_Space = 0x31;
const int kVK_Delete = 0x33;
const int kVK_Escape = 0x35;
const int kVK_Command = 0x37;
const int kVK_Shift = 0x38;
const int kVK_CapsLock = 0x39;
const int kVK_Option = 0x3A;
const int kVK_Control = 0x3B;
const int kVK_RightCommand = 0x36;
const int kVK_RightShift = 0x3C;
const int kVK_RightOption = 0x3D;
const int kVK_RightControl = 0x3E;
//const int kVK_Function = 0x3F;
const int kVK_F17 = 0x40;
const int kVK_VolumeUp = 0x48;
const int kVK_VolumeDown = 0x49;
const int kVK_Mute = 0x4A;
const int kVK_F18 = 0x4F;
const int kVK_F19 = 0x50;
const int kVK_F20 = 0x5A;
const int kVK_F5 = 0x60;
const int kVK_F6 = 0x61;
const int kVK_F7 = 0x62;
const int kVK_F3 = 0x63;
const int kVK_F8 = 0x64;
const int kVK_F9 = 0x65;
const int kVK_F11 = 0x67;
const int kVK_F13 = 0x69;
const int kVK_F16 = 0x6A;
const int kVK_F14 = 0x6B;
const int kVK_F10 = 0x6D;
const int kVK_F12 = 0x6F;
const int kVK_F15 = 0x71;
const int kVK_Help = 0x72;
const int kVK_Home = 0x73;
const int kVK_PageUp = 0x74;
const int kVK_ForwardDelete = 0x75;
const int kVK_F4 = 0x76;
const int kVK_End = 0x77;
const int kVK_F2 = 0x78;
const int kVK_PageDown = 0x79;
const int kVK_F1 = 0x7A;
const int kVK_LeftArrow = 0x7B;
const int kVK_RightArrow = 0x7C;
const int kVK_DownArrow = 0x7D;
const int kVK_UpArrow = 0x7E;
//const int kVK_ISO_Section = 0x0A;
//const int kVK_JIS_Yen = 0x5D;
//const int kVK_JIS_Underscore = 0x5E;
//const int kVK_JIS_KeypadComma = 0x5F;
//const int kVK_JIS_Eisu = 0x66;
//const int kVK_JIS_Kana = 0x68;
std::map<int, AvnKey> s_KeyMap =
{
{kVK_ANSI_A, A},
{kVK_ANSI_S, S},
{kVK_ANSI_D, D},
{kVK_ANSI_F, F},
{kVK_ANSI_H, H},
{kVK_ANSI_G, G},
{kVK_ANSI_Z, Z},
{kVK_ANSI_X, X},
{kVK_ANSI_C, C},
{kVK_ANSI_V, V},
{kVK_ANSI_B, B},
{kVK_ANSI_Q, Q},
{kVK_ANSI_W, W},
{kVK_ANSI_E, E},
{kVK_ANSI_R, R},
{kVK_ANSI_Y, Y},
{kVK_ANSI_T, T},
{kVK_ANSI_1, D1},
{kVK_ANSI_2, D2},
{kVK_ANSI_3, D3},
{kVK_ANSI_4, D4},
{kVK_ANSI_6, D6},
{kVK_ANSI_5, D5},
//{kVK_ANSI_Equal, ?},
{kVK_ANSI_9, D9},
{kVK_ANSI_7, D7},
{kVK_ANSI_Minus, OemMinus},
{kVK_ANSI_8, D8},
{kVK_ANSI_0, D0},
{kVK_ANSI_RightBracket, OemCloseBrackets},
{kVK_ANSI_O, O},
{kVK_ANSI_U, U},
{kVK_ANSI_LeftBracket, OemOpenBrackets},
{kVK_ANSI_I, I},
{kVK_ANSI_P, P},
{kVK_ANSI_L, L},
{kVK_ANSI_J, J},
{kVK_ANSI_Quote, OemQuotes},
{kVK_ANSI_K, AvnKeyK},
{kVK_ANSI_Semicolon, OemSemicolon},
{kVK_ANSI_Backslash, OemBackslash},
{kVK_ANSI_Comma, OemComma},
//{kVK_ANSI_Slash, ?},
{kVK_ANSI_N, N},
{kVK_ANSI_M, M},
{kVK_ANSI_Period, OemPeriod},
//{kVK_ANSI_Grave, ?},
{kVK_ANSI_KeypadDecimal, Decimal},
{kVK_ANSI_KeypadMultiply, Multiply},
{kVK_ANSI_KeypadPlus, OemPlus},
{kVK_ANSI_KeypadClear, AvnKeyClear},
{kVK_ANSI_KeypadDivide, Divide},
{kVK_ANSI_KeypadEnter, AvnKeyEnter},
{kVK_ANSI_KeypadMinus, OemMinus},
//{kVK_ANSI_KeypadEquals, ?},
{kVK_ANSI_Keypad0, NumPad0},
{kVK_ANSI_Keypad1, NumPad1},
{kVK_ANSI_Keypad2, NumPad2},
{kVK_ANSI_Keypad3, NumPad3},
{kVK_ANSI_Keypad4, NumPad4},
{kVK_ANSI_Keypad5, NumPad5},
{kVK_ANSI_Keypad6, NumPad6},
{kVK_ANSI_Keypad7, NumPad7},
{kVK_ANSI_Keypad8, NumPad8},
{kVK_ANSI_Keypad9, NumPad9},
{kVK_Return, AvnKeyReturn},
{kVK_Tab, AvnKeyTab},
{kVK_Space, Space},
{kVK_Delete, AvnKeyBack},
{kVK_Escape, Escape},
{kVK_Command, LWin},
{kVK_Shift, LeftShift},
{kVK_CapsLock, AvnKeyCapsLock},
{kVK_Option, LeftAlt},
{kVK_Control, LeftCtrl},
{kVK_RightCommand, RWin},
{kVK_RightShift, RightShift},
{kVK_RightOption, RightAlt},
{kVK_RightControl, RightCtrl},
//{kVK_Function, ?},
{kVK_F17, F17},
{kVK_VolumeUp, VolumeUp},
{kVK_VolumeDown, VolumeDown},
{kVK_Mute, VolumeMute},
{kVK_F18, F18},
{kVK_F19, F19},
{kVK_F20, F20},
{kVK_F5, F5},
{kVK_F6, F6},
{kVK_F7, F7},
{kVK_F3, F3},
{kVK_F8, F8},
{kVK_F9, F9},
{kVK_F11, F11},
{kVK_F13, F13},
{kVK_F16, F16},
{kVK_F14, F14},
{kVK_F10, F10},
{kVK_F12, F12},
{kVK_F15, F15},
{kVK_Help, Help},
{kVK_Home, Home},
{kVK_PageUp, PageUp},
{kVK_ForwardDelete, Delete},
{kVK_F4, F4},
{kVK_End, End},
{kVK_F2, F2},
{kVK_PageDown, PageDown},
{kVK_F1, F1},
{kVK_LeftArrow, Left},
{kVK_RightArrow, Right},
{kVK_DownArrow, Down},
{kVK_UpArrow, Up}
};

51
src/Avalonia.Native.OSX/Screens.mm

@ -0,0 +1,51 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
#include "common.h"
class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT GetScreenCount (int* ret)
{
@autoreleasepool
{
*ret = (int)[NSScreen screens].count;
return S_OK;
}
}
virtual HRESULT GetScreen (int index, AvnScreen* ret)
{
@autoreleasepool
{
if(index < 0 || index >= [NSScreen screens].count)
{
return E_INVALIDARG;
}
auto screen = [[NSScreen screens] objectAtIndex:index];
ret->Bounds.X = [screen frame].origin.x;
ret->Bounds.Y = [screen frame].origin.y;
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = [screen visibleFrame].origin.y;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->Primary = index == 0;
return S_OK;
}
}
};
extern IAvnScreens* CreateScreens()
{
return new Screens();
}

262
src/Avalonia.Native.OSX/SystemDialogs.mm

@ -0,0 +1,262 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
#include "common.h"
#include "window.h"
class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
{
public:
FORWARD_IUNKNOWN()
virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
const char* title,
const char* initialDirectory)
{
@autoreleasepool
{
auto panel = [NSOpenPanel openPanel];
panel.canChooseDirectories = true;
panel.canCreateDirectories = true;
panel.canChooseFiles = false;
if(title != nullptr)
{
panel.title = [NSString stringWithUTF8String:title];
}
if(initialDirectory != nullptr)
{
auto directoryString = [NSString stringWithUTF8String:initialDirectory];
panel.directoryURL = [NSURL fileURLWithPath:directoryString];
}
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
auto urls = [panel URLs];
if(urls.count > 0)
{
void* strings[urls.count];
for(int i = 0; i < urls.count; i++)
{
auto url = [urls objectAtIndex:i];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
strings[i] = (void*)[string UTF8String];
}
events->OnCompleted((int)urls.count, &strings[0]);
[panel orderOut:panel];
if(parentWindowHandle != nullptr)
{
auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
}
return;
}
}
events->OnCompleted(0, nullptr);
};
if(parentWindowHandle != nullptr)
{
auto windowBase = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
}
else
{
[panel beginWithCompletionHandler: handler];
}
}
}
virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
bool allowMultiple,
const char* title,
const char* initialDirectory,
const char* initialFile,
const char* filters)
{
@autoreleasepool
{
auto panel = [NSOpenPanel openPanel];
panel.allowsMultipleSelection = allowMultiple;
if(title != nullptr)
{
panel.title = [NSString stringWithUTF8String:title];
}
if(initialDirectory != nullptr)
{
auto directoryString = [NSString stringWithUTF8String:initialDirectory];
panel.directoryURL = [NSURL fileURLWithPath:directoryString];
}
if(initialFile != nullptr)
{
panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile];
}
if(filters != nullptr)
{
auto filtersString = [NSString stringWithUTF8String:filters];
if(filtersString.length > 0)
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
panel.allowedFileTypes = allowedTypes;
}
}
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
auto urls = [panel URLs];
if(urls.count > 0)
{
void* strings[urls.count];
for(int i = 0; i < urls.count; i++)
{
auto url = [urls objectAtIndex:i];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
strings[i] = (void*)[string UTF8String];
}
events->OnCompleted((int)urls.count, &strings[0]);
[panel orderOut:panel];
if(parentWindowHandle != nullptr)
{
auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
}
return;
}
}
events->OnCompleted(0, nullptr);
};
if(parentWindowHandle != nullptr)
{
auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[panel beginSheetModalForWindow:windowHolder->GetNSWindow() completionHandler:handler];
}
else
{
[panel beginWithCompletionHandler: handler];
}
}
}
virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
const char* title,
const char* initialDirectory,
const char* initialFile,
const char* filters)
{
@autoreleasepool
{
auto panel = [NSSavePanel savePanel];
if(title != nullptr)
{
panel.title = [NSString stringWithUTF8String:title];
}
if(initialDirectory != nullptr)
{
auto directoryString = [NSString stringWithUTF8String:initialDirectory];
panel.directoryURL = [NSURL fileURLWithPath:directoryString];
}
if(initialFile != nullptr)
{
panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile];
}
if(filters != nullptr)
{
auto filtersString = [NSString stringWithUTF8String:filters];
if(filtersString.length > 0)
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
panel.allowedFileTypes = allowedTypes;
}
}
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
void* strings[1];
auto url = [panel URL];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
strings[0] = (void*)[string UTF8String];
events->OnCompleted(1, &strings[0]);
[panel orderOut:panel];
if(parentWindowHandle != nullptr)
{
auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
}
return;
}
events->OnCompleted(0, nullptr);
};
if(parentWindowHandle != nullptr)
{
auto windowBase = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
[panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
}
else
{
[panel beginWithCompletionHandler: handler];
}
}
}
};
extern IAvnSystemDialogs* CreateSystemDialogs()
{
return new SystemDialogs();
}

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

Loading…
Cancel
Save