diff --git a/Avalonia.sln b/Avalonia.sln index ffd7b4d4f4..922c8f57dd 100644 --- a/Avalonia.sln +++ b/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} diff --git a/build/iOSWorkarounds.props b/build/iOSWorkarounds.props deleted file mode 100644 index fe46295770..0000000000 --- a/build/iOSWorkarounds.props +++ /dev/null @@ -1,5 +0,0 @@ - - - $(MSBuildToolsPath)\..\Roslyn - - diff --git a/dirs.proj b/dirs.proj index 26c8f54b23..a48ad6e03d 100644 --- a/dirs.proj +++ b/dirs.proj @@ -13,7 +13,7 @@ - + diff --git a/global.json b/global.json index a3fdca9bed..128511eb48 100644 --- a/global.json +++ b/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" } } diff --git a/samples/ControlCatalog.iOS/AppDelegate.cs b/samples/ControlCatalog.iOS/AppDelegate.cs index a8fc6b30a0..f1c2241003 100644 --- a/samples/ControlCatalog.iOS/AppDelegate.cs +++ b/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 { - 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() - .UseiOS() - .UseSkia().SetupWithoutStarting(); - Window = new AvaloniaWindow() {Content = new MainView(), StatusBarColor = Colors.LightSteelBlue}; - Window.MakeKeyAndVisible(); - return true; - } + } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj index 7596e4cfe2..2dbc095156 100644 --- a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj +++ b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj @@ -175,6 +175,6 @@ - + diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml index 3b378dbcbe..9c515ebe30 100644 --- a/src/Avalonia.Themes.Default/Window.xaml +++ b/src/Avalonia.Themes.Default/Window.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Window.xaml index 1856eec206..fc71798d97 100644 --- a/src/Avalonia.Themes.Fluent/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Window.xaml @@ -9,6 +9,7 @@ + diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..5058cff26d --- /dev/null +++ b/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 diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index fa7d6cb4bf..df1ecb4067 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/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; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index 423ca9fb7f..c052fb8948 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/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. /// /// - /// A value that represents the line break. + /// A value that represents the line break. /// - public abstract TextLineBreak LineBreak { get; } + public abstract TextLineBreak TextLineBreak { get; } /// /// Gets a value that indicates whether the line is collapsed. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 08d9107bb1..51092cddda 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/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; } /// - public override TextLineBreak LineBreak { get; } + public override TextLineBreak TextLineBreak { get; } /// 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); diff --git a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj index e57fcc643f..d5b95b3151 100644 --- a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj +++ b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj @@ -1,15 +1,24 @@ - + xamarin.ios10 true + latest + + + Code + + + Code + + + + + - + - - - diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs new file mode 100644 index 0000000000..b75aad17cf --- /dev/null +++ b/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 : 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(); + 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; + } + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/AvaloniaRootViewController.cs b/src/iOS/Avalonia.iOS/AvaloniaRootViewController.cs deleted file mode 100644 index 52f55d6574..0000000000 --- a/src/iOS/Avalonia.iOS/AvaloniaRootViewController.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs new file mode 100644 index 0000000000..dc963726b0 --- /dev/null +++ b/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)); + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index b1de4b8d47..7d367c99d1 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/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()); + + 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 Surfaces { get; set; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action 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; } } -} +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/AvaloniaWindow.cs b/src/iOS/Avalonia.iOS/AvaloniaWindow.cs deleted file mode 100644 index 19e2bb494c..0000000000 --- a/src/iOS/Avalonia.iOS/AvaloniaWindow.cs +++ /dev/null @@ -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; } - } - } -} diff --git a/src/iOS/Avalonia.iOS/AppBuilder.cs b/src/iOS/Avalonia.iOS/Boilerplate/AppBuilder.cs similarity index 57% rename from src/iOS/Avalonia.iOS/AppBuilder.cs rename to src/iOS/Avalonia.iOS/Boilerplate/AppBuilder.cs index cb8e0a7954..65143c939f 100644 --- a/src/iOS/Avalonia.iOS/AppBuilder.cs +++ b/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 { public AppBuilder() : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly)) + b => StandardRuntimePlatformServices.Register(b.ApplicationType.Assembly)) { - + this.UseSkia().UseWindowingSubsystem(iOS.Platform.Register); } } -} +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/Boilerplate/RuntimePlatform.cs b/src/iOS/Avalonia.iOS/Boilerplate/RuntimePlatform.cs new file mode 100644 index 0000000000..c5c4d66450 --- /dev/null +++ b/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 + }; + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/Boilerplate/Shared.cs b/src/iOS/Avalonia.iOS/Boilerplate/Shared.cs new file mode 100644 index 0000000000..c6e6e01e64 --- /dev/null +++ b/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().ToConstant(standardPlatform) + .Bind().ToConstant(new AssetLoader(assembly)) + .Bind().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 Backtraces = new List(); + 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 AssemblyNameCache + = new Dictionary(); + + private AssemblyDescriptor _defaultResmAssembly; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default assembly from which to load resm: assets for which no assembly is specified. + /// + public AssetLoader(Assembly assembly = null) + { + if (assembly == null) + assembly = Assembly.GetEntryAssembly(); + if (assembly != null) + _defaultResmAssembly = new AssemblyDescriptor(assembly); + } + + /// + /// Sets the default assembly from which to load assets for which no assembly is specified. + /// + /// The default assembly. + public void SetDefaultAssembly(Assembly assembly) + { + _defaultResmAssembly = new AssemblyDescriptor(assembly); + } + + /// + /// Checks if an asset with the specified URI exists. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// True if the asset could be found; otherwise false. + public bool Exists(Uri uri, Uri baseUri = null) + { + return GetAsset(uri, baseUri) != null; + } + + /// + /// Opens the asset with the requested URI. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// A stream containing the asset contents. + /// + /// The asset could not be found. + /// + public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the asset with the requested URI and returns the asset stream and the + /// assembly containing the asset. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The asset could not be found. + /// + 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; + } + + /// + /// Gets all assets of a folder and subfolders that match specified uri. + /// + /// The URI. + /// Base URI that is used if is relative. + /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset + public IEnumerable 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 = 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(); + path = path.TrimEnd('/') + '/'; + return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path)) + .Select(x => new Uri($"avares://{asm.Name}{x.Key}")); + } + + return Enumerable.Empty(); + } + + 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 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 Resources { get; } + public Dictionary 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); + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/Clipboard.cs b/src/iOS/Avalonia.iOS/ClipboardImpl.cs similarity index 91% rename from src/iOS/Avalonia.iOS/Clipboard.cs rename to src/iOS/Avalonia.iOS/ClipboardImpl.cs index 2deb49473f..b9f74d69c6 100644 --- a/src/iOS/Avalonia.iOS/Clipboard.cs +++ b/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 GetTextAsync() { @@ -29,4 +31,4 @@ namespace Avalonia.iOS public Task GetDataAsync(string format) => throw new PlatformNotSupportedException(); } -} +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/CursorFactory.cs b/src/iOS/Avalonia.iOS/CursorFactory.cs deleted file mode 100644 index a3b2e8906d..0000000000 --- a/src/iOS/Avalonia.iOS/CursorFactory.cs +++ /dev/null @@ -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"); - } -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs b/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs deleted file mode 100644 index 0cefba7f19..0000000000 --- a/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Avalonia.Rendering; -using CoreAnimation; -using Foundation; - -namespace Avalonia.iOS -{ - class DisplayLinkRenderTimer : IRenderTimer - { - public event Action 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 - } - } - } -} diff --git a/src/iOS/Avalonia.iOS/DisplayLinkTimer.cs b/src/iOS/Avalonia.iOS/DisplayLinkTimer.cs new file mode 100644 index 0000000000..df73479a65 --- /dev/null +++ b/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 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); + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs new file mode 100644 index 0000000000..635df43407 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs new file mode 100644 index 0000000000..64912b8ae3 --- /dev/null +++ b/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); + } + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs deleted file mode 100644 index d299ff99c1..0000000000 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ /dev/null @@ -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) - { - } - } -} diff --git a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs deleted file mode 100644 index 89c7aaf76c..0000000000 --- a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Avalonia.Platform; -using CoreGraphics; -using UIKit; - -namespace Avalonia.iOS -{ - - /// - /// 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. - /// - 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; } - } -} - diff --git a/src/iOS/Avalonia.iOS/Extensions.cs b/src/iOS/Avalonia.iOS/Extensions.cs index 28708b20d8..bf6262e5c5 100644 --- a/src/iOS/Avalonia.iOS/Extensions.cs +++ b/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)); } -} +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/LayerFbo.cs b/src/iOS/Avalonia.iOS/LayerFbo.cs new file mode 100644 index 0000000000..907af58c7e --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs new file mode 100644 index 0000000000..b484559ff3 --- /dev/null +++ b/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().ToConstant(GlFeature) + .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(new WindowingPlatformStub()) + .Bind().ToConstant(new ClipboardImpl()) + .Bind().ToConstant(new PlatformSettings()) + .Bind().ToConstant(new PlatformIconLoaderStub()) + .Bind().ToSingleton() + .Bind().ToSingleton() + .Bind().ToConstant(Timer) + .Bind().ToConstant(new PlatformThreadingInterface()) + .Bind().ToConstant(keyboard); + keyboard.PropertyChanged += (_, changed) => + { + if (changed.PropertyName == nameof(KeyboardDevice.FocusedElement)) + softKeyboard.UpdateKeyboard(keyboard.FocusedElement); + }; + } + + + } +} + diff --git a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs deleted file mode 100644 index c539aba846..0000000000 --- a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs +++ /dev/null @@ -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); - } - } -} diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs deleted file mode 100644 index 9545795629..0000000000 --- a/src/iOS/Avalonia.iOS/PlatformSettings.cs +++ /dev/null @@ -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; - } -} diff --git a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs index 7762e0e03b..fa36ab6c79 100644 --- a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs +++ b/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 }); } } -} +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/RuntimeInfo.cs b/src/iOS/Avalonia.iOS/RuntimeInfo.cs deleted file mode 100644 index 101561b246..0000000000 --- a/src/iOS/Avalonia.iOS/RuntimeInfo.cs +++ /dev/null @@ -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 - }; - } -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs new file mode 100644 index 0000000000..914f0ba548 --- /dev/null +++ b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.iOS +{ + public class SingleViewLifetime + { + + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs b/src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs new file mode 100644 index 0000000000..b05ab280d2 --- /dev/null +++ b/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(); + } + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/Specific/KeyboardEventsHelper.cs b/src/iOS/Avalonia.iOS/Specific/KeyboardEventsHelper.cs deleted file mode 100644 index cdf244d330..0000000000 --- a/src/iOS/Avalonia.iOS/Specific/KeyboardEventsHelper.cs +++ /dev/null @@ -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 -{ - /// - /// 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(); - /// - /// View that needs keyboard events and show/hide keyboard - internal class KeyboardEventsHelper 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().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; - } - - /// - /// HandleEvents in order to suspend keyboard notifications or resume it - /// - 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; - } - } -} diff --git a/src/iOS/Avalonia.iOS/Stubs.cs b/src/iOS/Avalonia.iOS/Stubs.cs new file mode 100644 index 0000000000..a35b301a7f --- /dev/null +++ b/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); + } + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs deleted file mode 100644 index 5a85a5ea88..0000000000 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ /dev/null @@ -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 _keyboardHelper; - - private IInputRoot _inputRoot; - - public TopLevelImpl() - { - _keyboardHelper = new KeyboardEventsHelper(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 Input { get; set; } - public Action Paint { get; set; } - public Action Resized { get; set; } - public Action 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 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; } - } -} diff --git a/src/iOS/Avalonia.iOS/TouchHandler.cs b/src/iOS/Avalonia.iOS/TouchHandler.cs new file mode 100644 index 0000000000..43b19c85af --- /dev/null +++ b/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 _knownTouches = new Dictionary(); + + 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); + } + } + + } +} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs b/src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs deleted file mode 100644 index aaca5f53d6..0000000000 --- a/src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs deleted file mode 100644 index b0092bc98a..0000000000 --- a/src/iOS/Avalonia.iOS/iOSPlatform.cs +++ /dev/null @@ -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(this T builder) where T : AppBuilderBase, 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().ToSingleton() - .Bind().ToTransient() - // TODO: what does this look like for iOS?? - //.Bind().ToTransient() - .Bind().ToTransient() - .Bind().ToConstant(KeyboardDevice) - .Bind().ToSingleton() - .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToSingleton() - .Bind().ToSingleton() - .Bind().ToSingleton() - .Bind().ToSingleton() - .Bind().ToSingleton(); - } - } -} diff --git a/src/iOS/Avalonia.iOSTestApplication/AppDelegate.cs b/src/iOS/Avalonia.iOSTestApplication/AppDelegate.cs deleted file mode 100644 index 4591fb2ae8..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/AppDelegate.cs +++ /dev/null @@ -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().UseSkia().UseiOS().SetupWithoutStarting(); - Window = new AvaloniaWindow { Content = new SimpleControl()}; - Window.MakeKeyAndVisible(); - return true; - } - } - - -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj b/src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj deleted file mode 100644 index 34c8ae7605..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj +++ /dev/null @@ -1,191 +0,0 @@ - - - - Debug - iPhoneSimulator - {8C923867-8A8F-4F6B-8B80-47D9E8436166} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - Avalonia.iOSTestApplication - Resources - AvaloniaiOSTestApplication - - - true - full - false - bin\iPhoneSimulator\Debug - DEBUG - prompt - 4 - false - i386 - SdkOnly - true - - - none - true - bin\iPhoneSimulator\Release - prompt - 4 - None - i386 - false - - - true - full - false - bin\iPhone\Debug - DEBUG - prompt - 4 - false - ARMv7, ARM64 - Entitlements.plist - iPhone Developer - True - 9.1 - None - False - False - False - False - True - True - True - False - - - - none - true - bin\iPhone\Release - prompt - 4 - Entitlements.plist - ARMv7, ARM64 - false - iPhone Developer - - - none - True - bin\iPhone\Ad-Hoc - prompt - 4 - False - ARMv7, ARM64 - Entitlements.plist - True - Automatic:AdHoc - iPhone Distribution - - - none - True - bin\iPhone\AppStore - prompt - 4 - False - ARMv7, ARM64 - Entitlements.plist - Automatic:AppStore - iPhone Distribution - - - true - - - - - - - - - - - - - - - - - - - - - - - - - {3e53a01a-b331-47f3-b828-4a5717e77a24} - Avalonia.Markup.Xaml - - - {6417e941-21bc-467b-a771-0de389353ce6} - Avalonia.Markup - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {7062ae20-5dcc-4442-9645-8195bdece63e} - Avalonia.Diagnostics - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} - Avalonia.Themes.Default - - - {7d2d3083-71dd-4cc9-8907-39a0d86fb322} - Avalonia.Skia - false - false - - - {4488ad85-1495-4809-9aa4-ddfe0a48527e} - Avalonia.iOS - false - false - - - - - - - - - - - diff --git a/src/iOS/Avalonia.iOSTestApplication/Entitlements.plist b/src/iOS/Avalonia.iOSTestApplication/Entitlements.plist deleted file mode 100644 index 0c67376eba..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/Entitlements.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/iOS/Avalonia.iOSTestApplication/GettingStarted.Xamarin b/src/iOS/Avalonia.iOSTestApplication/GettingStarted.Xamarin deleted file mode 100644 index 810f716685..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/GettingStarted.Xamarin +++ /dev/null @@ -1,4 +0,0 @@ - - GS\iOS\CS\iOSApp\GettingStarted.html - false - \ No newline at end of file diff --git a/src/iOS/Avalonia.iOSTestApplication/Info.plist b/src/iOS/Avalonia.iOSTestApplication/Info.plist deleted file mode 100644 index 44e29bed12..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CFBundleDisplayName - Avalonia.iOSTestApplication - CFBundleIdentifier - com.your-company.Avalonia.iOSTestApplication - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - MinimumOSVersion - 8.0 - CFBundleIconFiles - - Default-568h@2x.png - - - diff --git a/src/iOS/Avalonia.iOSTestApplication/Main.cs b/src/iOS/Avalonia.iOSTestApplication/Main.cs deleted file mode 100644 index a2e0d635cc..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/Main.cs +++ /dev/null @@ -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"); - } - } -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOSTestApplication/Properties/AssemblyInfo.cs b/src/iOS/Avalonia.iOSTestApplication/Properties/AssemblyInfo.cs deleted file mode 100644 index 9154e2a135..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/Properties/AssemblyInfo.cs +++ /dev/null @@ -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")] diff --git a/src/iOS/Avalonia.iOSTestApplication/Resources/Default-568h@2x.png b/src/iOS/Avalonia.iOSTestApplication/Resources/Default-568h@2x.png deleted file mode 100644 index 29973dcbed..0000000000 Binary files a/src/iOS/Avalonia.iOSTestApplication/Resources/Default-568h@2x.png and /dev/null differ diff --git a/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml b/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml deleted file mode 100644 index 214bcd8797..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml.cs b/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml.cs deleted file mode 100644 index b287755ef6..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/SimpleApp.xaml.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/src/iOS/Avalonia.iOSTestApplication/SimpleControl.cs b/src/iOS/Avalonia.iOSTestApplication/SimpleControl.cs deleted file mode 100644 index 7e888f65bc..0000000000 --- a/src/iOS/Avalonia.iOSTestApplication/SimpleControl.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 7abfe29f11..8f1bd5979a 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/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