Browse Source

Merge branch 'master' into feature-devtool-search-regex

pull/4529/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
87fc1ca442
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 130
      Avalonia.sln
  2. 5
      build/iOSWorkarounds.props
  3. 2
      dirs.proj
  4. 2
      global.json
  5. 23
      samples/ControlCatalog.iOS/AppDelegate.cs
  6. 2
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  7. 1
      src/Avalonia.Themes.Default/Window.xaml
  8. 1
      src/Avalonia.Themes.Fluent/Window.xaml
  9. 5
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  10. 6
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  11. 4
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  12. 17
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  13. 19
      src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
  14. 49
      src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
  15. 58
      src/iOS/Avalonia.iOS/AvaloniaRootViewController.cs
  16. 32
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  17. 143
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  18. 26
      src/iOS/Avalonia.iOS/AvaloniaWindow.cs
  19. 7
      src/iOS/Avalonia.iOS/Boilerplate/AppBuilder.cs
  20. 19
      src/iOS/Avalonia.iOS/Boilerplate/RuntimePlatform.cs
  21. 595
      src/iOS/Avalonia.iOS/Boilerplate/Shared.cs
  22. 6
      src/iOS/Avalonia.iOS/ClipboardImpl.cs
  23. 11
      src/iOS/Avalonia.iOS/CursorFactory.cs
  24. 32
      src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs
  25. 37
      src/iOS/Avalonia.iOS/DisplayLinkTimer.cs
  26. 69
      src/iOS/Avalonia.iOS/EaglDisplay.cs
  27. 94
      src/iOS/Avalonia.iOS/EaglLayerSurface.cs
  28. 27
      src/iOS/Avalonia.iOS/EmbeddableImpl.cs
  29. 60
      src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
  30. 4
      src/iOS/Avalonia.iOS/Extensions.cs
  31. 143
      src/iOS/Avalonia.iOS/LayerFbo.cs
  32. 50
      src/iOS/Avalonia.iOS/Platform.cs
  33. 46
      src/iOS/Avalonia.iOS/PlatformIconLoader.cs
  34. 14
      src/iOS/Avalonia.iOS/PlatformSettings.cs
  35. 33
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs
  36. 17
      src/iOS/Avalonia.iOS/RuntimeInfo.cs
  37. 7
      src/iOS/Avalonia.iOS/SingleViewLifetime.cs
  38. 24
      src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs
  39. 147
      src/iOS/Avalonia.iOS/Specific/KeyboardEventsHelper.cs
  40. 60
      src/iOS/Avalonia.iOS/Stubs.cs
  41. 145
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  42. 52
      src/iOS/Avalonia.iOS/TouchHandler.cs
  43. 23
      src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs
  44. 49
      src/iOS/Avalonia.iOS/iOSPlatform.cs
  45. 39
      src/iOS/Avalonia.iOSTestApplication/AppDelegate.cs
  46. 191
      src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj
  47. 5
      src/iOS/Avalonia.iOSTestApplication/Entitlements.plist
  48. 4
      src/iOS/Avalonia.iOSTestApplication/GettingStarted.Xamarin
  49. 38
      src/iOS/Avalonia.iOSTestApplication/Info.plist
  50. 20
      src/iOS/Avalonia.iOSTestApplication/Main.cs
  51. 36
      src/iOS/Avalonia.iOSTestApplication/Properties/AssemblyInfo.cs
  52. BIN
      src/iOS/Avalonia.iOSTestApplication/Resources/Default-568h@2x.png
  53. 6
      src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml
  54. 20
      src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml.cs
  55. 20
      src/iOS/Avalonia.iOSTestApplication/SimpleControl.cs
  56. 26
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

130
Avalonia.sln

@ -87,8 +87,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CF
EndProject
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LeakTests", "tests\Avalonia.LeakTests\Avalonia.LeakTests.csproj", "{E1AA3DBF-9056-4530-9376-18119A7A3FFE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.UnitTests", "tests\Avalonia.UnitTests\Avalonia.UnitTests.csproj", "{88060192-33D5-4932-B0F9-8BD2763E857D}"
@ -124,6 +122,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
build\ApiDiff.props = build\ApiDiff.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props
@ -150,14 +149,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\System.Memory.props = build\System.Memory.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
build\ApiDiff.props = build\ApiDiff.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
ProjectSection(SolutionItems) = preProject
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
build\BuildTargets.targets = build\BuildTargets.targets
build\LegacyProject.targets = build\LegacyProject.targets
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
@ -218,17 +216,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
@ -951,26 +948,6 @@ Global
{4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhone.Build.0 = Release|Any CPU
{4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.Build.0 = AppStore|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.ActiveCfg = Debug|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.Build.0 = Debug|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|Any CPU.ActiveCfg = Release|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.ActiveCfg = Release|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.Build.0 = Release|iPhone
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1843,54 +1820,6 @@ Global
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2011,6 +1940,54 @@ Global
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.Build.0 = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.Build.0 = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2061,7 +2038,6 @@ Global
{7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
{FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
{4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}
{8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}
{E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{88060192-33D5-4932-B0F9-8BD2763E857D} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}

5
build/iOSWorkarounds.props

@ -1,5 +0,0 @@
<Project>
<PropertyGroup Condition="'$(iOSRoslynPathHackRequired)' == 'true'">
<CscToolPath>$(MSBuildToolsPath)\..\Roslyn</CscToolPath>
</PropertyGroup>
</Project>

2
dirs.proj

@ -13,7 +13,7 @@
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS') Or $([MSBuild]::IsOsPlatform('Windows')) ">
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS')">
<ProjectReference Remove="src/iOS/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" />
</ItemGroup>

2
global.json

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

23
samples/ControlCatalog.iOS/AppDelegate.cs

@ -11,25 +11,8 @@ namespace ControlCatalog
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
public partial class AppDelegate : AvaloniaAppDelegate<App>
{
public override UIWindow Window { get; set; }
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
{
AppBuilder.Configure<App>()
.UseiOS()
.UseSkia().SetupWithoutStarting();
Window = new AvaloniaWindow() {Content = new MainView(), StatusBarColor = Colors.LightSteelBlue};
Window.MakeKeyAndVisible();
return true;
}
}
}
}

2
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@ -175,6 +175,6 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<Import Project="..\..\build\iOSWorkarounds.props" />
<Import Project="..\..\build\LegacyProject.targets" />
<Import Project="..\..\build\SkiaSharp.props" />
</Project>

1
src/Avalonia.Themes.Default/Window.xaml

@ -8,6 +8,7 @@
<Panel>
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
<Panel Background="Transparent" Margin="{TemplateBinding WindowDecorationMargin}" />
<VisualLayerManager>
<VisualLayerManager.ChromeOverlayLayer>
<TitleBar />

1
src/Avalonia.Themes.Fluent/Window.xaml

@ -9,6 +9,7 @@
<Panel>
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
<Panel Background="Transparent" Margin="{TemplateBinding WindowDecorationMargin}" />
<VisualLayerManager>
<VisualLayerManager.ChromeOverlayLayer>
<TitleBar />

5
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -0,0 +1,5 @@
Compat issues with assembly Avalonia.Visuals:
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
Total Issues: 3

6
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -221,7 +221,7 @@ namespace Avalonia.Media.TextFormatting
while (currentPosition < _text.Length)
{
var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
_paragraphProperties, previousLine?.LineBreak);
_paragraphProperties, previousLine?.TextLineBreak);
currentPosition += textLine.TextRange.Length;
@ -230,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) &&
height + textLine.LineMetrics.Size.Height > MaxHeight)
{
if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None)
if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None)
{
var collapsedLine =
previousLine.Collapse(GetCollapsingProperties(MaxWidth));
@ -255,7 +255,7 @@ namespace Avalonia.Media.TextFormatting
previousLine = textLine;
if (currentPosition != _text.Length || textLine.LineBreak == null)
if (currentPosition != _text.Length || textLine.TextLineBreak == null)
{
continue;
}

4
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@ -35,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
/// Gets the state of the line when broken by line breaking process.
/// </summary>
/// <returns>
/// A <see cref="LineBreak"/> value that represents the line break.
/// A <see cref="TextLineBreak"/> value that represents the line break.
/// </returns>
public abstract TextLineBreak LineBreak { get; }
public abstract TextLineBreak TextLineBreak { get; }
/// <summary>
/// Gets a value that indicates whether the line is collapsed.

17
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Media.TextFormatting
{
_textRuns = textRuns;
LineMetrics = lineMetrics;
LineBreak = lineBreak;
TextLineBreak = lineBreak;
HasCollapsed = hasCollapsed;
}
@ -27,7 +27,7 @@ namespace Avalonia.Media.TextFormatting
public override TextLineMetrics LineMetrics { get; }
/// <inheritdoc/>
public override TextLineBreak LineBreak { get; }
public override TextLineBreak TextLineBreak { get; }
/// <inheritdoc/>
public override bool HasCollapsed { get; }
@ -122,7 +122,7 @@ namespace Avalonia.Media.TextFormatting
textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
LineMetrics.TextBaseline, textRange, false);
return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true);
}
availableWidth -= currentRun.GlyphRun.Bounds.Width;
@ -268,6 +268,17 @@ namespace Avalonia.Media.TextFormatting
var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength ==
TextRange.Length;
var characterIndex = codepointIndex - run.Text.Start;
var codepoint = Codepoint.ReadAt(run.GlyphRun.Characters, characterIndex, out _);
if (codepoint.IsBreakChar)
{
foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(codepointIndex - 1, out _);
isAtEnd = true;
}
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);

19
src/iOS/Avalonia.iOS/Avalonia.iOS.csproj

@ -1,15 +1,24 @@
<Project>
<Project>
<Import Project="Sdk.props" Sdk="MSBuild.Sdk.Extras" />
<PropertyGroup>
<TargetFramework>xamarin.ios10</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Update="Boilerplate\Shared.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Boilerplate\RuntimePlatform.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="OpenTK-1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" ExcludeAssets="All" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<Import Project="..\..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
<Import Project="..\..\..\build\Rx.props" />
<Import Project="Sdk.targets" Sdk="MSBuild.Sdk.Extras" />
<Import Project="..\..\..\build\iOSWorkarounds.props" />
</Project>

49
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@ -0,0 +1,49 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Foundation;
using UIKit;
namespace Avalonia.iOS
{
public class AvaloniaAppDelegate<TApp> : UIResponder, IUIApplicationDelegate
where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
[Export("window")]
public UIWindow Window { get; set; }
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
var builder = AppBuilder.Configure<TApp>();
CustomizeAppBuilder(builder);
var lifetime = new Lifetime();
builder.AfterSetup(_ =>
{
Window = new UIWindow();
var view = new AvaloniaView();
lifetime.View = view;
Window.RootViewController = new UIViewController
{
View = view
};
});
builder.SetupWithLifetime(lifetime);
Window.Hidden = false;
return true;
}
class Lifetime : ISingleViewApplicationLifetime
{
public AvaloniaView View;
public Control MainView
{
get => View.Content;
set => View.Content = value;
}
}
}
}

58
src/iOS/Avalonia.iOS/AvaloniaRootViewController.cs

@ -1,58 +0,0 @@
using Avalonia.Media;
using CoreGraphics;
using UIKit;
namespace Avalonia.iOS
{
class AvaloniaRootViewController : UIViewController
{
private object _content;
private Color _statusBarColor = Colors.White;
public object Content
{
get { return _content; }
set
{
_content = value;
var view = (View as AvaloniaView);
if (view != null)
view.Content = value;
}
}
public Color StatusBarColor
{
get { return _statusBarColor; }
set
{
_statusBarColor = value;
var view = (View as AvaloniaView);
if (view != null)
view.BackgroundColor = value.ToUiColor();
}
}
void AutoFit()
{
var needFlip = !UIDevice.CurrentDevice.CheckSystemVersion(8, 0) &&
(InterfaceOrientation == UIInterfaceOrientation.LandscapeLeft
|| InterfaceOrientation == UIInterfaceOrientation.LandscapeRight);
// Bounds here (if top level) needs to correspond with the rendertarget
var frame = UIScreen.MainScreen.Bounds;
if (needFlip)
frame = new CGRect(frame.Y, frame.X, frame.Height, frame.Width);
((AvaloniaView) View).Padding =
new Thickness(0, UIApplication.SharedApplication.StatusBarFrame.Size.Height, 0, 0);
View.Frame = frame;
}
public override void LoadView()
{
View = new AvaloniaView() {Content = Content, BackgroundColor = _statusBarColor.ToUiColor()};
UIApplication.Notifications.ObserveDidChangeStatusBarOrientation(delegate { AutoFit(); });
UIApplication.Notifications.ObserveDidChangeStatusBarFrame(delegate { AutoFit(); });
AutoFit();
}
}
}

32
src/iOS/Avalonia.iOS/AvaloniaView.Text.cs

@ -0,0 +1,32 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Avalonia.iOS
{
[Adopts("UIKeyInput")]
public partial class AvaloniaView
{
public override bool CanBecomeFirstResponder => true;
[Export("hasText")] public bool HasText => false;
[Export("insertText:")]
public void InsertText(string text) =>
_topLevelImpl.Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice.Instance,
0, InputRoot, text));
[Export("deleteBackward")]
public void DeleteBackward()
{
// TODO: pass this through IME infrastructure instead of emulating a backspace press
_topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
0, InputRoot, RawKeyEventType.KeyDown, Key.Back, RawInputModifiers.None));
_topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
0, InputRoot, RawKeyEventType.KeyUp, Key.Back, RawInputModifiers.None));
}
}
}

143
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -1,48 +1,139 @@
using Avalonia.Controls.Embedding;
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using CoreAnimation;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using OpenGLES;
using UIKit;
namespace Avalonia.iOS
{
public class AvaloniaView : UIView
public partial class AvaloniaView : UIView
{
private EmbeddableImpl _impl;
private EmbeddableControlRoot _root;
private Thickness _padding;
internal IInputRoot InputRoot { get; private set; }
private TopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel;
private TouchHandler _touches;
public Thickness Padding
public AvaloniaView()
{
_topLevelImpl = new TopLevelImpl(this);
_touches = new TouchHandler(this, _topLevelImpl);
_topLevel = new EmbeddableControlRoot(_topLevelImpl);
_topLevel.Prepare();
_topLevel.Renderer.Start();
var l = (CAEAGLLayer) Layer;
l.ContentsScale = UIScreen.MainScreen.Scale;
l.Opaque = true;
l.DrawableProperties = new NSDictionary(
EAGLDrawableProperty.RetainedBacking, false,
EAGLDrawableProperty.ColorFormat, EAGLColorFormat.RGBA8
);
_topLevelImpl.Surfaces = new[] {new EaglLayerSurface(l)};
MultipleTouchEnabled = true;
}
internal class TopLevelImpl : ITopLevelImpl
{
get { return _padding; }
set
private readonly AvaloniaView _view;
public AvaloniaView View => _view;
public TopLevelImpl(AvaloniaView view)
{
_view = view;
}
public void Dispose()
{
_padding = value;
SetNeedsLayout();
// No-op
}
public IRenderer CreateRenderer(IRenderRoot root) => new DeferredRenderer(root,
AvaloniaLocator.Current.GetService<IRenderLoop>());
public void Invalidate(Rect rect)
{
// No-op
}
public void SetInputRoot(IInputRoot inputRoot)
{
_view.InputRoot = inputRoot;
}
public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
public PixelPoint PointToScreen(Point point) => new PixelPoint((int) point.X, (int) point.Y);
public void SetCursor(IPlatformHandle cursor)
{
// no-op
}
public IPopupImpl CreatePopup()
{
// In-window popups
return null;
}
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
// No-op
}
public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height);
public double RenderScaling => _view.ContentScaleFactor;
public IEnumerable<object> Surfaces { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action Closed { get; set; }
public Action LostFocus { get; set; }
// legacy no-op
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public WindowTransparencyLevel TransparencyLevel { get; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
new AcrylicPlatformCompensationLevels();
}
public AvaloniaView()
[Export("layerClass")]
public static Class LayerClass()
{
_impl = new EmbeddableImpl();
AddSubview(_impl);
BackgroundColor = UIColor.White;
AutoresizingMask = UIViewAutoresizing.All;
_root = new EmbeddableControlRoot(_impl);
_root.Prepare();
return new Class(typeof(CAEAGLLayer));
}
public override void TouchesBegan(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
public override void TouchesMoved(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
public override void TouchesEnded(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
public override void TouchesCancelled(NSSet touches, UIEvent evt) => _touches.Handle(touches, evt);
public override void LayoutSubviews()
{
_impl.Frame = new CGRect(Padding.Left, Padding.Top,
Frame.Width - Padding.Left - Padding.Right,
Frame.Height - Padding.Top - Padding.Bottom);
_topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize);
base.LayoutSubviews();
}
public object Content
public Control Content
{
get { return _root.Content; }
set { _root.Content = value; }
get => (Control)_topLevel.Content;
set => _topLevel.Content = value;
}
}
}
}

26
src/iOS/Avalonia.iOS/AvaloniaWindow.cs

@ -1,26 +0,0 @@
using Avalonia.Media;
using UIKit;
namespace Avalonia.iOS
{
public sealed class AvaloniaWindow : UIWindow
{
readonly AvaloniaRootViewController _controller = new AvaloniaRootViewController();
public object Content
{
get { return _controller.Content; }
set { _controller.Content = value; }
}
public AvaloniaWindow() : base(UIScreen.MainScreen.Bounds)
{
RootViewController = _controller;
}
public Color StatusBarColor
{
get { return _controller.StatusBarColor; }
set { _controller.StatusBarColor = value; }
}
}
}

7
src/iOS/Avalonia.iOS/AppBuilder.cs → src/iOS/Avalonia.iOS/Boilerplate/AppBuilder.cs

@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.iOS;
using Avalonia.Shared.PlatformSupport;
namespace Avalonia
@ -6,9 +7,9 @@ namespace Avalonia
public class AppBuilder : AppBuilderBase<AppBuilder>
{
public AppBuilder() : base(new StandardRuntimePlatform(),
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly))
b => StandardRuntimePlatformServices.Register(b.ApplicationType.Assembly))
{
this.UseSkia().UseWindowingSubsystem(iOS.Platform.Register);
}
}
}
}

19
src/iOS/Avalonia.iOS/Boilerplate/RuntimePlatform.cs

@ -0,0 +1,19 @@
using Avalonia.Platform;
namespace Avalonia.Shared.PlatformSupport
{
partial class StandardRuntimePlatform
{
public RuntimePlatformInfo GetRuntimeInfo()
{
return new RuntimePlatformInfo
{
IsDesktop = false,
IsMobile = true,
IsMono = true,
IsUnix = true,
OperatingSystem = OperatingSystemType.iOS
};
}
}
}

595
src/iOS/Avalonia.iOS/Boilerplate/Shared.cs

@ -0,0 +1,595 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using Avalonia.Utilities;
namespace Avalonia.Shared.PlatformSupport
{
static class StandardRuntimePlatformServices
{
public static void Register(Assembly assembly = null)
{
var standardPlatform = new StandardRuntimePlatform();
AssetLoader.RegisterResUriParsers();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToConstant(standardPlatform)
.Bind<IAssetLoader>().ToConstant(new AssetLoader(assembly))
.Bind<IDynamicLibraryLoader>().ToConstant(
#if __IOS__
new IOSLoader()
#else
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IDynamicLibraryLoader)new Win32Loader()
: new UnixLoader()
#endif
);
}
}
internal partial class StandardRuntimePlatform : IRuntimePlatform
{
public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
{
return new Timer(_ => tick(), null, interval, interval);
}
public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
class UnmanagedBlob : IUnmanagedBlob
{
private readonly StandardRuntimePlatform _plat;
private IntPtr _address;
private readonly object _lock = new object();
#if DEBUG
private static readonly List<string> Backtraces = new List<string>();
private static Thread GCThread;
private readonly string _backtrace;
private static readonly object _btlock = new object();
class GCThreadDetector
{
~GCThreadDetector()
{
GCThread = Thread.CurrentThread;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Spawn() => new GCThreadDetector();
static UnmanagedBlob()
{
Spawn();
GC.WaitForPendingFinalizers();
}
#endif
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
{
if (size <= 0)
throw new ArgumentException("Positive number required", nameof(size));
_plat = plat;
_address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
#if DEBUG
_backtrace = Environment.StackTrace;
lock (_btlock)
Backtraces.Add(_backtrace);
#endif
}
void DoDispose()
{
lock (_lock)
{
if (!IsDisposed)
{
#if DEBUG
lock (_btlock)
Backtraces.Remove(_backtrace);
#endif
_plat?.Free(_address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
_address = IntPtr.Zero;
Size = 0;
}
}
}
public void Dispose()
{
#if DEBUG
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
{
lock (_lock)
{
if (!IsDisposed)
{
Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+ Environment.StackTrace
+ "\n\nBlob created by " + _backtrace);
}
}
}
#endif
DoDispose();
GC.SuppressFinalize(this);
}
~UnmanagedBlob()
{
#if DEBUG
Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
#endif
DoDispose();
}
public IntPtr Address => IsDisposed ? throw new ObjectDisposedException("UnmanagedBlob") : _address;
public int Size { get; private set; }
public bool IsDisposed { get; private set; }
}
#if NET461 || NETCOREAPP2_0
[DllImport("libc", SetLastError = true)]
private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
[DllImport("libc", SetLastError = true)]
private static extern int munmap(IntPtr addr, IntPtr length);
[DllImport("libc", SetLastError = true)]
private static extern long sysconf(int name);
private bool? _useMmap;
private bool UseMmap
=> _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
IntPtr Alloc(int size)
{
if (UseMmap)
{
var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to allocate memory: " + errno);
}
return rv;
}
else
return Marshal.AllocHGlobal(size);
}
void Free(IntPtr ptr, int len)
{
if (UseMmap)
{
if (munmap(ptr, new IntPtr(len)) == -1)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to free memory: " + errno);
}
}
else
Marshal.FreeHGlobal(ptr);
}
#else
IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
#endif
}
internal class IOSLoader : IDynamicLibraryLoader
{
IntPtr IDynamicLibraryLoader.LoadLibrary(string dll)
{
throw new PlatformNotSupportedException();
}
IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional)
{
throw new PlatformNotSupportedException();
}
}
public class AssetLoader : IAssetLoader
{
private const string AvaloniaResourceName = "!AvaloniaResources";
private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
= new Dictionary<string, AssemblyDescriptor>();
private AssemblyDescriptor _defaultResmAssembly;
/// <summary>
/// Initializes a new instance of the <see cref="AssetLoader"/> class.
/// </summary>
/// <param name="assembly">
/// The default assembly from which to load resm: assets for which no assembly is specified.
/// </param>
public AssetLoader(Assembly assembly = null)
{
if (assembly == null)
assembly = Assembly.GetEntryAssembly();
if (assembly != null)
_defaultResmAssembly = new AssemblyDescriptor(assembly);
}
/// <summary>
/// Sets the default assembly from which to load assets for which no assembly is specified.
/// </summary>
/// <param name="assembly">The default assembly.</param>
public void SetDefaultAssembly(Assembly assembly)
{
_defaultResmAssembly = new AssemblyDescriptor(assembly);
}
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri baseUri = null)
{
return GetAsset(uri, baseUri) != null;
}
/// <summary>
/// Opens the asset with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>A stream containing the asset contents.</returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
/// <summary>
/// Opens the asset with the requested URI and returns the asset stream and the
/// assembly containing the asset.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
{
var asset = GetAsset(uri, baseUri);
if (asset == null)
{
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
return (asset.GetStream(), asset.Assembly);
}
public Assembly GetAssembly(Uri uri, Uri baseUri)
{
if (!uri.IsAbsoluteUri && baseUri != null)
uri = new Uri(baseUri, uri);
return GetAssembly(uri).Assembly;
}
/// <summary>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
{
if (uri.IsAbsoluteUri && uri.Scheme == "resm")
{
var assembly = GetAssembly(uri);
return assembly?.Resources.Where(x => x.Key.Contains(uri.AbsolutePath))
.Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>();
}
uri = EnsureAbsolute(uri, baseUri);
if (uri.Scheme == "avares")
{
var (asm, path) = GetResAsmAndPath(uri);
if (asm == null)
{
throw new ArgumentException(
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
}
if (asm?.AvaloniaResources == null)
return Enumerable.Empty<Uri>();
path = path.TrimEnd('/') + '/';
return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path))
.Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
}
return Enumerable.Empty<Uri>();
}
private Uri EnsureAbsolute(Uri uri, Uri baseUri)
{
if (uri.IsAbsoluteUri)
return uri;
if(baseUri == null)
throw new ArgumentException($"Relative uri {uri} without base url");
if (!baseUri.IsAbsoluteUri)
throw new ArgumentException($"Base uri {baseUri} is relative");
if (baseUri.Scheme == "resm")
throw new ArgumentException(
$"Relative uris for 'resm' scheme aren't supported; {baseUri} uses resm");
return new Uri(baseUri, uri);
}
private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
{
if (uri.IsAbsoluteUri && uri.Scheme == "resm")
{
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
if (asm == null)
{
throw new ArgumentException(
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
}
IAssetDescriptor rv;
var resourceKey = uri.AbsolutePath;
asm.Resources.TryGetValue(resourceKey, out rv);
return rv;
}
uri = EnsureAbsolute(uri, baseUri);
if (uri.Scheme == "avares")
{
var (asm, path) = GetResAsmAndPath(uri);
if (asm.AvaloniaResources == null)
return null;
asm.AvaloniaResources.TryGetValue(path, out var desc);
return desc;
}
throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
}
private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
{
var asm = GetAssembly(uri.Authority);
return (asm, uri.AbsolutePath);
}
private AssemblyDescriptor GetAssembly(Uri uri)
{
if (uri != null)
{
if (!uri.IsAbsoluteUri)
return null;
if (uri.Scheme == "avares")
return GetResAsmAndPath(uri).asm;
if (uri.Scheme == "resm")
{
var qs = ParseQueryString(uri);
string assemblyName;
if (qs.TryGetValue("assembly", out assemblyName))
{
return GetAssembly(assemblyName);
}
}
}
return null;
}
private AssemblyDescriptor GetAssembly(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
AssemblyDescriptor rv;
if (!AssemblyNameCache.TryGetValue(name, out rv))
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
if (match != null)
{
AssemblyNameCache[name] = rv = new AssemblyDescriptor(match);
}
else
{
// iOS does not support loading assemblies dynamically!
//
#if __IOS__
throw new InvalidOperationException(
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
#else
name = Uri.UnescapeDataString(name);
AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
#endif
}
}
return rv;
}
private Dictionary<string, string> ParseQueryString(Uri uri)
{
return uri.Query.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Split('='))
.ToDictionary(p => p[0], p => p[1]);
}
private interface IAssetDescriptor
{
Stream GetStream();
Assembly Assembly { get; }
}
private class AssemblyResourceDescriptor : IAssetDescriptor
{
private readonly Assembly _asm;
private readonly string _name;
public AssemblyResourceDescriptor(Assembly asm, string name)
{
_asm = asm;
_name = name;
}
public Stream GetStream()
{
return _asm.GetManifestResourceStream(_name);
}
public Assembly Assembly => _asm;
}
private class AvaloniaResourceDescriptor : IAssetDescriptor
{
private readonly int _offset;
private readonly int _length;
public Assembly Assembly { get; }
public AvaloniaResourceDescriptor(Assembly asm, int offset, int length)
{
_offset = offset;
_length = length;
Assembly = asm;
}
public Stream GetStream()
{
return new SlicedStream(Assembly.GetManifestResourceStream(AvaloniaResourceName), _offset, _length);
}
}
class SlicedStream : Stream
{
private readonly Stream _baseStream;
private readonly int _from;
public SlicedStream(Stream baseStream, int from, int length)
{
Length = length;
_baseStream = baseStream;
_from = from;
_baseStream.Position = from;
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position));
}
public override long Seek(long offset, SeekOrigin origin)
{
if (origin == SeekOrigin.Begin)
Position = offset;
if (origin == SeekOrigin.End)
Position = _from + Length + offset;
if (origin == SeekOrigin.Current)
Position = Position + offset;
return Position;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override bool CanRead => true;
public override bool CanSeek => _baseStream.CanRead;
public override bool CanWrite => false;
public override long Length { get; }
public override long Position
{
get => _baseStream.Position - _from;
set => _baseStream.Position = value + _from;
}
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
}
public override void Close() => _baseStream.Close();
}
private class AssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
Assembly = assembly;
if (assembly != null)
{
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
using (var resources = assembly.GetManifestResourceStream(AvaloniaResourceName))
{
if (resources != null)
{
Resources.Remove(AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => "/" + r.Path.TrimStart('/'), r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
}
}
}
}
public Assembly Assembly { get; }
public Dictionary<string, IAssetDescriptor> Resources { get; }
public Dictionary<string, IAssetDescriptor> AvaloniaResources { get; }
public string Name { get; }
}
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
UriParser.Register(new GenericUriParser(
GenericUriParserOptions.GenericAuthority |
GenericUriParserOptions.NoUserInfo |
GenericUriParserOptions.NoPort |
GenericUriParserOptions.NoQuery |
GenericUriParserOptions.NoFragment), "avares", -1);
}
}
}

6
src/iOS/Avalonia.iOS/Clipboard.cs → src/iOS/Avalonia.iOS/ClipboardImpl.cs

@ -1,10 +1,12 @@
using System;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using UIKit;
namespace Avalonia.iOS
{
public class Clipboard : IClipboard
public class ClipboardImpl : IClipboard
{
public Task<string> GetTextAsync()
{
@ -29,4 +31,4 @@ namespace Avalonia.iOS
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
}
}
}

11
src/iOS/Avalonia.iOS/CursorFactory.cs

@ -1,11 +0,0 @@
using System;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.iOS
{
class CursorFactory : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "NULL");
}
}

32
src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs

@ -1,32 +0,0 @@
using System;
using Avalonia.Rendering;
using CoreAnimation;
using Foundation;
namespace Avalonia.iOS
{
class DisplayLinkRenderTimer : IRenderTimer
{
public event Action<TimeSpan> Tick;
private CADisplayLink _link;
public DisplayLinkRenderTimer()
{
_link = CADisplayLink.Create(OnFrame);
_link.AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
}
private void OnFrame()
{
try
{
Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount));
}
catch (Exception)
{
//TODO: log
}
}
}
}

37
src/iOS/Avalonia.iOS/DisplayLinkTimer.cs

@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Rendering;
using CoreAnimation;
using Foundation;
using UIKit;
namespace Avalonia.iOS
{
class DisplayLinkTimer : IRenderTimer
{
public event Action<TimeSpan> Tick;
private Stopwatch _st = Stopwatch.StartNew();
public DisplayLinkTimer()
{
var link = CADisplayLink.Create(OnLinkTick);
TimerThread = new Thread(() =>
{
link.AddToRunLoop(NSRunLoop.Current, NSRunLoopMode.Common);
NSRunLoop.Current.Run();
});
TimerThread.Start();
UIApplication.Notifications.ObserveDidEnterBackground((_,__) => link.Paused = true);
UIApplication.Notifications.ObserveWillEnterForeground((_, __) => link.Paused = false);
}
public Thread TimerThread { get; }
private void OnLinkTick()
{
Tick?.Invoke(_st.Elapsed);
}
}
}

69
src/iOS/Avalonia.iOS/EaglDisplay.cs

@ -0,0 +1,69 @@
using System;
using Avalonia.OpenGL;
using OpenGLES;
using OpenTK.Graphics.ES30;
namespace Avalonia.iOS
{
class EaglFeature : IWindowingPlatformGlFeature
{
public IGlContext CreateContext() => throw new System.NotSupportedException();
public IGlContext MainContext => Context;
public GlContext Context { get; } = new GlContext();
}
class GlContext : IGlContext
{
public EAGLContext Context { get; private set; }
public GlContext()
{
const string path = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES";
var libGl = ObjCRuntime.Dlfcn.dlopen(path, 1);
if (libGl == IntPtr.Zero)
throw new OpenGlException("Unable to load " + path);
GlInterface = new GlInterface(Version, proc => ObjCRuntime.Dlfcn.dlsym(libGl, proc));
Context = new EAGLContext(EAGLRenderingAPI.OpenGLES3);
}
public void Dispose()
{
Context?.Dispose();
Context = null;
}
class ResetContext : IDisposable
{
private EAGLContext _old;
private bool _disposed;
public ResetContext(EAGLContext old)
{
_old = old;
}
public void Dispose()
{
if(_disposed)
return;
_disposed = true;
EAGLContext.SetCurrentContext(_old);
_old = null;
}
}
public IDisposable MakeCurrent()
{
var old = EAGLContext.CurrentContext;
if (!EAGLContext.SetCurrentContext(Context))
throw new OpenGlException("Unable to make context current");
return new ResetContext(old);
}
public GlVersion Version { get; } = new GlVersion(GlProfileType.OpenGLES, 3, 0);
public GlInterface GlInterface { get; }
public int SampleCount { get; } = 0;
public int StencilSize { get; } = 9;
}
}

94
src/iOS/Avalonia.iOS/EaglLayerSurface.cs

@ -0,0 +1,94 @@
using System;
using System.Threading;
using Avalonia.OpenGL;
using CoreAnimation;
using OpenTK.Graphics.ES30;
namespace Avalonia.iOS
{
class EaglLayerSurface : IGlPlatformSurface
{
private readonly CAEAGLLayer _layer;
public EaglLayerSurface(CAEAGLLayer layer)
{
_layer = layer;
}
class RenderSession : IGlPlatformSurfaceRenderingSession
{
private readonly GlContext _ctx;
private readonly IDisposable _restoreContext;
private readonly SizeSynchronizedLayerFbo _fbo;
public RenderSession(GlContext ctx, IDisposable restoreContext, SizeSynchronizedLayerFbo fbo)
{
_ctx = ctx;
_restoreContext = restoreContext;
_fbo = fbo;
Size = new PixelSize(_fbo.Width, _fbo.Height);
Scaling = _fbo.Scaling;
Context = ctx;
}
public void Dispose()
{
GL.Finish();
_fbo.Present();
_restoreContext.Dispose();
}
public IGlContext Context { get; }
public PixelSize Size { get; }
public double Scaling { get; }
public bool IsYFlipped { get; }
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly GlContext _ctx;
private readonly SizeSynchronizedLayerFbo _fbo;
public RenderTarget(GlContext ctx, SizeSynchronizedLayerFbo fbo)
{
_ctx = ctx;
_fbo = fbo;
}
public void Dispose()
{
CheckThread();
using (_ctx.MakeCurrent())
_fbo.Dispose();
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
CheckThread();
var restoreContext = _ctx.MakeCurrent();
_fbo.Bind();
return new RenderSession(_ctx, restoreContext, _fbo);
}
}
static void CheckThread()
{
if (Platform.Timer.TimerThread != Thread.CurrentThread)
throw new InvalidOperationException("Invalid thread, go away");
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
CheckThread();
var ctx = Platform.GlFeature.Context;
using (ctx.MakeCurrent())
{
var fbo = new SizeSynchronizedLayerFbo(ctx.Context, _layer);
if (!fbo.Sync())
throw new InvalidOperationException("Unable to create render target");
return new RenderTarget(ctx, fbo);
}
}
}
}

27
src/iOS/Avalonia.iOS/EmbeddableImpl.cs

@ -1,27 +0,0 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Platform;
namespace Avalonia.iOS
{
class EmbeddableImpl : TopLevelImpl
{
public void SetTitle(string title)
{
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IDisposable ShowDialog()
{
return Disposable.Empty;
}
public void SetSystemDecorations(SystemDecorations enabled)
{
}
}
}

60
src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs

@ -1,60 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using CoreGraphics;
using UIKit;
namespace Avalonia.iOS
{
/// <summary>
/// This is a bit weird, but CG doesn't provide proper bitmap
/// with lockable bits, but can create one from data pointer
/// So we are using our own buffer here.
/// </summary>
class EmulatedFramebuffer : ILockedFramebuffer
{
private nfloat _viewWidth;
private nfloat _viewHeight;
public EmulatedFramebuffer(UIView view)
{
var factor = (int) UIScreen.MainScreen.Scale;
var frame = view.Frame;
_viewWidth = frame.Width;
_viewHeight = frame.Height;
Size = new PixelSize((int)frame.Width * factor, (int)frame.Height * factor);
RowBytes = Size.Width * 4;
Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Size.Height * RowBytes);
}
public void Dispose()
{
if (Address == IntPtr.Zero)
return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Size.Width, Size.Height, 8, Size.Width * 4,
colorSpace, (CGImageAlphaInfo) nfo))
using (var image = bContext.ToImage())
using (var context = UIGraphics.GetCurrentContext())
{
// flip the image for CGContext.DrawImage
context.TranslateCTM(0, _viewHeight);
context.ScaleCTM(1, -1);
context.DrawImage(new CGRect(0, 0, _viewWidth, _viewHeight), image);
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
public IntPtr Address { get; private set; }
public PixelSize Size { get; }
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
}

4
src/iOS/Avalonia.iOS/Extensions.cs

@ -14,10 +14,10 @@ namespace Avalonia.iOS
static nfloat ColorComponent(byte c) => ((float) c) / 255;
public static UIColor ToUiColor(this Color color)=>new UIColor(
public static UIColor ToUiColor(this Color color) => new UIColor(
ColorComponent(color.R),
ColorComponent(color.G),
ColorComponent(color.B),
ColorComponent(color.A));
}
}
}

143
src/iOS/Avalonia.iOS/LayerFbo.cs

@ -0,0 +1,143 @@
using System;
using CoreAnimation;
using OpenGLES;
using OpenTK.Graphics.ES20;
namespace Avalonia.iOS
{
public class LayerFbo
{
private readonly EAGLContext _context;
private readonly CAEAGLLayer _layer;
private int _framebuffer;
private int _renderbuffer;
private int _depthBuffer;
private bool _disposed;
private LayerFbo(EAGLContext context, CAEAGLLayer layer, in int framebuffer, in int renderbuffer, in int depthBuffer)
{
_context = context;
_layer = layer;
_framebuffer = framebuffer;
_renderbuffer = renderbuffer;
_depthBuffer = depthBuffer;
}
public static LayerFbo TryCreate(EAGLContext context, CAEAGLLayer layer)
{
if (context != EAGLContext.CurrentContext)
return null;
GL.GenFramebuffers(1, out int fb);
GL.GenRenderbuffers(1, out int rb);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fb);
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rb);
context.RenderBufferStorage((uint) All.Renderbuffer, layer);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.ColorAttachment0, RenderbufferTarget.Renderbuffer, rb);
int w;
int h;
GL.GetRenderbufferParameter(RenderbufferTarget.Renderbuffer, RenderbufferParameterName.RenderbufferWidth, out w);
GL.GetRenderbufferParameter(RenderbufferTarget.Renderbuffer, RenderbufferParameterName.RenderbufferHeight, out h);
GL.GenRenderbuffers(1, out int depthBuffer);
//GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthBuffer);
//GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.DepthComponent16, w, h);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.DepthAttachment, RenderbufferTarget.Renderbuffer, depthBuffer);
var frameBufferError = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if(frameBufferError != FramebufferErrorCode.FramebufferComplete)
{
GL.DeleteFramebuffers(1, ref fb);
GL.DeleteRenderbuffers(1, ref depthBuffer);
GL.DeleteRenderbuffers(1, ref rb);
return null;
}
return new LayerFbo(context, layer, fb, rb, depthBuffer)
{
Width = w,
Height = h
};
}
public int Width { get; private set; }
public int Height { get; private set; }
public void Bind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer);
}
public void Present()
{
Bind();
var success = _context.PresentRenderBuffer((uint) All.Renderbuffer);
}
public void Dispose()
{
if(_disposed)
return;
_disposed = true;
GL.DeleteFramebuffers(1, ref _framebuffer);
GL.DeleteRenderbuffers(1, ref _depthBuffer);
GL.DeleteRenderbuffers(1, ref _renderbuffer);
if (_context != EAGLContext.CurrentContext)
throw new InvalidOperationException("Associated EAGLContext is not current");
}
}
class SizeSynchronizedLayerFbo : IDisposable
{
private readonly EAGLContext _context;
private readonly CAEAGLLayer _layer;
private LayerFbo _fbo;
private nfloat _oldLayerWidth, _oldLayerHeight, _oldLayerScale;
public SizeSynchronizedLayerFbo(EAGLContext context, CAEAGLLayer layer)
{
_context = context;
_layer = layer;
}
public bool Sync()
{
if (_fbo != null
&& _oldLayerWidth == _layer.Bounds.Width
&& _oldLayerHeight == _layer.Bounds.Height
&& _oldLayerScale == _layer.ContentsScale)
return true;
_fbo?.Dispose();
_fbo = null;
_fbo = LayerFbo.TryCreate(_context, _layer);
_oldLayerWidth = _layer.Bounds.Width;
_oldLayerHeight = _layer.Bounds.Height;
_oldLayerScale = _layer.ContentsScale;
return _fbo != null;
}
public void Dispose()
{
if (_context != EAGLContext.CurrentContext)
throw new InvalidOperationException("Associated EAGLContext is not current");
_fbo?.Dispose();
_fbo = null;
}
public void Bind()
{
if(!Sync())
throw new InvalidOperationException("Unable to create a render target");
_fbo.Bind();
}
public void Present() => _fbo.Present();
public int Width => _fbo?.Width ?? 0;
public int Height => _fbo?.Height ?? 0;
public double Scaling => _oldLayerScale;
}
}

50
src/iOS/Avalonia.iOS/Platform.cs

@ -0,0 +1,50 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
namespace Avalonia.iOS
{
static class Platform
{
public static EaglFeature GlFeature;
public static DisplayLinkTimer Timer;
class PlatformSettings : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(10, 10);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
}
public static void Register()
{
GlFeature ??= new EaglFeature();
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
var softKeyboard = new SoftKeyboardHelper();
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatformGlFeature>().ToConstant(GlFeature)
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToConstant(new PlatformSettings())
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>()
.Bind<IRenderTimer>().ToConstant(Timer)
.Bind<IPlatformThreadingInterface>().ToConstant(new PlatformThreadingInterface())
.Bind<IKeyboardDevice>().ToConstant(keyboard);
keyboard.PropertyChanged += (_, changed) =>
{
if (changed.PropertyName == nameof(KeyboardDevice.FocusedElement))
softKeyboard.UpdateKeyboard(keyboard.FocusedElement);
};
}
}
}

46
src/iOS/Avalonia.iOS/PlatformIconLoader.cs

@ -1,46 +0,0 @@
using System.IO;
using Avalonia.Platform;
namespace Avalonia.iOS
{
class PlatformIconLoader : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
using (var stream = new MemoryStream())
{
bitmap.Save(stream);
return LoadIcon(stream);
}
}
public IWindowIconImpl LoadIcon(Stream stream)
{
return new FakeIcon(stream);
}
public IWindowIconImpl LoadIcon(string fileName)
{
using (var file = File.Open(fileName, FileMode.Open))
{
return new FakeIcon(file);
}
}
}
// Stores the icon created as a stream to support saving even though an icon is never shown
public class FakeIcon : IWindowIconImpl
{
private readonly Stream stream = new MemoryStream();
public FakeIcon(Stream stream)
{
stream.CopyTo(this.stream);
}
public void Save(Stream outputStream)
{
stream.CopyTo(outputStream);
}
}
}

14
src/iOS/Avalonia.iOS/PlatformSettings.cs

@ -1,14 +0,0 @@
using System;
using Avalonia.Platform;
using UIKit;
namespace Avalonia.iOS
{
class PlatformSettings : IPlatformSettings
{
public Size DoubleClickSize =>new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
public double RenderScalingFactor => UIScreen.MainScreen.Scale;
public double LayoutScalingFactor => 1;
}
}

33
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -2,6 +2,7 @@ using System;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using CoreFoundation;
using Foundation;
namespace Avalonia.iOS
@ -18,32 +19,7 @@ namespace Avalonia.iOS
//Mobile platforms are using external main loop
throw new NotSupportedException();
}
/*
class Timer : NSObject
{
private readonly Action _tick;
private NSTimer _timer;
public Timer(TimeSpan interval, Action tick)
{
_tick = tick;
_timer = new NSTimer(NSDate.Now, interval.TotalSeconds, true, OnTick);
}
[Export("onTick")]
private void OnTick(NSTimer nsTimer)
{
_tick();
}
protected override void Dispose(bool disposing)
{
if(disposing)
_timer.Dispose();
base.Dispose(disposing);
}
}*/
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick());
@ -55,7 +31,8 @@ namespace Avalonia.iOS
return;
_signaled = true;
}
NSRunLoop.Main.BeginInvokeOnMainThread(() =>
DispatchQueue.MainQueue.DispatchAsync(() =>
{
lock (this)
_signaled = false;
@ -63,4 +40,4 @@ namespace Avalonia.iOS
});
}
}
}
}

17
src/iOS/Avalonia.iOS/RuntimeInfo.cs

@ -1,17 +0,0 @@
using Avalonia.Platform;
namespace Avalonia.Shared.PlatformSupport
{
internal partial class StandardRuntimePlatform
{
public RuntimePlatformInfo GetRuntimeInfo() => new RuntimePlatformInfo
{
IsCoreClr = false,
IsDesktop = false,
IsMobile = true,
IsDotNetFramework = false,
IsMono = true,
IsUnix = true,
OperatingSystem = OperatingSystemType.Android
};
}
}

7
src/iOS/Avalonia.iOS/SingleViewLifetime.cs

@ -0,0 +1,7 @@
namespace Avalonia.iOS
{
public class SingleViewLifetime
{
}
}

24
src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs

@ -0,0 +1,24 @@
using Avalonia.Controls;
using Avalonia.Input;
namespace Avalonia.iOS
{
public class SoftKeyboardHelper
{
private AvaloniaView _oldView;
public void UpdateKeyboard(IInputElement focusedElement)
{
if (_oldView?.IsFirstResponder == true)
_oldView?.ResignFirstResponder();
_oldView = null;
//TODO: Raise a routed event to determine if any control wants to become the text input handler
if (focusedElement is TextBox)
{
var view = ((focusedElement.VisualRoot as TopLevel)?.PlatformImpl as AvaloniaView.TopLevelImpl)?.View;
view?.BecomeFirstResponder();
}
}
}
}

147
src/iOS/Avalonia.iOS/Specific/KeyboardEventsHelper.cs

@ -1,147 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using ObjCRuntime;
using UIKit;
namespace Avalonia.iOS.Specific
{
/// <summary>
/// In order to have properly handle of keyboard event in iOS View should already made some things in the View:
/// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class
/// 2. Implement all the methods required by UIKeyInput:
/// 2.1 Implement HasText
/// example:
/// [Export("hasText")]
/// bool HasText => _keyboardHelper.HasText()
/// 2.2 Implement InsertText
/// example:
/// [Export("insertText:")]
/// void InsertText(string text) => _keyboardHelper.InsertText(text);
/// 2.3 Implement InsertText
/// example:
/// [Export("deleteBackward")]
/// void DeleteBackward() => _keyboardHelper.DeleteBackward();
/// 3.Let iOS know that this can become a first responder:
/// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
/// or
/// public override bool CanBecomeFirstResponder { get { return true; } }
///
/// 4. To show keyboard:
/// view.BecomeFirstResponder();
/// 5. To hide keyboard
/// view.ResignFirstResponder();
/// </summary>
/// <typeparam name="TView">View that needs keyboard events and show/hide keyboard</typeparam>
internal class KeyboardEventsHelper<TView> where TView : UIView, ITopLevelImpl
{
private TView _view;
private IInputElement _lastFocusedElement;
public KeyboardEventsHelper(TView view)
{
_view = view;
var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType<AdoptsAttribute>().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault();
if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!");
HandleEvents = true;
}
/// <summary>
/// HandleEvents in order to suspend keyboard notifications or resume it
/// </summary>
public bool HandleEvents { get; set; }
public bool HasText() => false;
public bool CanBecomeFirstResponder() => true;
public void DeleteBackward()
{
HandleKey(Key.Back, RawKeyEventType.KeyDown);
HandleKey(Key.Back, RawKeyEventType.KeyUp);
}
public void InsertText(string text)
{
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text);
_view.Input(rawTextEvent);
}
private void HandleKey(Key key, RawKeyEventType type)
{
var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, RawInputModifiers.None);
_view.Input(rawKeyEvent);
}
//currently not found a way to get InputModifiers state
//private static InputModifiers GetModifierKeys(object e)
//{
// var im = InputModifiers.None;
// //if (IsCtrlPressed) rv |= InputModifiers.Control;
// //if (IsShiftPressed) rv |= InputModifiers.Shift;
// return im;
//}
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
return element is TextBox;
}
private void TryShowHideKeyboard(IInputElement element, bool value)
{
if (value)
{
_view.BecomeFirstResponder();
}
else
{
_view.ResignFirstResponder();
}
}
public void UpdateKeyboardState(IInputElement element)
{
var focusedElement = element;
bool oldValue = NeedsKeyboard(_lastFocusedElement);
bool newValue = NeedsKeyboard(focusedElement);
if (newValue != oldValue || newValue)
{
TryShowHideKeyboard(focusedElement, newValue);
}
_lastFocusedElement = element;
}
public void ActivateAutoShowKeyboard()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
//just in case we've called more than once the method
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
}
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
}
}
public void Dispose()
{
HandleEvents = false;
}
}
}

60
src/iOS/Avalonia.iOS/Stubs.cs

@ -0,0 +1,60 @@
using System;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.iOS
{
class CursorFactoryStub : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "NULL");
}
class WindowingPlatformStub : IWindowingPlatform
{
public IWindowImpl CreateWindow() => throw new NotSupportedException();
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
}
class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
using (var stream = new MemoryStream())
{
bitmap.Save(stream);
return LoadIcon(stream);
}
}
public IWindowIconImpl LoadIcon(Stream stream)
{
var ms = new MemoryStream();
stream.CopyTo(ms);
return new IconStub(ms);
}
public IWindowIconImpl LoadIcon(string fileName)
{
using (var file = File.Open(fileName, FileMode.Open))
return LoadIcon(file);
}
}
public class IconStub : IWindowIconImpl
{
private readonly MemoryStream _ms;
public IconStub(MemoryStream stream)
{
_ms = stream;
}
public void Save(Stream outputStream)
{
_ms.Position = 0;
_ms.CopyTo(outputStream);
}
}
}

145
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -1,145 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.iOS.Specific;
using Avalonia.Platform;
using Avalonia.Rendering;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Avalonia.iOS
{
[Adopts("UIKeyInput")]
class TopLevelImpl : UIView, ITopLevelImpl, IFramebufferPlatformSurface
{
private readonly KeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private IInputRoot _inputRoot;
public TopLevelImpl()
{
_keyboardHelper = new KeyboardEventsHelper<TopLevelImpl>(this);
AutoresizingMask = UIViewAutoresizing.All;
_keyboardHelper.ActivateAutoShowKeyboard();
Surfaces = new object[] { this };
}
[Export("hasText")]
public bool HasText => _keyboardHelper.HasText();
[Export("insertText:")]
public void InsertText(string text) => _keyboardHelper.InsertText(text);
[Export("deleteBackward")]
public void DeleteBackward() => _keyboardHelper.DeleteBackward();
public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
public Action Closed { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public new IPlatformHandle Handle => null;
public double RenderScaling => UIScreen.MainScreen.Scale;
public override void LayoutSubviews() => Resized?.Invoke(ClientSize);
public Size ClientSize => Bounds.Size.ToAvalonia();
public IMouseDevice MouseDevice => iOSPlatform.MouseDevice;
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer(root);
}
public override void Draw(CGRect rect)
{
Paint?.Invoke(new Rect(rect.X, rect.Y, rect.Width, rect.Height));
}
public void Invalidate(Rect rect) => SetNeedsDisplay();
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
public Point PointToClient(PixelPoint point) => point.ToPoint(1);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public void SetCursor(IPlatformHandle cursor)
{
//Not supported
}
public IEnumerable<object> Surfaces { get; }
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
var touch = touches.AnyObject as UITouch;
if (touch != null)
{
var location = touch.LocationInView(this).ToAvalonia();
Input?.Invoke(new RawPointerEventArgs(
iOSPlatform.MouseDevice,
(uint)touch.Timestamp,
_inputRoot,
RawPointerEventType.LeftButtonUp,
location,
RawInputModifiers.None));
}
}
Point _touchLastPoint;
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
var touch = touches.AnyObject as UITouch;
if (touch != null)
{
var location = touch.LocationInView(this).ToAvalonia();
_touchLastPoint = location;
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.Move, location, RawInputModifiers.None));
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.LeftButtonDown, location, RawInputModifiers.None));
}
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
var touch = touches.AnyObject as UITouch;
if (touch != null)
{
var location = touch.LocationInView(this).ToAvalonia();
if (iOSPlatform.MouseDevice.Captured != null)
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.Move, location, RawInputModifiers.LeftMouseButton));
else
{
//magic number based on test - correction of 0.02 is working perfect
double correction = 0.02;
Input?.Invoke(new RawMouseWheelEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp,
_inputRoot, location, (location - _touchLastPoint) * correction, RawInputModifiers.LeftMouseButton));
}
_touchLastPoint = location;
}
}
public ILockedFramebuffer Lock() => new EmulatedFramebuffer(this);
public IPopupImpl CreatePopup() => null;
public Action LostFocus { get; set; }
}
}

52
src/iOS/Avalonia.iOS/TouchHandler.cs

@ -0,0 +1,52 @@
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Foundation;
using UIKit;
namespace Avalonia.iOS
{
class TouchHandler
{
private readonly AvaloniaView _view;
private readonly ITopLevelImpl _tl;
public TouchDevice _device = new TouchDevice();
public TouchHandler(AvaloniaView view, ITopLevelImpl tl)
{
_view = view;
_tl = tl;
}
ulong Ts(UIEvent evt) => (ulong) (evt.Timestamp * 1000);
private IInputRoot Root => _view.InputRoot;
private static long _nextTouchPointId = 1;
private Dictionary<UITouch, long> _knownTouches = new Dictionary<UITouch, long>();
public void Handle(NSSet touches, UIEvent evt)
{
foreach (UITouch t in touches)
{
var pt = t.LocationInView(_view).ToAvalonia();
if (!_knownTouches.TryGetValue(t, out var id))
_knownTouches[t] = id = _nextTouchPointId++;
var ev = new RawTouchEventArgs(_device, Ts(evt), Root,
t.Phase switch
{
UITouchPhase.Began => RawPointerEventType.TouchBegin,
UITouchPhase.Ended => RawPointerEventType.TouchEnd,
UITouchPhase.Cancelled => RawPointerEventType.TouchCancel,
_ => RawPointerEventType.TouchUpdate
}, pt, RawInputModifiers.None, id);
_device.ProcessRawEvent(ev);
if (t.Phase == UITouchPhase.Cancelled || t.Phase == UITouchPhase.Ended)
_knownTouches.Remove(t);
}
}
}
}

23
src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs

@ -1,23 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.iOS
{
class WindowingPlatformImpl : IWindowingPlatform
{
public IWindowImpl CreateWindow()
{
throw new NotSupportedException();
}
public WindowImpl CreateEmbeddableWindow()
{
throw new NotSupportedException();
}
public IPopupImpl CreatePopup()
{
throw new NotImplementedException();
}
}
}

49
src/iOS/Avalonia.iOS/iOSPlatform.cs

@ -1,49 +0,0 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.iOS;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
namespace Avalonia
{
public static class iOSApplicationExtensions
{
public static T UseiOS<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(iOSPlatform.Initialize, "iOS");
return builder;
}
}
}
namespace Avalonia.iOS
{
public class iOSPlatform
{
internal static MouseDevice MouseDevice;
internal static KeyboardDevice KeyboardDevice;
public static void Initialize()
{
MouseDevice = new MouseDevice();
KeyboardDevice = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToSingleton<StandardRuntimePlatform>()
.Bind<IClipboard>().ToTransient<Clipboard>()
// TODO: what does this look like for iOS??
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IWindowingPlatform>().ToSingleton<WindowingPlatformImpl>()
.Bind<IRenderTimer>().ToSingleton<DisplayLinkRenderTimer>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>();
}
}
}

39
src/iOS/Avalonia.iOSTestApplication/AppDelegate.cs

@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Foundation;
using Avalonia.Controls;
using Avalonia.iOS;
using Avalonia.Media;
using Avalonia.Threading;
using UIKit;
namespace Avalonia.iOSTestApplication
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
public override UIWindow Window { get; set; }
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
{
AppBuilder.Configure<SimpleApp>().UseSkia().UseiOS().SetupWithoutStarting();
Window = new AvaloniaWindow { Content = new SimpleControl()};
Window.MakeKeyAndVisible();
return true;
}
}
}

191
src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj

@ -1,191 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
<ProjectGuid>{8C923867-8A8F-4F6B-8B80-47D9E8436166}</ProjectGuid>
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Exe</OutputType>
<RootNamespace>Avalonia.iOSTestApplication</RootNamespace>
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
<AssemblyName>AvaloniaiOSTestApplication</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>i386</MtouchArch>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchDebug>true</MtouchDebug>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\iPhoneSimulator\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<MtouchLink>None</MtouchLink>
<MtouchArch>i386</MtouchArch>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhone\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<CodesignKey>iPhone Developer</CodesignKey>
<MtouchDebug>True</MtouchDebug>
<MtouchSdkVersion>9.1</MtouchSdkVersion>
<MtouchLink>None</MtouchLink>
<MtouchProfiling>False</MtouchProfiling>
<MtouchFastDev>False</MtouchFastDev>
<MtouchUseLlvm>False</MtouchUseLlvm>
<MtouchUseThumb>False</MtouchUseThumb>
<MtouchUseSGen>True</MtouchUseSGen>
<MtouchUseRefCounting>True</MtouchUseRefCounting>
<MtouchOptimizePNGs>True</MtouchOptimizePNGs>
<DeviceSpecificBuild>False</DeviceSpecificBuild>
<MtouchI18n />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\iPhone\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<ConsolePause>false</ConsolePause>
<CodesignKey>iPhone Developer</CodesignKey>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Ad-Hoc|iPhone' ">
<DebugType>none</DebugType>
<Optimize>True</Optimize>
<OutputPath>bin\iPhone\Ad-Hoc</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<BuildIpa>True</BuildIpa>
<CodesignProvision>Automatic:AdHoc</CodesignProvision>
<CodesignKey>iPhone Distribution</CodesignKey>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'AppStore|iPhone' ">
<DebugType>none</DebugType>
<Optimize>True</Optimize>
<OutputPath>bin\iPhone\AppStore</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
<MtouchArch>ARMv7, ARM64</MtouchArch>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<CodesignProvision>Automatic:AppStore</CodesignProvision>
<CodesignKey>iPhone Distribution</CodesignKey>
</PropertyGroup>
<PropertyGroup>
<DisableSourceLink>true</DisableSourceLink>
</PropertyGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" />
<Compile Include="SimpleApp.xaml.cs" />
<Compile Include="SimpleControl.cs" />
<None Include="GettingStarted.Xamarin" />
<None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\Default-568h%402x.png" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Xamarin.iOS" />
</ItemGroup>
<ItemGroup>
<Content Include="Entitlements.plist" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>
<IsAppExtension>false</IsAppExtension>
<IsWatchApp>false</IsWatchApp>
</ProjectReference>
<ProjectReference Include="..\Avalonia.iOS\Avalonia.iOS.csproj">
<Project>{4488ad85-1495-4809-9aa4-ddfe0a48527e}</Project>
<Name>Avalonia.iOS</Name>
<IsAppExtension>false</IsAppExtension>
<IsWatchApp>false</IsWatchApp>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="SimpleApp.xaml">
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\iOSWorkarounds.props" />
<Import Project="..\..\..\build\LegacyProject.targets" />
</Project>

5
src/iOS/Avalonia.iOSTestApplication/Entitlements.plist

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

4
src/iOS/Avalonia.iOSTestApplication/GettingStarted.Xamarin

@ -1,4 +0,0 @@
<GettingStarted>
<LocalContent>GS\iOS\CS\iOSApp\GettingStarted.html</LocalContent>
<EmbeddedNavigation>false</EmbeddedNavigation>
</GettingStarted>

38
src/iOS/Avalonia.iOSTestApplication/Info.plist

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Avalonia.iOSTestApplication</string>
<key>CFBundleIdentifier</key>
<string>com.your-company.Avalonia.iOSTestApplication</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleIconFiles</key>
<array>
<string>Default-568h@2x.png</string>
</array>
</dict>
</plist>

20
src/iOS/Avalonia.iOSTestApplication/Main.cs

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Foundation;
using UIKit;
namespace Avalonia.iOSTestApplication
{
public class Application
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, "AppDelegate");
}
}
}

36
src/iOS/Avalonia.iOSTestApplication/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Avalonia.iOSTestApplication")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Avalonia.iOSTestApplication")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8c923867-8a8f-4f6b-8b80-47d9e8436166")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

BIN
src/iOS/Avalonia.iOSTestApplication/Resources/Default-568h@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

6
src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml

@ -1,6 +0,0 @@
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles>
</Application>

20
src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml.cs

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Avalonia.Markup.Xaml;
using Foundation;
using UIKit;
namespace Avalonia.iOSTestApplication
{
public class SimpleApp : Avalonia.Application
{
public override void Initialize()
{
//Enforce load
new Avalonia.Themes.Default.DefaultTheme();
AvaloniaXamlLoader.Load(this);
}
}
}

20
src/iOS/Avalonia.iOSTestApplication/SimpleControl.cs

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Avalonia.Controls;
using Avalonia.Media;
namespace Avalonia.iOSTestApplication
{
class SimpleControl : ContentControl
{
public SimpleControl()
{
Content = new Button() {Content = "WAT"};
MinWidth = 100;
MinHeight = 200;
Background = Brushes.CadetBlue;
}
}
}

26
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
@ -331,6 +333,30 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Ignore_Invisible_Characters()
{
using (Start())
{
var defaultTextRunProperties =
new GenericTextRunProperties(Typeface.Default);
const string text = "01234567🎉\n";
var source = new SingleBufferTextSource(text, defaultTextRunProperties);
var textParagraphProperties = new GenericTextParagraphProperties(defaultTextRunProperties);
var formatter = TextFormatter.Current;
var textLine = formatter.FormatLine(source, 0, double.PositiveInfinity, textParagraphProperties);
var nextCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(8, 2));
Assert.Equal(new CharacterHit(8, 2), nextCharacterHit);
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

Loading…
Cancel
Save