Merge branch 'master' into rxui-events
This commit is contained in:
+53
-77
@@ -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,11 +216,11 @@ 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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}"
|
||||
EndProject
|
||||
@@ -232,7 +230,6 @@ 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
|
||||
@@ -955,26 +952,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
|
||||
@@ -1847,54 +1824,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
|
||||
@@ -2015,6 +1944,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
|
||||
@@ -2089,7 +2066,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}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup Condition="'$(iOSRoslynPathHackRequired)' == 'true'">
|
||||
<CscToolPath>$(MSBuildToolsPath)\..\Roslyn</CscToolPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -14,7 +14,7 @@
|
||||
<ProjectReference Remove="src/Android/**/*.*proj" />
|
||||
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS') Or $([MSBuild]::IsOsPlatform('Windows')) ">
|
||||
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS')">
|
||||
<ProjectReference Remove="src/iOS/**/*.*proj" />
|
||||
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
+1
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,25 +11,8 @@ namespace ControlCatalog
|
||||
// User Interface of the application, as well as listening (and optionally responding) to
|
||||
// application events from iOS.
|
||||
[Register("AppDelegate")]
|
||||
public partial class AppDelegate : UIApplicationDelegate
|
||||
public partial class AppDelegate : AvaloniaAppDelegate<App>
|
||||
{
|
||||
public override UIWindow Window { get; set; }
|
||||
|
||||
//
|
||||
// This method is invoked when the application has loaded and is ready to run. In this
|
||||
// method you should instantiate the window, load the UI into it and then make the window
|
||||
// visible.
|
||||
//
|
||||
// You have 17 seconds to return from this method, or iOS will terminate your application.
|
||||
//
|
||||
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
|
||||
{
|
||||
AppBuilder.Configure<App>()
|
||||
.UseiOS()
|
||||
.UseSkia().SetupWithoutStarting();
|
||||
Window = new AvaloniaWindow() {Content = new MainView(), StatusBarColor = Colors.LightSteelBlue};
|
||||
Window.MakeKeyAndVisible();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,6 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<Import Project="..\..\build\iOSWorkarounds.props" />
|
||||
<Import Project="..\..\build\LegacyProject.targets" />
|
||||
<Import Project="..\..\build\SkiaSharp.props" />
|
||||
</Project>
|
||||
|
||||
@@ -201,7 +201,6 @@
|
||||
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<Setter Property="GridLinesVisibility" Value="Vertical" />
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource ThemeBorderHighColor}" />
|
||||
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource ThemeBorderHighColor}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowColor}"/>
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Avalonia.Controls
|
||||
/// <summary>
|
||||
/// Stores the current <see cref="ToolTip"/> instance in the control.
|
||||
/// </summary>
|
||||
private static readonly AttachedProperty<ToolTip> ToolTipProperty =
|
||||
internal static readonly AttachedProperty<ToolTip> ToolTipProperty =
|
||||
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
|
||||
|
||||
private IPopupHost _popup;
|
||||
|
||||
@@ -35,6 +35,13 @@ namespace Avalonia.Controls
|
||||
control.PointerEnter += ControlPointerEnter;
|
||||
control.PointerLeave += ControlPointerLeave;
|
||||
}
|
||||
|
||||
if (ToolTip.GetIsOpen(control) && e.NewValue != e.OldValue && !(e.NewValue is ToolTip))
|
||||
{
|
||||
var tip = control.GetValue(ToolTip.ToolTipProperty);
|
||||
|
||||
tip.Content = e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
internal void TipOpenChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
|
||||
@@ -17,12 +17,12 @@ namespace Avalonia.Controls.Utils
|
||||
void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e);
|
||||
}
|
||||
|
||||
internal class CollectionChangedEventManager : IWeakSubscriber<NotifyCollectionChangedEventArgs>
|
||||
internal class CollectionChangedEventManager
|
||||
{
|
||||
public static CollectionChangedEventManager Instance { get; } = new CollectionChangedEventManager();
|
||||
|
||||
private ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>> _entries =
|
||||
new ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>>();
|
||||
private ConditionalWeakTable<INotifyCollectionChanged, Entry> _entries =
|
||||
new ConditionalWeakTable<INotifyCollectionChanged, Entry>();
|
||||
|
||||
private CollectionChangedEventManager()
|
||||
{
|
||||
@@ -34,17 +34,13 @@ namespace Avalonia.Controls.Utils
|
||||
listener = listener ?? throw new ArgumentNullException(nameof(listener));
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
|
||||
if (!_entries.TryGetValue(collection, out var listeners))
|
||||
if (!_entries.TryGetValue(collection, out var entry))
|
||||
{
|
||||
listeners = new List<WeakReference<ICollectionChangedListener>>();
|
||||
_entries.Add(collection, listeners);
|
||||
WeakSubscriptionManager.Subscribe(
|
||||
collection,
|
||||
nameof(INotifyCollectionChanged.CollectionChanged),
|
||||
this);
|
||||
entry = new Entry(collection);
|
||||
_entries.Add(collection, entry);
|
||||
}
|
||||
|
||||
foreach (var l in listeners)
|
||||
foreach (var l in entry.Listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target) && target == listener)
|
||||
{
|
||||
@@ -53,7 +49,7 @@ namespace Avalonia.Controls.Utils
|
||||
}
|
||||
}
|
||||
|
||||
listeners.Add(new WeakReference<ICollectionChangedListener>(listener));
|
||||
entry.Listeners.Add(new WeakReference<ICollectionChangedListener>(listener));
|
||||
}
|
||||
|
||||
public void RemoveListener(INotifyCollectionChanged collection, ICollectionChangedListener listener)
|
||||
@@ -62,8 +58,10 @@ namespace Avalonia.Controls.Utils
|
||||
listener = listener ?? throw new ArgumentNullException(nameof(listener));
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
|
||||
if (_entries.TryGetValue(collection, out var listeners))
|
||||
if (_entries.TryGetValue(collection, out var entry))
|
||||
{
|
||||
var listeners = entry.Listeners;
|
||||
|
||||
for (var i = 0; i < listeners.Count; ++i)
|
||||
{
|
||||
if (listeners[i].TryGetTarget(out var target) && target == listener)
|
||||
@@ -72,10 +70,7 @@ namespace Avalonia.Controls.Utils
|
||||
|
||||
if (listeners.Count == 0)
|
||||
{
|
||||
WeakSubscriptionManager.Unsubscribe(
|
||||
collection,
|
||||
nameof(INotifyCollectionChanged.CollectionChanged),
|
||||
this);
|
||||
entry.Dispose();
|
||||
_entries.Remove(collection);
|
||||
}
|
||||
|
||||
@@ -88,51 +83,72 @@ namespace Avalonia.Controls.Utils
|
||||
"Collection listener not registered for this collection/listener combination.");
|
||||
}
|
||||
|
||||
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private class Entry : IWeakSubscriber<NotifyCollectionChangedEventArgs>, IDisposable
|
||||
{
|
||||
static void Notify(
|
||||
INotifyCollectionChanged incc,
|
||||
NotifyCollectionChangedEventArgs args,
|
||||
List<WeakReference<ICollectionChangedListener>> listeners)
|
||||
private INotifyCollectionChanged _collection;
|
||||
|
||||
public Entry(INotifyCollectionChanged collection)
|
||||
{
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.PreChanged(incc, args);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.Changed(incc, args);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.PostChanged(incc, args);
|
||||
}
|
||||
}
|
||||
_collection = collection;
|
||||
Listeners = new List<WeakReference<ICollectionChangedListener>>();
|
||||
WeakSubscriptionManager.Subscribe(
|
||||
_collection,
|
||||
nameof(INotifyCollectionChanged.CollectionChanged),
|
||||
this);
|
||||
}
|
||||
|
||||
if (sender is INotifyCollectionChanged incc && _entries.TryGetValue(incc, out var listeners))
|
||||
public List<WeakReference<ICollectionChangedListener>> Listeners { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var l = listeners.ToList();
|
||||
WeakSubscriptionManager.Unsubscribe(
|
||||
_collection,
|
||||
nameof(INotifyCollectionChanged.CollectionChanged),
|
||||
this);
|
||||
}
|
||||
|
||||
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
static void Notify(
|
||||
INotifyCollectionChanged incc,
|
||||
NotifyCollectionChangedEventArgs args,
|
||||
List<WeakReference<ICollectionChangedListener>> listeners)
|
||||
{
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.PreChanged(incc, args);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.Changed(incc, args);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var l in listeners)
|
||||
{
|
||||
if (l.TryGetTarget(out var target))
|
||||
{
|
||||
target.PostChanged(incc, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var l = Listeners.ToList();
|
||||
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
Notify(incc, e, l);
|
||||
Notify(_collection, e, l);
|
||||
}
|
||||
else
|
||||
{
|
||||
var inccCapture = incc;
|
||||
var eCapture = e;
|
||||
Dispatcher.UIThread.Post(() => Notify(inccCapture, eCapture, l));
|
||||
Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
private readonly IVisual _control;
|
||||
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
|
||||
private AvaloniaPropertyViewModel _selectedProperty;
|
||||
private string _propertyFilter;
|
||||
|
||||
public ControlDetailsViewModel(IVisual control, string propertyFilter)
|
||||
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
|
||||
{
|
||||
_control = control;
|
||||
|
||||
TreePage = treePage;
|
||||
|
||||
var properties = GetAvaloniaProperties(control)
|
||||
.Concat(GetClrProperties(control))
|
||||
.OrderBy(x => x, PropertyComparer.Instance)
|
||||
@@ -25,7 +26,6 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
.ToList();
|
||||
|
||||
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList());
|
||||
_propertyFilter = propertyFilter;
|
||||
|
||||
var view = new DataGridCollectionView(properties);
|
||||
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));
|
||||
@@ -43,19 +43,9 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public DataGridCollectionView PropertiesView { get; }
|
||||
public TreePageViewModel TreePage { get; }
|
||||
|
||||
public string PropertyFilter
|
||||
{
|
||||
get => _propertyFilter;
|
||||
set
|
||||
{
|
||||
if (RaiseAndSetIfChanged(ref _propertyFilter, value))
|
||||
{
|
||||
PropertiesView.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
public DataGridCollectionView PropertiesView { get; }
|
||||
|
||||
public AvaloniaPropertyViewModel SelectedProperty
|
||||
{
|
||||
@@ -81,7 +71,7 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
if (o is AvaloniaObject ao)
|
||||
{
|
||||
return AvaloniaPropertyRegistry.Instance.GetRegistered(ao)
|
||||
.Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(ao.GetType()))
|
||||
.Union(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(ao.GetType()))
|
||||
.Select(x => new AvaloniaPropertyViewModel(ao, x));
|
||||
}
|
||||
else
|
||||
@@ -137,9 +127,14 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
|
||||
private bool FilterProperty(object arg)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(PropertyFilter) && arg is PropertyViewModel property)
|
||||
if (!string.IsNullOrWhiteSpace(TreePage.PropertyFilter) && arg is PropertyViewModel property)
|
||||
{
|
||||
return property.Name.IndexOf(PropertyFilter, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
if (TreePage.UseRegexFilter)
|
||||
{
|
||||
return TreePage.FilterRegex?.IsMatch(property.Name) ?? true;
|
||||
}
|
||||
|
||||
return property.Name.IndexOf(TreePage.PropertyFilter, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Selection;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Avalonia.Diagnostics.ViewModels
|
||||
{
|
||||
internal class TreePageViewModel : ViewModelBase, IDisposable
|
||||
internal class TreePageViewModel : ViewModelBase, IDisposable, INotifyDataErrorInfo
|
||||
{
|
||||
private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
|
||||
private TreeNode _selectedNode;
|
||||
private ControlDetailsViewModel _details;
|
||||
private string _propertyFilter;
|
||||
private string _propertyFilter = string.Empty;
|
||||
private bool _useRegexFilter;
|
||||
|
||||
public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
|
||||
{
|
||||
@@ -26,15 +31,10 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
get => _selectedNode;
|
||||
private set
|
||||
{
|
||||
if (Details != null)
|
||||
{
|
||||
_propertyFilter = Details.PropertyFilter;
|
||||
}
|
||||
|
||||
if (RaiseAndSetIfChanged(ref _selectedNode, value))
|
||||
{
|
||||
Details = value != null ?
|
||||
new ControlDetailsViewModel(value.Visual, _propertyFilter) :
|
||||
new ControlDetailsViewModel(this, value.Visual) :
|
||||
null;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,63 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public Regex FilterRegex { get; set; }
|
||||
|
||||
private void UpdateFilterRegex()
|
||||
{
|
||||
void ClearError()
|
||||
{
|
||||
if (_errors.Remove(nameof(PropertyFilter)))
|
||||
{
|
||||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter)));
|
||||
}
|
||||
}
|
||||
|
||||
if (UseRegexFilter)
|
||||
{
|
||||
try
|
||||
{
|
||||
FilterRegex = new Regex(PropertyFilter, RegexOptions.Compiled);
|
||||
ClearError();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_errors[nameof(PropertyFilter)] = exception.Message;
|
||||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearError();
|
||||
}
|
||||
}
|
||||
|
||||
public string PropertyFilter
|
||||
{
|
||||
get => _propertyFilter;
|
||||
set
|
||||
{
|
||||
if (RaiseAndSetIfChanged(ref _propertyFilter, value))
|
||||
{
|
||||
UpdateFilterRegex();
|
||||
Details.PropertiesView.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseRegexFilter
|
||||
{
|
||||
get => _useRegexFilter;
|
||||
set
|
||||
{
|
||||
if (RaiseAndSetIfChanged(ref _useRegexFilter, value))
|
||||
{
|
||||
UpdateFilterRegex();
|
||||
Details.PropertiesView.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var node in Nodes)
|
||||
@@ -130,5 +187,17 @@ namespace Avalonia.Diagnostics.ViewModels
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable GetErrors(string propertyName)
|
||||
{
|
||||
if (_errors.TryGetValue(propertyName, out var error))
|
||||
{
|
||||
yield return error;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasErrors => _errors.Count > 0;
|
||||
|
||||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,31 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
|
||||
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView">
|
||||
<Grid ColumnDefinitions="*">
|
||||
<DockPanel Grid.Column="0">
|
||||
<TextBox DockPanel.Dock="Top"
|
||||
BorderThickness="0"
|
||||
Text="{Binding PropertyFilter}"
|
||||
Watermark="Filter properties"/>
|
||||
<DataGrid Items="{Binding PropertiesView}"
|
||||
BorderThickness="0"
|
||||
RowBackground="Transparent"
|
||||
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}"
|
||||
CanUserResizeColumns="true">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,*">
|
||||
|
||||
<TextBox Grid.Row="0" Grid.Column="0"
|
||||
BorderThickness="0"
|
||||
Text="{Binding TreePage.PropertyFilter}"
|
||||
Watermark="Filter properties" />
|
||||
|
||||
<CheckBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Content="Regex"
|
||||
IsChecked="{Binding TreePage.UseRegexFilter}"/>
|
||||
|
||||
<DataGrid Items="{Binding PropertiesView}"
|
||||
Grid.Row="1" Grid.ColumnSpan="2"
|
||||
BorderThickness="0"
|
||||
RowBackground="Transparent"
|
||||
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}"
|
||||
CanUserResizeColumns="true">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Value" Binding="{Binding Value}" />
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}" />
|
||||
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -45,34 +45,35 @@
|
||||
</TextBlock>
|
||||
|
||||
<DataValidationErrors>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
|
||||
<TextBlock Name="watermark"
|
||||
Opacity="0.5"
|
||||
Text="{TemplateBinding Watermark}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"
|
||||
TextWrapping="{TemplateBinding TextWrapping}"
|
||||
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
|
||||
Grid.Column="1" Grid.ColumnSpan="1"/>
|
||||
<TextPresenter Name="PART_TextPresenter"
|
||||
Text="{TemplateBinding Text, Mode=TwoWay}"
|
||||
CaretIndex="{TemplateBinding CaretIndex}"
|
||||
SelectionStart="{TemplateBinding SelectionStart}"
|
||||
SelectionEnd="{TemplateBinding SelectionEnd}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"
|
||||
TextWrapping="{TemplateBinding TextWrapping}"
|
||||
PasswordChar="{TemplateBinding PasswordChar}"
|
||||
RevealPassword="{TemplateBinding RevealPassword}"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
CaretBrush="{TemplateBinding CaretBrush}"
|
||||
Grid.Column="1" Grid.ColumnSpan="1"/>
|
||||
<ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
|
||||
<ScrollViewer Grid.Column="1" Grid.ColumnSpan="1"
|
||||
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
|
||||
<Panel>
|
||||
<TextBlock Name="watermark"
|
||||
Opacity="0.5"
|
||||
Text="{TemplateBinding Watermark}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"
|
||||
TextWrapping="{TemplateBinding TextWrapping}"
|
||||
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
|
||||
<TextPresenter Name="PART_TextPresenter"
|
||||
Text="{TemplateBinding Text, Mode=TwoWay}"
|
||||
CaretIndex="{TemplateBinding CaretIndex}"
|
||||
SelectionStart="{TemplateBinding SelectionStart}"
|
||||
SelectionEnd="{TemplateBinding SelectionEnd}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"
|
||||
TextWrapping="{TemplateBinding TextWrapping}"
|
||||
PasswordChar="{TemplateBinding PasswordChar}"
|
||||
RevealPassword="{TemplateBinding RevealPassword}"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
CaretBrush="{TemplateBinding CaretBrush}" />
|
||||
</Panel>
|
||||
</ScrollViewer>
|
||||
<ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
|
||||
</Grid>
|
||||
</DataValidationErrors>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
@@ -129,7 +130,7 @@
|
||||
<Style Selector="TextBox.revealPasswordButton[AcceptsReturn=False][IsReadOnly=False]:not(TextBox:empty)">
|
||||
<Setter Property="InnerRightContent">
|
||||
<Template>
|
||||
<Panel Margin="0,0,4,0">
|
||||
<Panel Margin="4,0">
|
||||
<Panel.Styles>
|
||||
<Style Selector="ToggleButton[IsChecked=True]">
|
||||
<Setter Property="(ToolTip.Tip)" Value="Hide Password" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<Panel>
|
||||
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
|
||||
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
|
||||
<Panel Background="Transparent" Margin="{TemplateBinding WindowDecorationMargin}" />
|
||||
<VisualLayerManager>
|
||||
<VisualLayerManager.ChromeOverlayLayer>
|
||||
<TitleBar />
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<Panel>
|
||||
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
|
||||
<Border Background="{TemplateBinding Background}" IsHitTestVisible="False" />
|
||||
<Panel Background="Transparent" Margin="{TemplateBinding WindowDecorationMargin}" />
|
||||
<VisualLayerManager>
|
||||
<VisualLayerManager.ChromeOverlayLayer>
|
||||
<TitleBar />
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
|
||||
/// Gets the state of the line when broken by line breaking process.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="LineBreak"/> value that represents the line break.
|
||||
/// A <see cref="TextLineBreak"/> value that represents the line break.
|
||||
/// </returns>
|
||||
public abstract TextLineBreak LineBreak { get; }
|
||||
public abstract TextLineBreak TextLineBreak { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the line is collapsed.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
{
|
||||
_textRuns = textRuns;
|
||||
LineMetrics = lineMetrics;
|
||||
LineBreak = lineBreak;
|
||||
TextLineBreak = lineBreak;
|
||||
HasCollapsed = hasCollapsed;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
public override TextLineMetrics LineMetrics { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override TextLineBreak LineBreak { get; }
|
||||
public override TextLineBreak TextLineBreak { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasCollapsed { get; }
|
||||
@@ -122,7 +122,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
|
||||
LineMetrics.TextBaseline, textRange, false);
|
||||
|
||||
return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
|
||||
return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true);
|
||||
}
|
||||
|
||||
availableWidth -= currentRun.GlyphRun.Bounds.Width;
|
||||
@@ -268,6 +268,17 @@ namespace Avalonia.Media.TextFormatting
|
||||
var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength ==
|
||||
TextRange.Length;
|
||||
|
||||
var characterIndex = codepointIndex - run.Text.Start;
|
||||
|
||||
var codepoint = Codepoint.ReadAt(run.GlyphRun.Characters, characterIndex, out _);
|
||||
|
||||
if (codepoint.IsBreakChar)
|
||||
{
|
||||
foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(codepointIndex - 1, out _);
|
||||
|
||||
isAtEnd = true;
|
||||
}
|
||||
|
||||
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
|
||||
foundCharacterHit :
|
||||
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<Import Project="Sdk.props" Sdk="MSBuild.Sdk.Extras" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>xamarin.ios10</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" ExcludeAssets="All" />
|
||||
<Compile Update="Boilerplate\Shared.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Boilerplate\RuntimePlatform.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK-1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
|
||||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
|
||||
<Import Project="..\..\..\build\Rx.props" />
|
||||
<Import Project="Sdk.targets" Sdk="MSBuild.Sdk.Extras" />
|
||||
<Import Project="..\..\..\build\iOSWorkarounds.props" />
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
public class AvaloniaAppDelegate<TApp> : UIResponder, IUIApplicationDelegate
|
||||
where TApp : Application, new()
|
||||
{
|
||||
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
|
||||
|
||||
[Export("window")]
|
||||
public UIWindow Window { get; set; }
|
||||
|
||||
[Export("application:didFinishLaunchingWithOptions:")]
|
||||
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
|
||||
{
|
||||
var builder = AppBuilder.Configure<TApp>();
|
||||
CustomizeAppBuilder(builder);
|
||||
var lifetime = new Lifetime();
|
||||
builder.AfterSetup(_ =>
|
||||
{
|
||||
Window = new UIWindow();
|
||||
var view = new AvaloniaView();
|
||||
lifetime.View = view;
|
||||
Window.RootViewController = new UIViewController
|
||||
{
|
||||
View = view
|
||||
};
|
||||
});
|
||||
|
||||
builder.SetupWithLifetime(lifetime);
|
||||
|
||||
Window.Hidden = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
class Lifetime : ISingleViewApplicationLifetime
|
||||
{
|
||||
public AvaloniaView View;
|
||||
public Control MainView
|
||||
{
|
||||
get => View.Content;
|
||||
set => View.Content = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
public Thickness Padding
|
||||
{
|
||||
get { return _padding; }
|
||||
set
|
||||
{
|
||||
_padding = value;
|
||||
SetNeedsLayout();
|
||||
}
|
||||
}
|
||||
internal IInputRoot InputRoot { get; private set; }
|
||||
private TopLevelImpl _topLevelImpl;
|
||||
private EmbeddableControlRoot _topLevel;
|
||||
private TouchHandler _touches;
|
||||
|
||||
public AvaloniaView()
|
||||
{
|
||||
_topLevelImpl = new TopLevelImpl(this);
|
||||
_touches = new TouchHandler(this, _topLevelImpl);
|
||||
_topLevel = new EmbeddableControlRoot(_topLevelImpl);
|
||||
_topLevel.Prepare();
|
||||
|
||||
_impl = new EmbeddableImpl();
|
||||
AddSubview(_impl);
|
||||
BackgroundColor = UIColor.White;
|
||||
AutoresizingMask = UIViewAutoresizing.All;
|
||||
_root = new EmbeddableControlRoot(_impl);
|
||||
_root.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
|
||||
{
|
||||
private readonly AvaloniaView _view;
|
||||
public AvaloniaView View => _view;
|
||||
|
||||
public TopLevelImpl(AvaloniaView view)
|
||||
{
|
||||
_view = view;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer(IRenderRoot root) => new DeferredRenderer(root,
|
||||
AvaloniaLocator.Current.GetService<IRenderLoop>());
|
||||
|
||||
public void Invalidate(Rect rect)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public void SetInputRoot(IInputRoot inputRoot)
|
||||
{
|
||||
_view.InputRoot = inputRoot;
|
||||
}
|
||||
|
||||
public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
|
||||
|
||||
public PixelPoint PointToScreen(Point point) => new PixelPoint((int) point.X, (int) point.Y);
|
||||
|
||||
public void SetCursor(IPlatformHandle cursor)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public IPopupImpl CreatePopup()
|
||||
{
|
||||
// In-window popups
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height);
|
||||
public double RenderScaling => _view.ContentScaleFactor;
|
||||
public IEnumerable<object> Surfaces { get; set; }
|
||||
public Action<RawInputEventArgs> Input { get; set; }
|
||||
public Action<Rect> Paint { get; set; }
|
||||
public Action<Size> Resized { get; set; }
|
||||
public Action<double> ScalingChanged { get; set; }
|
||||
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
|
||||
public Action Closed { get; set; }
|
||||
|
||||
public Action LostFocus { get; set; }
|
||||
|
||||
// legacy no-op
|
||||
public IMouseDevice MouseDevice { get; } = new MouseDevice();
|
||||
public WindowTransparencyLevel TransparencyLevel { get; }
|
||||
|
||||
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
|
||||
new AcrylicPlatformCompensationLevels();
|
||||
}
|
||||
|
||||
[Export("layerClass")]
|
||||
public static Class LayerClass()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.iOS;
|
||||
using Avalonia.Shared.PlatformSupport;
|
||||
|
||||
namespace Avalonia
|
||||
@@ -6,9 +7,9 @@ namespace Avalonia
|
||||
public class AppBuilder : AppBuilderBase<AppBuilder>
|
||||
{
|
||||
public AppBuilder() : base(new StandardRuntimePlatform(),
|
||||
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly))
|
||||
b => StandardRuntimePlatformServices.Register(b.ApplicationType.Assembly))
|
||||
{
|
||||
|
||||
this.UseSkia().UseWindowingSubsystem(iOS.Platform.Register);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Interop;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Shared.PlatformSupport
|
||||
{
|
||||
static class StandardRuntimePlatformServices
|
||||
{
|
||||
public static void Register(Assembly assembly = null)
|
||||
{
|
||||
var standardPlatform = new StandardRuntimePlatform();
|
||||
AssetLoader.RegisterResUriParsers();
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<IRuntimePlatform>().ToConstant(standardPlatform)
|
||||
.Bind<IAssetLoader>().ToConstant(new AssetLoader(assembly))
|
||||
.Bind<IDynamicLibraryLoader>().ToConstant(
|
||||
#if __IOS__
|
||||
new IOSLoader()
|
||||
#else
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? (IDynamicLibraryLoader)new Win32Loader()
|
||||
: new UnixLoader()
|
||||
#endif
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal partial class StandardRuntimePlatform : IRuntimePlatform
|
||||
{
|
||||
public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
|
||||
{
|
||||
return new Timer(_ => tick(), null, interval, interval);
|
||||
}
|
||||
|
||||
public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
|
||||
|
||||
class UnmanagedBlob : IUnmanagedBlob
|
||||
{
|
||||
private readonly StandardRuntimePlatform _plat;
|
||||
private IntPtr _address;
|
||||
private readonly object _lock = new object();
|
||||
#if DEBUG
|
||||
private static readonly List<string> Backtraces = new List<string>();
|
||||
private static Thread GCThread;
|
||||
private readonly string _backtrace;
|
||||
private static readonly object _btlock = new object();
|
||||
|
||||
class GCThreadDetector
|
||||
{
|
||||
~GCThreadDetector()
|
||||
{
|
||||
GCThread = Thread.CurrentThread;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void Spawn() => new GCThreadDetector();
|
||||
|
||||
static UnmanagedBlob()
|
||||
{
|
||||
Spawn();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
throw new ArgumentException("Positive number required", nameof(size));
|
||||
_plat = plat;
|
||||
_address = plat.Alloc(size);
|
||||
GC.AddMemoryPressure(size);
|
||||
Size = size;
|
||||
#if DEBUG
|
||||
_backtrace = Environment.StackTrace;
|
||||
lock (_btlock)
|
||||
Backtraces.Add(_backtrace);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DoDispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
#if DEBUG
|
||||
lock (_btlock)
|
||||
Backtraces.Remove(_backtrace);
|
||||
#endif
|
||||
_plat?.Free(_address, Size);
|
||||
GC.RemoveMemoryPressure(Size);
|
||||
IsDisposed = true;
|
||||
_address = IntPtr.Zero;
|
||||
Size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if DEBUG
|
||||
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
|
||||
+ Environment.StackTrace
|
||||
+ "\n\nBlob created by " + _backtrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~UnmanagedBlob()
|
||||
{
|
||||
#if DEBUG
|
||||
Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
|
||||
#endif
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public IntPtr Address => IsDisposed ? throw new ObjectDisposedException("UnmanagedBlob") : _address;
|
||||
public int Size { get; private set; }
|
||||
public bool IsDisposed { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if NET461 || NETCOREAPP2_0
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int munmap(IntPtr addr, IntPtr length);
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern long sysconf(int name);
|
||||
|
||||
private bool? _useMmap;
|
||||
private bool UseMmap
|
||||
=> _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
|
||||
|
||||
IntPtr Alloc(int size)
|
||||
{
|
||||
if (UseMmap)
|
||||
{
|
||||
var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
|
||||
if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
|
||||
{
|
||||
var errno = Marshal.GetLastWin32Error();
|
||||
throw new Exception("Unable to allocate memory: " + errno);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
else
|
||||
return Marshal.AllocHGlobal(size);
|
||||
}
|
||||
|
||||
void Free(IntPtr ptr, int len)
|
||||
{
|
||||
if (UseMmap)
|
||||
{
|
||||
if (munmap(ptr, new IntPtr(len)) == -1)
|
||||
{
|
||||
var errno = Marshal.GetLastWin32Error();
|
||||
throw new Exception("Unable to free memory: " + errno);
|
||||
}
|
||||
}
|
||||
else
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
#else
|
||||
IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
|
||||
void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal class IOSLoader : IDynamicLibraryLoader
|
||||
{
|
||||
IntPtr IDynamicLibraryLoader.LoadLibrary(string dll)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class AssetLoader : IAssetLoader
|
||||
{
|
||||
private const string AvaloniaResourceName = "!AvaloniaResources";
|
||||
private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
|
||||
= new Dictionary<string, AssemblyDescriptor>();
|
||||
|
||||
private AssemblyDescriptor _defaultResmAssembly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assembly">
|
||||
/// The default assembly from which to load resm: assets for which no assembly is specified.
|
||||
/// </param>
|
||||
public AssetLoader(Assembly assembly = null)
|
||||
{
|
||||
if (assembly == null)
|
||||
assembly = Assembly.GetEntryAssembly();
|
||||
if (assembly != null)
|
||||
_defaultResmAssembly = new AssemblyDescriptor(assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default assembly from which to load assets for which no assembly is specified.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The default assembly.</param>
|
||||
public void SetDefaultAssembly(Assembly assembly)
|
||||
{
|
||||
_defaultResmAssembly = new AssemblyDescriptor(assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an asset with the specified URI exists.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="baseUri">
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
/// </param>
|
||||
/// <returns>True if the asset could be found; otherwise false.</returns>
|
||||
public bool Exists(Uri uri, Uri baseUri = null)
|
||||
{
|
||||
return GetAsset(uri, baseUri) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the asset with the requested URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="baseUri">
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
/// </param>
|
||||
/// <returns>A stream containing the asset contents.</returns>
|
||||
/// <exception cref="FileNotFoundException">
|
||||
/// The asset could not be found.
|
||||
/// </exception>
|
||||
public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the asset with the requested URI and returns the asset stream and the
|
||||
/// assembly containing the asset.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="baseUri">
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The stream containing the resource contents together with the assembly.
|
||||
/// </returns>
|
||||
/// <exception cref="FileNotFoundException">
|
||||
/// The asset could not be found.
|
||||
/// </exception>
|
||||
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
|
||||
{
|
||||
var asset = GetAsset(uri, baseUri);
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
throw new FileNotFoundException($"The resource {uri} could not be found.");
|
||||
}
|
||||
|
||||
return (asset.GetStream(), asset.Assembly);
|
||||
}
|
||||
|
||||
public Assembly GetAssembly(Uri uri, Uri baseUri)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri && baseUri != null)
|
||||
uri = new Uri(baseUri, uri);
|
||||
return GetAssembly(uri).Assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all assets of a folder and subfolders that match specified uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
|
||||
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
|
||||
public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
|
||||
{
|
||||
if (uri.IsAbsoluteUri && uri.Scheme == "resm")
|
||||
{
|
||||
var assembly = GetAssembly(uri);
|
||||
|
||||
return assembly?.Resources.Where(x => x.Key.Contains(uri.AbsolutePath))
|
||||
.Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
|
||||
Enumerable.Empty<Uri>();
|
||||
}
|
||||
|
||||
uri = EnsureAbsolute(uri, baseUri);
|
||||
if (uri.Scheme == "avares")
|
||||
{
|
||||
var (asm, path) = GetResAsmAndPath(uri);
|
||||
if (asm == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"No default assembly, entry assembly or explicit assembly specified; " +
|
||||
"don't know where to look up for the resource, try specifying assembly explicitly.");
|
||||
}
|
||||
|
||||
if (asm?.AvaloniaResources == null)
|
||||
return Enumerable.Empty<Uri>();
|
||||
path = path.TrimEnd('/') + '/';
|
||||
return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path))
|
||||
.Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
|
||||
}
|
||||
|
||||
return Enumerable.Empty<Uri>();
|
||||
}
|
||||
|
||||
private Uri EnsureAbsolute(Uri uri, Uri baseUri)
|
||||
{
|
||||
if (uri.IsAbsoluteUri)
|
||||
return uri;
|
||||
if(baseUri == null)
|
||||
throw new ArgumentException($"Relative uri {uri} without base url");
|
||||
if (!baseUri.IsAbsoluteUri)
|
||||
throw new ArgumentException($"Base uri {baseUri} is relative");
|
||||
if (baseUri.Scheme == "resm")
|
||||
throw new ArgumentException(
|
||||
$"Relative uris for 'resm' scheme aren't supported; {baseUri} uses resm");
|
||||
return new Uri(baseUri, uri);
|
||||
}
|
||||
|
||||
private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
|
||||
{
|
||||
if (uri.IsAbsoluteUri && uri.Scheme == "resm")
|
||||
{
|
||||
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
|
||||
|
||||
if (asm == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"No default assembly, entry assembly or explicit assembly specified; " +
|
||||
"don't know where to look up for the resource, try specifying assembly explicitly.");
|
||||
}
|
||||
|
||||
IAssetDescriptor rv;
|
||||
|
||||
var resourceKey = uri.AbsolutePath;
|
||||
asm.Resources.TryGetValue(resourceKey, out rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
uri = EnsureAbsolute(uri, baseUri);
|
||||
|
||||
if (uri.Scheme == "avares")
|
||||
{
|
||||
var (asm, path) = GetResAsmAndPath(uri);
|
||||
if (asm.AvaloniaResources == null)
|
||||
return null;
|
||||
asm.AvaloniaResources.TryGetValue(path, out var desc);
|
||||
return desc;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
|
||||
}
|
||||
|
||||
private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
|
||||
{
|
||||
var asm = GetAssembly(uri.Authority);
|
||||
return (asm, uri.AbsolutePath);
|
||||
}
|
||||
|
||||
private AssemblyDescriptor GetAssembly(Uri uri)
|
||||
{
|
||||
if (uri != null)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri)
|
||||
return null;
|
||||
if (uri.Scheme == "avares")
|
||||
return GetResAsmAndPath(uri).asm;
|
||||
|
||||
if (uri.Scheme == "resm")
|
||||
{
|
||||
var qs = ParseQueryString(uri);
|
||||
string assemblyName;
|
||||
|
||||
if (qs.TryGetValue("assembly", out assemblyName))
|
||||
{
|
||||
return GetAssembly(assemblyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AssemblyDescriptor GetAssembly(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
AssemblyDescriptor rv;
|
||||
if (!AssemblyNameCache.TryGetValue(name, out rv))
|
||||
{
|
||||
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
|
||||
if (match != null)
|
||||
{
|
||||
AssemblyNameCache[name] = rv = new AssemblyDescriptor(match);
|
||||
}
|
||||
else
|
||||
{
|
||||
// iOS does not support loading assemblies dynamically!
|
||||
//
|
||||
#if __IOS__
|
||||
throw new InvalidOperationException(
|
||||
$"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
|
||||
#else
|
||||
name = Uri.UnescapeDataString(name);
|
||||
AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseQueryString(Uri uri)
|
||||
{
|
||||
return uri.Query.TrimStart('?')
|
||||
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => p.Split('='))
|
||||
.ToDictionary(p => p[0], p => p[1]);
|
||||
}
|
||||
|
||||
private interface IAssetDescriptor
|
||||
{
|
||||
Stream GetStream();
|
||||
Assembly Assembly { get; }
|
||||
}
|
||||
|
||||
private class AssemblyResourceDescriptor : IAssetDescriptor
|
||||
{
|
||||
private readonly Assembly _asm;
|
||||
private readonly string _name;
|
||||
|
||||
public AssemblyResourceDescriptor(Assembly asm, string name)
|
||||
{
|
||||
_asm = asm;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public Stream GetStream()
|
||||
{
|
||||
return _asm.GetManifestResourceStream(_name);
|
||||
}
|
||||
|
||||
public Assembly Assembly => _asm;
|
||||
}
|
||||
|
||||
private class AvaloniaResourceDescriptor : IAssetDescriptor
|
||||
{
|
||||
private readonly int _offset;
|
||||
private readonly int _length;
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
public AvaloniaResourceDescriptor(Assembly asm, int offset, int length)
|
||||
{
|
||||
_offset = offset;
|
||||
_length = length;
|
||||
Assembly = asm;
|
||||
}
|
||||
|
||||
public Stream GetStream()
|
||||
{
|
||||
return new SlicedStream(Assembly.GetManifestResourceStream(AvaloniaResourceName), _offset, _length);
|
||||
}
|
||||
}
|
||||
|
||||
class SlicedStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly int _from;
|
||||
|
||||
public SlicedStream(Stream baseStream, int from, int length)
|
||||
{
|
||||
Length = length;
|
||||
_baseStream = baseStream;
|
||||
_from = from;
|
||||
_baseStream.Position = from;
|
||||
}
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position));
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
if (origin == SeekOrigin.Begin)
|
||||
Position = offset;
|
||||
if (origin == SeekOrigin.End)
|
||||
Position = _from + Length + offset;
|
||||
if (origin == SeekOrigin.Current)
|
||||
Position = Position + offset;
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => _baseStream.CanRead;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length { get; }
|
||||
public override long Position
|
||||
{
|
||||
get => _baseStream.Position - _from;
|
||||
set => _baseStream.Position = value + _from;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
|
||||
public override void Close() => _baseStream.Close();
|
||||
}
|
||||
|
||||
private class AssemblyDescriptor
|
||||
{
|
||||
public AssemblyDescriptor(Assembly assembly)
|
||||
{
|
||||
Assembly = assembly;
|
||||
|
||||
if (assembly != null)
|
||||
{
|
||||
Resources = assembly.GetManifestResourceNames()
|
||||
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
|
||||
Name = assembly.GetName().Name;
|
||||
using (var resources = assembly.GetManifestResourceStream(AvaloniaResourceName))
|
||||
{
|
||||
if (resources != null)
|
||||
{
|
||||
Resources.Remove(AvaloniaResourceName);
|
||||
|
||||
var indexLength = new BinaryReader(resources).ReadInt32();
|
||||
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
|
||||
var baseOffset = indexLength + 4;
|
||||
AvaloniaResources = index.ToDictionary(r => "/" + r.Path.TrimStart('/'), r => (IAssetDescriptor)
|
||||
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Assembly Assembly { get; }
|
||||
public Dictionary<string, IAssetDescriptor> Resources { get; }
|
||||
public Dictionary<string, IAssetDescriptor> AvaloniaResources { get; }
|
||||
public string Name { get; }
|
||||
}
|
||||
|
||||
public static void RegisterResUriParsers()
|
||||
{
|
||||
if (!UriParser.IsKnownScheme("avares"))
|
||||
UriParser.Register(new GenericUriParser(
|
||||
GenericUriParserOptions.GenericAuthority |
|
||||
GenericUriParserOptions.NoUserInfo |
|
||||
GenericUriParserOptions.NoPort |
|
||||
GenericUriParserOptions.NoQuery |
|
||||
GenericUriParserOptions.NoFragment), "avares", -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
public class Clipboard : IClipboard
|
||||
public class ClipboardImpl : IClipboard
|
||||
{
|
||||
public Task<string> GetTextAsync()
|
||||
{
|
||||
@@ -29,4 +31,4 @@ namespace Avalonia.iOS
|
||||
|
||||
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using Avalonia.Rendering;
|
||||
using CoreAnimation;
|
||||
using Foundation;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
class DisplayLinkRenderTimer : IRenderTimer
|
||||
{
|
||||
public event Action<TimeSpan> Tick;
|
||||
private CADisplayLink _link;
|
||||
|
||||
public DisplayLinkRenderTimer()
|
||||
{
|
||||
|
||||
_link = CADisplayLink.Create(OnFrame);
|
||||
_link.AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
|
||||
}
|
||||
|
||||
private void OnFrame()
|
||||
{
|
||||
try
|
||||
{
|
||||
Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//TODO: log
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Rendering;
|
||||
using CoreAnimation;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
class DisplayLinkTimer : IRenderTimer
|
||||
{
|
||||
public event Action<TimeSpan> Tick;
|
||||
private Stopwatch _st = Stopwatch.StartNew();
|
||||
|
||||
public DisplayLinkTimer()
|
||||
{
|
||||
var link = CADisplayLink.Create(OnLinkTick);
|
||||
TimerThread = new Thread(() =>
|
||||
{
|
||||
link.AddToRunLoop(NSRunLoop.Current, NSRunLoopMode.Common);
|
||||
NSRunLoop.Current.Run();
|
||||
});
|
||||
TimerThread.Start();
|
||||
UIApplication.Notifications.ObserveDidEnterBackground((_,__) => link.Paused = true);
|
||||
UIApplication.Notifications.ObserveWillEnterForeground((_, __) => link.Paused = false);
|
||||
}
|
||||
|
||||
public Thread TimerThread { get; }
|
||||
|
||||
private void OnLinkTick()
|
||||
{
|
||||
Tick?.Invoke(_st.Elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia.Platform;
|
||||
using CoreGraphics;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This is a bit weird, but CG doesn't provide proper bitmap
|
||||
/// with lockable bits, but can create one from data pointer
|
||||
/// So we are using our own buffer here.
|
||||
/// </summary>
|
||||
class EmulatedFramebuffer : ILockedFramebuffer
|
||||
{
|
||||
private nfloat _viewWidth;
|
||||
private nfloat _viewHeight;
|
||||
|
||||
public EmulatedFramebuffer(UIView view)
|
||||
{
|
||||
var factor = (int) UIScreen.MainScreen.Scale;
|
||||
var frame = view.Frame;
|
||||
_viewWidth = frame.Width;
|
||||
_viewHeight = frame.Height;
|
||||
Size = new PixelSize((int)frame.Width * factor, (int)frame.Height * factor);
|
||||
RowBytes = Size.Width * 4;
|
||||
Dpi = new Vector(96, 96) * factor;
|
||||
Format = PixelFormat.Rgba8888;
|
||||
Address = Marshal.AllocHGlobal(Size.Height * RowBytes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Address == IntPtr.Zero)
|
||||
return;
|
||||
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
|
||||
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
|
||||
using (var bContext = new CGBitmapContext(Address, Size.Width, Size.Height, 8, Size.Width * 4,
|
||||
colorSpace, (CGImageAlphaInfo) nfo))
|
||||
using (var image = bContext.ToImage())
|
||||
using (var context = UIGraphics.GetCurrentContext())
|
||||
{
|
||||
// flip the image for CGContext.DrawImage
|
||||
context.TranslateCTM(0, _viewHeight);
|
||||
context.ScaleCTM(1, -1);
|
||||
context.DrawImage(new CGRect(0, 0, _viewWidth, _viewHeight), image);
|
||||
}
|
||||
Marshal.FreeHGlobal(Address);
|
||||
Address = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public IntPtr Address { get; private set; }
|
||||
public PixelSize Size { get; }
|
||||
public int RowBytes { get; }
|
||||
public Vector Dpi { get; }
|
||||
public PixelFormat Format { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.OpenGL;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Shared.PlatformSupport;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
static class Platform
|
||||
{
|
||||
public static EaglFeature GlFeature;
|
||||
public static DisplayLinkTimer Timer;
|
||||
class PlatformSettings : IPlatformSettings
|
||||
{
|
||||
public Size DoubleClickSize { get; } = new Size(10, 10);
|
||||
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
|
||||
}
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
GlFeature ??= new EaglFeature();
|
||||
Timer ??= new DisplayLinkTimer();
|
||||
var keyboard = new KeyboardDevice();
|
||||
var softKeyboard = new SoftKeyboardHelper();
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<IWindowingPlatformGlFeature>().ToConstant(GlFeature)
|
||||
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryStub())
|
||||
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
|
||||
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
|
||||
.Bind<IPlatformSettings>().ToConstant(new PlatformSettings())
|
||||
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
|
||||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
|
||||
.Bind<IRenderLoop>().ToSingleton<RenderLoop>()
|
||||
.Bind<IRenderTimer>().ToConstant(Timer)
|
||||
.Bind<IPlatformThreadingInterface>().ToConstant(new PlatformThreadingInterface())
|
||||
.Bind<IKeyboardDevice>().ToConstant(keyboard);
|
||||
keyboard.PropertyChanged += (_, changed) =>
|
||||
{
|
||||
if (changed.PropertyName == nameof(KeyboardDevice.FocusedElement))
|
||||
softKeyboard.UpdateKeyboard(keyboard.FocusedElement);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
public class SingleViewLifetime
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Raw;
|
||||
using Avalonia.Platform;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS.Specific
|
||||
{
|
||||
/// <summary>
|
||||
/// In order to have properly handle of keyboard event in iOS View should already made some things in the View:
|
||||
/// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class
|
||||
/// 2. Implement all the methods required by UIKeyInput:
|
||||
/// 2.1 Implement HasText
|
||||
/// example:
|
||||
/// [Export("hasText")]
|
||||
/// bool HasText => _keyboardHelper.HasText()
|
||||
/// 2.2 Implement InsertText
|
||||
/// example:
|
||||
/// [Export("insertText:")]
|
||||
/// void InsertText(string text) => _keyboardHelper.InsertText(text);
|
||||
/// 2.3 Implement InsertText
|
||||
/// example:
|
||||
/// [Export("deleteBackward")]
|
||||
/// void DeleteBackward() => _keyboardHelper.DeleteBackward();
|
||||
/// 3.Let iOS know that this can become a first responder:
|
||||
/// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
|
||||
/// or
|
||||
/// public override bool CanBecomeFirstResponder { get { return true; } }
|
||||
///
|
||||
/// 4. To show keyboard:
|
||||
/// view.BecomeFirstResponder();
|
||||
/// 5. To hide keyboard
|
||||
/// view.ResignFirstResponder();
|
||||
/// </summary>
|
||||
/// <typeparam name="TView">View that needs keyboard events and show/hide keyboard</typeparam>
|
||||
internal class KeyboardEventsHelper<TView> where TView : UIView, ITopLevelImpl
|
||||
{
|
||||
private TView _view;
|
||||
private IInputElement _lastFocusedElement;
|
||||
|
||||
public KeyboardEventsHelper(TView view)
|
||||
{
|
||||
_view = view;
|
||||
|
||||
var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType<AdoptsAttribute>().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault();
|
||||
|
||||
if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!");
|
||||
|
||||
HandleEvents = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HandleEvents in order to suspend keyboard notifications or resume it
|
||||
/// </summary>
|
||||
public bool HandleEvents { get; set; }
|
||||
|
||||
public bool HasText() => false;
|
||||
|
||||
public bool CanBecomeFirstResponder() => true;
|
||||
|
||||
public void DeleteBackward()
|
||||
{
|
||||
HandleKey(Key.Back, RawKeyEventType.KeyDown);
|
||||
HandleKey(Key.Back, RawKeyEventType.KeyUp);
|
||||
}
|
||||
|
||||
public void InsertText(string text)
|
||||
{
|
||||
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text);
|
||||
_view.Input(rawTextEvent);
|
||||
}
|
||||
|
||||
private void HandleKey(Key key, RawKeyEventType type)
|
||||
{
|
||||
var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, RawInputModifiers.None);
|
||||
_view.Input(rawKeyEvent);
|
||||
}
|
||||
|
||||
//currently not found a way to get InputModifiers state
|
||||
//private static InputModifiers GetModifierKeys(object e)
|
||||
//{
|
||||
// var im = InputModifiers.None;
|
||||
// //if (IsCtrlPressed) rv |= InputModifiers.Control;
|
||||
// //if (IsShiftPressed) rv |= InputModifiers.Shift;
|
||||
|
||||
// return im;
|
||||
//}
|
||||
|
||||
private bool NeedsKeyboard(IInputElement element)
|
||||
{
|
||||
//may be some other elements
|
||||
return element is TextBox;
|
||||
}
|
||||
|
||||
private void TryShowHideKeyboard(IInputElement element, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_view.BecomeFirstResponder();
|
||||
}
|
||||
else
|
||||
{
|
||||
_view.ResignFirstResponder();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateKeyboardState(IInputElement element)
|
||||
{
|
||||
var focusedElement = element;
|
||||
bool oldValue = NeedsKeyboard(_lastFocusedElement);
|
||||
bool newValue = NeedsKeyboard(focusedElement);
|
||||
|
||||
if (newValue != oldValue || newValue)
|
||||
{
|
||||
TryShowHideKeyboard(focusedElement, newValue);
|
||||
}
|
||||
|
||||
_lastFocusedElement = element;
|
||||
}
|
||||
|
||||
public void ActivateAutoShowKeyboard()
|
||||
{
|
||||
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
|
||||
|
||||
//just in case we've called more than once the method
|
||||
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
|
||||
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
|
||||
}
|
||||
|
||||
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
|
||||
{
|
||||
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
HandleEvents = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Controls.Platform.Surfaces;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Raw;
|
||||
using Avalonia.iOS.Specific;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
[Adopts("UIKeyInput")]
|
||||
class TopLevelImpl : UIView, ITopLevelImpl, IFramebufferPlatformSurface
|
||||
{
|
||||
private readonly KeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
|
||||
|
||||
private IInputRoot _inputRoot;
|
||||
|
||||
public TopLevelImpl()
|
||||
{
|
||||
_keyboardHelper = new KeyboardEventsHelper<TopLevelImpl>(this);
|
||||
AutoresizingMask = UIViewAutoresizing.All;
|
||||
_keyboardHelper.ActivateAutoShowKeyboard();
|
||||
|
||||
Surfaces = new object[] { this };
|
||||
}
|
||||
|
||||
[Export("hasText")]
|
||||
public bool HasText => _keyboardHelper.HasText();
|
||||
|
||||
[Export("insertText:")]
|
||||
public void InsertText(string text) => _keyboardHelper.InsertText(text);
|
||||
|
||||
[Export("deleteBackward")]
|
||||
public void DeleteBackward() => _keyboardHelper.DeleteBackward();
|
||||
|
||||
public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
|
||||
|
||||
public Action Closed { get; set; }
|
||||
public Action<RawInputEventArgs> Input { get; set; }
|
||||
public Action<Rect> Paint { get; set; }
|
||||
public Action<Size> Resized { get; set; }
|
||||
public Action<double> ScalingChanged { get; set; }
|
||||
|
||||
public new IPlatformHandle Handle => null;
|
||||
|
||||
public double RenderScaling => UIScreen.MainScreen.Scale;
|
||||
|
||||
|
||||
public override void LayoutSubviews() => Resized?.Invoke(ClientSize);
|
||||
|
||||
public Size ClientSize => Bounds.Size.ToAvalonia();
|
||||
|
||||
public IMouseDevice MouseDevice => iOSPlatform.MouseDevice;
|
||||
|
||||
public IRenderer CreateRenderer(IRenderRoot root)
|
||||
{
|
||||
return new ImmediateRenderer(root);
|
||||
}
|
||||
|
||||
public override void Draw(CGRect rect)
|
||||
{
|
||||
Paint?.Invoke(new Rect(rect.X, rect.Y, rect.Width, rect.Height));
|
||||
}
|
||||
|
||||
public void Invalidate(Rect rect) => SetNeedsDisplay();
|
||||
|
||||
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
|
||||
|
||||
public Point PointToClient(PixelPoint point) => point.ToPoint(1);
|
||||
|
||||
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
|
||||
|
||||
public void SetCursor(IPlatformHandle cursor)
|
||||
{
|
||||
//Not supported
|
||||
}
|
||||
|
||||
public IEnumerable<object> Surfaces { get; }
|
||||
|
||||
public override void TouchesEnded(NSSet touches, UIEvent evt)
|
||||
{
|
||||
var touch = touches.AnyObject as UITouch;
|
||||
if (touch != null)
|
||||
{
|
||||
var location = touch.LocationInView(this).ToAvalonia();
|
||||
|
||||
Input?.Invoke(new RawPointerEventArgs(
|
||||
iOSPlatform.MouseDevice,
|
||||
(uint)touch.Timestamp,
|
||||
_inputRoot,
|
||||
RawPointerEventType.LeftButtonUp,
|
||||
location,
|
||||
RawInputModifiers.None));
|
||||
}
|
||||
}
|
||||
|
||||
Point _touchLastPoint;
|
||||
public override void TouchesBegan(NSSet touches, UIEvent evt)
|
||||
{
|
||||
var touch = touches.AnyObject as UITouch;
|
||||
if (touch != null)
|
||||
{
|
||||
var location = touch.LocationInView(this).ToAvalonia();
|
||||
_touchLastPoint = location;
|
||||
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
|
||||
RawPointerEventType.Move, location, RawInputModifiers.None));
|
||||
|
||||
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
|
||||
RawPointerEventType.LeftButtonDown, location, RawInputModifiers.None));
|
||||
}
|
||||
}
|
||||
|
||||
public override void TouchesMoved(NSSet touches, UIEvent evt)
|
||||
{
|
||||
var touch = touches.AnyObject as UITouch;
|
||||
if (touch != null)
|
||||
{
|
||||
var location = touch.LocationInView(this).ToAvalonia();
|
||||
if (iOSPlatform.MouseDevice.Captured != null)
|
||||
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
|
||||
RawPointerEventType.Move, location, RawInputModifiers.LeftMouseButton));
|
||||
else
|
||||
{
|
||||
//magic number based on test - correction of 0.02 is working perfect
|
||||
double correction = 0.02;
|
||||
|
||||
Input?.Invoke(new RawMouseWheelEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp,
|
||||
_inputRoot, location, (location - _touchLastPoint) * correction, RawInputModifiers.LeftMouseButton));
|
||||
}
|
||||
_touchLastPoint = location;
|
||||
}
|
||||
}
|
||||
|
||||
public ILockedFramebuffer Lock() => new EmulatedFramebuffer(this);
|
||||
|
||||
public IPopupImpl CreatePopup() => null;
|
||||
|
||||
public Action LostFocus { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Raw;
|
||||
using Avalonia.Platform;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
class TouchHandler
|
||||
{
|
||||
private readonly AvaloniaView _view;
|
||||
private readonly ITopLevelImpl _tl;
|
||||
public TouchDevice _device = new TouchDevice();
|
||||
|
||||
public TouchHandler(AvaloniaView view, ITopLevelImpl tl)
|
||||
{
|
||||
_view = view;
|
||||
_tl = tl;
|
||||
}
|
||||
|
||||
ulong Ts(UIEvent evt) => (ulong) (evt.Timestamp * 1000);
|
||||
private IInputRoot Root => _view.InputRoot;
|
||||
private static long _nextTouchPointId = 1;
|
||||
private Dictionary<UITouch, long> _knownTouches = new Dictionary<UITouch, long>();
|
||||
|
||||
public void Handle(NSSet touches, UIEvent evt)
|
||||
{
|
||||
foreach (UITouch t in touches)
|
||||
{
|
||||
var pt = t.LocationInView(_view).ToAvalonia();
|
||||
if (!_knownTouches.TryGetValue(t, out var id))
|
||||
_knownTouches[t] = id = _nextTouchPointId++;
|
||||
|
||||
var ev = new RawTouchEventArgs(_device, Ts(evt), Root,
|
||||
t.Phase switch
|
||||
{
|
||||
UITouchPhase.Began => RawPointerEventType.TouchBegin,
|
||||
UITouchPhase.Ended => RawPointerEventType.TouchEnd,
|
||||
UITouchPhase.Cancelled => RawPointerEventType.TouchCancel,
|
||||
_ => RawPointerEventType.TouchUpdate
|
||||
}, pt, RawInputModifiers.None, id);
|
||||
|
||||
_device.ProcessRawEvent(ev);
|
||||
|
||||
if (t.Phase == UITouchPhase.Cancelled || t.Phase == UITouchPhase.Ended)
|
||||
_knownTouches.Remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.iOS;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Shared.PlatformSupport;
|
||||
|
||||
namespace Avalonia
|
||||
{
|
||||
public static class iOSApplicationExtensions
|
||||
{
|
||||
public static T UseiOS<T>(this T builder) where T : AppBuilderBase<T>, new()
|
||||
{
|
||||
builder.UseWindowingSubsystem(iOSPlatform.Initialize, "iOS");
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Avalonia.iOS
|
||||
{
|
||||
public class iOSPlatform
|
||||
{
|
||||
internal static MouseDevice MouseDevice;
|
||||
internal static KeyboardDevice KeyboardDevice;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
MouseDevice = new MouseDevice();
|
||||
KeyboardDevice = new KeyboardDevice();
|
||||
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<IRuntimePlatform>().ToSingleton<StandardRuntimePlatform>()
|
||||
.Bind<IClipboard>().ToTransient<Clipboard>()
|
||||
// TODO: what does this look like for iOS??
|
||||
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
|
||||
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
|
||||
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
|
||||
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
|
||||
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
|
||||
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
|
||||
.Bind<IWindowingPlatform>().ToSingleton<WindowingPlatformImpl>()
|
||||
.Bind<IRenderTimer>().ToSingleton<DisplayLinkRenderTimer>()
|
||||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
|
||||
.Bind<IRenderLoop>().ToSingleton<RenderLoop>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Foundation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.iOS;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using UIKit;
|
||||
|
||||
namespace Avalonia.iOSTestApplication
|
||||
{
|
||||
// The UIApplicationDelegate for the application. This class is responsible for launching the
|
||||
// User Interface of the application, as well as listening (and optionally responding) to
|
||||
// application events from iOS.
|
||||
[Register("AppDelegate")]
|
||||
public partial class AppDelegate : UIApplicationDelegate
|
||||
{
|
||||
public override UIWindow Window { get; set; }
|
||||
|
||||
//
|
||||
// This method is invoked when the application has loaded and is ready to run. In this
|
||||
// method you should instantiate the window, load the UI into it and then make the window
|
||||
// visible.
|
||||
//
|
||||
// You have 17 seconds to return from this method, or iOS will terminate your application.
|
||||
//
|
||||
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
|
||||
{
|
||||
AppBuilder.Configure<SimpleApp>().UseSkia().UseiOS().SetupWithoutStarting();
|
||||
Window = new AvaloniaWindow { Content = new SimpleControl()};
|
||||
Window.MakeKeyAndVisible();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
|
||||
<ProjectGuid>{8C923867-8A8F-4F6B-8B80-47D9E8436166}</ProjectGuid>
|
||||
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Avalonia.iOSTestApplication</RootNamespace>
|
||||
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
|
||||
<AssemblyName>AvaloniaiOSTestApplication</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<MtouchArch>i386</MtouchArch>
|
||||
<MtouchLink>SdkOnly</MtouchLink>
|
||||
<MtouchDebug>true</MtouchDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchArch>i386</MtouchArch>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhone\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<MtouchDebug>True</MtouchDebug>
|
||||
<MtouchSdkVersion>9.1</MtouchSdkVersion>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchProfiling>False</MtouchProfiling>
|
||||
<MtouchFastDev>False</MtouchFastDev>
|
||||
<MtouchUseLlvm>False</MtouchUseLlvm>
|
||||
<MtouchUseThumb>False</MtouchUseThumb>
|
||||
<MtouchUseSGen>True</MtouchUseSGen>
|
||||
<MtouchUseRefCounting>True</MtouchUseRefCounting>
|
||||
<MtouchOptimizePNGs>True</MtouchOptimizePNGs>
|
||||
<DeviceSpecificBuild>False</DeviceSpecificBuild>
|
||||
<MtouchI18n />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhone\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Ad-Hoc|iPhone' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
<OutputPath>bin\iPhone\Ad-Hoc</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>False</ConsolePause>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<BuildIpa>True</BuildIpa>
|
||||
<CodesignProvision>Automatic:AdHoc</CodesignProvision>
|
||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'AppStore|iPhone' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
<OutputPath>bin\iPhone\AppStore</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>False</ConsolePause>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<CodesignProvision>Automatic:AppStore</CodesignProvision>
|
||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DisableSourceLink>true</DisableSourceLink>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="AppDelegate.cs" />
|
||||
<Compile Include="SimpleApp.xaml.cs" />
|
||||
<Compile Include="SimpleControl.cs" />
|
||||
<None Include="GettingStarted.Xamarin" />
|
||||
<None Include="Info.plist" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BundleResource Include="Resources\Default-568h%402x.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Xamarin.iOS" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Entitlements.plist" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
|
||||
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
|
||||
<Name>Avalonia.Markup.Xaml</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
|
||||
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
|
||||
<Name>Avalonia.Markup</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
|
||||
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
|
||||
<Name>Avalonia.Animation</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
|
||||
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
|
||||
<Name>Avalonia.Base</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
|
||||
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
|
||||
<Name>Avalonia.Controls</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
|
||||
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
|
||||
<Name>Avalonia.Diagnostics</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
|
||||
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
|
||||
<Name>Avalonia.Input</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
|
||||
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
|
||||
<Name>Avalonia.Interactivity</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
|
||||
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
|
||||
<Name>Avalonia.Layout</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
|
||||
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
|
||||
<Name>Avalonia.Visuals</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
|
||||
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
|
||||
<Name>Avalonia.Styling</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
|
||||
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
|
||||
<Name>Avalonia.Themes.Default</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
|
||||
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
|
||||
<Name>Avalonia.Skia</Name>
|
||||
<IsAppExtension>false</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Avalonia.iOS\Avalonia.iOS.csproj">
|
||||
<Project>{4488ad85-1495-4809-9aa4-ddfe0a48527e}</Project>
|
||||
<Name>Avalonia.iOS</Name>
|
||||
<IsAppExtension>false</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="SimpleApp.xaml">
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<Import Project="..\..\..\build\Rx.props" />
|
||||
<Import Project="..\..\..\build\iOSWorkarounds.props" />
|
||||
<Import Project="..\..\..\build\LegacyProject.targets" />
|
||||
</Project>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -1,4 +0,0 @@
|
||||
<GettingStarted>
|
||||
<LocalContent>GS\iOS\CS\iOSApp\GettingStarted.html</LocalContent>
|
||||
<EmbeddedNavigation>false</EmbeddedNavigation>
|
||||
</GettingStarted>
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Avalonia.iOSTestApplication</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.your-company.Avalonia.iOSTestApplication</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>Default-568h@2x.png</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,6 +0,0 @@
|
||||
<Application xmlns="https://github.com/avaloniaui">
|
||||
<Application.Styles>
|
||||
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
|
||||
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,35 @@ namespace Avalonia.Controls.UnitTests
|
||||
Assert.True(ToolTip.GetIsOpen(target));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Content_Should_Update_When_Tip_Property_Changes_And_Already_Open()
|
||||
{
|
||||
using (UnitTestApplication.Start(TestServices.StyledWindow))
|
||||
{
|
||||
var window = new Window();
|
||||
|
||||
var target = new Decorator()
|
||||
{
|
||||
[ToolTip.TipProperty] = "Tip",
|
||||
[ToolTip.ShowDelayProperty] = 0
|
||||
};
|
||||
|
||||
window.Content = target;
|
||||
|
||||
window.ApplyTemplate();
|
||||
window.Presenter.ApplyTemplate();
|
||||
|
||||
_mouseHelper.Enter(target);
|
||||
|
||||
Assert.True(ToolTip.GetIsOpen(target));
|
||||
Assert.Equal("Tip", target.GetValue(ToolTip.ToolTipProperty).Content);
|
||||
|
||||
|
||||
ToolTip.SetTip(target, "Tip1");
|
||||
Assert.Equal("Tip1", target.GetValue(ToolTip.ToolTipProperty).Content);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Open_On_Pointer_Enter_With_Delay()
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls.Utils;
|
||||
using Xunit;
|
||||
using CollectionChangedEventManager = Avalonia.Controls.Utils.CollectionChangedEventManager;
|
||||
|
||||
namespace Avalonia.Controls.UnitTests.Utils
|
||||
{
|
||||
public class CollectionChangedEventManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddListener_Listens_To_Events()
|
||||
{
|
||||
var source = new AvaloniaList<string>();
|
||||
var listener = new Listener();
|
||||
|
||||
CollectionChangedEventManager.Instance.AddListener(source, listener);
|
||||
|
||||
Assert.Empty(listener.Received);
|
||||
|
||||
source.Add("foo");
|
||||
|
||||
Assert.Equal(1, listener.Received.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveListener_Stops_Listening_To_Events()
|
||||
{
|
||||
var source = new AvaloniaList<string>();
|
||||
var listener = new Listener();
|
||||
|
||||
CollectionChangedEventManager.Instance.AddListener(source, listener);
|
||||
CollectionChangedEventManager.Instance.RemoveListener(source, listener);
|
||||
|
||||
source.Add("foo");
|
||||
|
||||
Assert.Empty(listener.Received);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Receives_Events_From_Wrapped_Collection()
|
||||
{
|
||||
var source = new WrappingCollection();
|
||||
var listener = new Listener();
|
||||
|
||||
CollectionChangedEventManager.Instance.AddListener(source, listener);
|
||||
|
||||
Assert.Empty(listener.Received);
|
||||
|
||||
source.Add("foo");
|
||||
|
||||
Assert.Equal(1, listener.Received.Count);
|
||||
}
|
||||
|
||||
private class Listener : ICollectionChangedListener
|
||||
{
|
||||
public List<NotifyCollectionChangedEventArgs> Received { get; } = new List<NotifyCollectionChangedEventArgs>();
|
||||
|
||||
public void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Received.Add(e);
|
||||
}
|
||||
|
||||
public void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
public void PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class WrappingCollection : INotifyCollectionChanged
|
||||
{
|
||||
private AvaloniaList<string> _inner = new AvaloniaList<string>();
|
||||
|
||||
public void Add(string s) => _inner.Add(s);
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||
{
|
||||
add => _inner.CollectionChanged += value;
|
||||
remove => _inner.CollectionChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user