diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a0da4aa9b..78b9cff039 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,16 +1,31 @@ -This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible: +## What does the pull request do? -- What does the pull request do? -- What is the current behavior? -- What is the updated/expected behavior with this PR? -- How was the solution implemented (if it's not obvious)? +Give a bit of background on the PR here, together with links to with related issues etc. -Checklist: +## What is the current behavior? + +If the PR is a fix, describe the current incorrect behavior, otherwise delete this section. + +## What is the updated/expected behavior with this PR? + +Describe how to test the PR. + +## How was the solution implemented (if it's not obvious)? + +Include any information that might be of use to a reviewer here. + +## Checklist - [ ] Added unit tests (if possible)? - [ ] Added XML documentation to any related classes? - [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation +## Breaking changes + +List any breaking changes here. When the PR is merged please add an entry to https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes + +## Fixed issues + If the pull request fixes issue(s) list them like this: Fixes #123 diff --git a/.gitignore b/.gitignore index 6ee6057d5f..2b2c9c3d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,4 @@ Logs/ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist +build-intermediate diff --git a/.gitmodules b/.gitmodules index 2d2a9ac497..22c56307b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github url = https://github.com/AvaloniaUI/Portable.Xaml.git +[submodule "nukebuild/Numerge"] + path = nukebuild/Numerge + url = https://github.com/kekekeks/Numerge.git diff --git a/.ncrunch/Avalonia.Desktop.v3.ncrunchproject b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.net461.v3.ncrunchproject b/.ncrunch/Avalonia.net461.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.nuke b/.nuke new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b0c0c807cb..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: csharp -os: - - linux -dist: trusty -osx_image: xcode8.3 -env: - global: - - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - - DOTNET_CLI_TELEMETRY_OPTOUT=1 -mono: - - 5.2.0 -dotnet: 2.1.200 -script: - - sudo apt-get update - - sudo apt-get install castxml - - ./build.sh --target "Travis" --configuration "Release" -notifications: - email: false - webhooks: - urls: - - https://webhooks.gitter.im/e/98f653320ef2b7506c05 - on_success: change - on_failure: always - on_start: never diff --git a/Avalonia.sln b/Avalonia.sln index df60ff4a75..2f7560049c 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 @@ -130,6 +131,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 ProjectSection(SolutionItems) = preProject build\Base.props = build\Base.props build\Binding.props = build\Binding.props + build\BuildTargets.targets = build\BuildTargets.targets build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props @@ -147,7 +149,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Splat.props = build\Splat.props build\System.Memory.props = build\System.Memory.props build\XUnit.props = build\XUnit.props - build\BuildTargets.targets = build\BuildTargets.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" @@ -189,7 +190,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -1714,6 +1723,102 @@ Global {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.Build.0 = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.ActiveCfg = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.Build.0 = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhone.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhone.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.Build.0 = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhone.ActiveCfg = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhone.Build.0 = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhone.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhone.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.Build.0 = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.ActiveCfg = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.Build.0 = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1767,6 +1872,9 @@ Global {E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C} {3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C} + {AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} + {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution index 1b5b0c8930..a2208a9a91 100644 --- a/Avalonia.v3.ncrunchsolution +++ b/Avalonia.v3.ncrunchsolution @@ -2,6 +2,7 @@ tests\TestFiles\**.* + src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll True .ncrunch diff --git a/Directory.Build.props b/Directory.Build.props index 50476c81f1..1f26df9bbc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - $(MSBuildThisFileDirectory)artifacts/nuget + $(MSBuildThisFileDirectory)build-intermediate/nuget diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 484fb4586f..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -os: Visual Studio 2017 -skip_branch_with_pr: true -configuration: -- Release -environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - NUGET_API_URL: https://www.nuget.org/api/v2/package - MYGET_API_KEY: - secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK - MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package -init: -- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")} -before_build: -- git submodule update --init -build_script: -- ps: .\build.ps1 -Target "AppVeyor" -Configuration "$env:configuration" - -test: off -artifacts: - - path: artifacts\nuget\*.nupkg - - path: artifacts\zip\*.zip - - path: artifacts\inspectcode.xml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6929f8dee..39333f37ba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,19 +11,18 @@ jobs: sudo apt-get install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 - + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-Linux" -configuration="Release" + nuke --target CiAzureLinux --configuration=Release - task: PublishTestResults@2 inputs: @@ -39,6 +38,13 @@ jobs: inputs: version: '2.1.403' + - task: CmdLine@2 + displayName: 'Install Mono 5.18' + inputs: + script: | + curl -o ./mono.pkg https://download.mono-project.com/archive/5.18.0/macos-10-universal/MonoFramework-MDK-5.18.0.225.macos10.xamarin.universal.pkg + sudo installer -verbose -pkg ./mono.pkg -target / + - task: Xcode@5 inputs: actions: 'build' @@ -52,16 +58,18 @@ jobs: - task: CmdLine@2 displayName: 'Install CastXML' inputs: - script: brew install castxml + script: | + brew update + brew install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export COREHOST_TRACE=0 @@ -72,7 +80,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-OSX" -configuration="Release" + nuke --target CiAzureOSX --configuration Release - task: PublishTestResults@2 inputs: @@ -84,30 +92,30 @@ jobs: inputs: pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/' artifactName: 'Avalonia.Native.OSX' - condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) + condition: succeeded() - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget' artifactName: 'NuGetOSX' - condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) + condition: succeeded() - job: Windows pool: vmImage: 'vs2017-win2016' steps: - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | set PATH=%PATH%;%USERPROFILE%\.dotnet\tools - dotnet cake build.cake -target="Azure-Windows" -configuration="Release" + nuke --target CiAzureWindows --configuration Release - task: PublishTestResults@2 inputs: @@ -119,10 +127,10 @@ jobs: inputs: pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget' artifactName: 'NuGet' - condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) + condition: succeeded() - task: PublishBuildArtifacts@1 inputs: pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip' artifactName: 'Samples' - condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) + condition: succeeded() diff --git a/build.cake b/build.cake deleted file mode 100644 index f10a12c4e6..0000000000 --- a/build.cake +++ /dev/null @@ -1,312 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// TOOLS -/////////////////////////////////////////////////////////////////////////////// - -#tool "nuget:?package=NuGet.CommandLine&version=4.7.1" -#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3" -#tool "nuget:?package=xunit.runner.console&version=2.3.1" -#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559" - -/////////////////////////////////////////////////////////////////////////////// -// USINGS -/////////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -/////////////////////////////////////////////////////////////////////////////// -// SCRIPTS -/////////////////////////////////////////////////////////////////////////////// - -#load "./parameters.cake" - -/////////////////////////////////////////////////////////////////////////////// -// SETUP -/////////////////////////////////////////////////////////////////////////////// - -Setup(context => -{ - var parameters = new Parameters(context); - - Information("Building version {0} of Avalonia ({1}) using version {2} of Cake.", - parameters.Version, - parameters.Configuration, - typeof(ICakeContext).Assembly.GetName().Version.ToString()); - - if (parameters.IsRunningOnAppVeyor) - { - Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name); - Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch); - } - Information("Target:" + context.TargetTask.Name); - Information("Configuration: " + parameters.Configuration); - Information("IsLocalBuild: " + parameters.IsLocalBuild); - Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix); - Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows); - Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor); - Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure); - Information("IsPullRequest: " + parameters.IsPullRequest); - Information("IsMainRepo: " + parameters.IsMainRepo); - Information("IsMasterBranch: " + parameters.IsMasterBranch); - Information("IsReleaseBranch: " + parameters.IsReleaseBranch); - Information("IsTagged: " + parameters.IsTagged); - Information("IsReleasable: " + parameters.IsReleasable); - Information("IsMyGetRelease: " + parameters.IsMyGetRelease); - Information("IsNuGetRelease: " + parameters.IsNuGetRelease); - - return parameters; -}); - -/////////////////////////////////////////////////////////////////////////////// -// TEARDOWN -/////////////////////////////////////////////////////////////////////////////// - -Teardown((context, buildContext) => -{ - Information("Finished running tasks."); -}); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("Clean-Impl") - .Does(data => -{ - CleanDirectories(data.BuildDirs); - CleanDirectory(data.ArtifactsDir); - CleanDirectory(data.NugetRoot); - CleanDirectory(data.ZipRoot); - CleanDirectory(data.TestResultsRoot); -}); - -void DotNetCoreBuild(Parameters parameters) -{ - var settings = new DotNetCoreBuildSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCoreBuild(parameters.MSBuildSolution, settings); -} - -Task("Build-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - }); - } - else - { - DotNetCoreBuild(data); - } -}); - -void RunCoreTest(string project, Parameters parameters, bool coreOnly = false) -{ - if(!project.EndsWith(".csproj")) - project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); - Information("Running tests from " + project); - var frameworks = new List(){"netcoreapp2.0"}; - foreach(var fw in frameworks) - { - if(!fw.StartsWith("netcoreapp") && coreOnly) - continue; - Information("Running for " + fw); - - var settings = new DotNetCoreTestSettings { - Configuration = parameters.Configuration, - Framework = fw, - NoBuild = true, - NoRestore = true - }; - - if (parameters.PublishTestResults) - { - settings.Logger = "trx"; - settings.ResultsDirectory = parameters.TestResultsRoot; - } - - DotNetCoreTest(project, settings); - } -} - -Task("Run-Unit-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Base.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Skia.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data, false); - if (data.IsRunningOnWindows) - { - RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data, false); - } -}); - -Task("Run-Designer-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data, false); -}); - -Task("Run-Render-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data, true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data, true); -}); - -Task("Run-Leak-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(() => -{ - var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe"); - var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings - { - Arguments = new ProcessArgumentBuilder() - .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath) - .Append("--propagate-exit-code") - .Append("--") - .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"), - Timeout = 120000 - }); - - if (leakTestsExitCode != 0) - { - throw new Exception("Leak Tests failed"); - } -}); - -Task("Zip-Files-Impl") - .Does(data => -{ - Zip(data.BinRoot, data.ZipCoreArtifacts); - - Zip(data.NugetRoot, data.ZipNuGetArtifacts); - - Zip(data.ZipSourceControlCatalogDesktopDirs, - data.ZipTargetControlCatalogDesktopDirs, - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); -}); - -void DotNetCorePack(Parameters parameters) -{ - var settings = new DotNetCorePackSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCorePack(parameters.MSBuildSolution, settings); -} - -Task("Create-NuGet-Packages-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - settings.WithTarget("Pack"); - }); - } - else - { - DotNetCorePack(data); - } -}); - -/////////////////////////////////////////////////////////////////////////////// -// TARGETS -/////////////////////////////////////////////////////////////////////////////// - -Task("Build") - .IsDependentOn("Clean-Impl") - .IsDependentOn("Build-Impl"); - -Task("Run-Tests") - .IsDependentOn("Build") - .IsDependentOn("Run-Unit-Tests-Impl") - .IsDependentOn("Run-Render-Tests-Impl") - .IsDependentOn("Run-Designer-Tests-Impl") - .IsDependentOn("Run-Leak-Tests-Impl"); - -Task("Package") - .IsDependentOn("Run-Tests") - .IsDependentOn("Create-NuGet-Packages-Impl"); - -Task("AppVeyor") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Travis") - .IsDependentOn("Run-Tests"); - -Task("Azure-Linux") - .IsDependentOn("Run-Tests"); - -Task("Azure-OSX") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Azure-Windows") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -/////////////////////////////////////////////////////////////////////////////// -// EXECUTE -/////////////////////////////////////////////////////////////////////////////// - -var target = Context.Argument("target", "Default"); - -if (target == "Default") -{ - target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests"; -} - -RunTarget(target); diff --git a/build.ps1 b/build.ps1 index 46696db2b2..57e2f80075 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,201 +1,69 @@ -########################################################################## -# This is the Cake bootstrapper script for PowerShell. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## - -<# - -.SYNOPSIS -This is a Powershell script to bootstrap a Cake build. - -.DESCRIPTION -This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) -and execute your Cake build script with the parameters you provide. - -.PARAMETER Script -The build script to execute. -.PARAMETER Target -The build script target to run. -.PARAMETER Platform -The build platform to use. -.PARAMETER Configuration -The build configuration to use. -.PARAMETER Verbosity -Specifies the amount of information to be displayed. -.PARAMETER Experimental -Tells Cake to use the latest Roslyn release. -.PARAMETER WhatIf -Performs a dry run of the build script. -No tasks will be executed. -.PARAMETER Mono -Tells Cake to use the Mono scripting engine. -.PARAMETER SkipToolPackageRestore -Skips restoring of packages. -.PARAMETER SkipTests -Skips unit tests -.PARAMETER ScriptArgs -Remaining arguments are added here. - -.LINK -http://cakebuild.net - -#> - [CmdletBinding()] Param( - [string]$Script = "build.cake", - [string]$Target = "Default", - [ValidateSet("Any CPU", "x86", "x64", "NetCoreOnly", "iPhone")] - [string]$Platform = "Any CPU", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity = "Verbose", - [switch]$Experimental, - [Alias("DryRun","Noop")] - [switch]$WhatIf, - [switch]$Mono, - [switch]$SkipToolPackageRestore, + #[switch]$CustomParam, [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] - [string[]]$ScriptArgs + [string[]]$BuildArguments ) -[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null -function MD5HashFile([string] $filePath) -{ - if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) - { - return $null - } - - [System.IO.Stream] $file = $null; - [System.Security.Cryptography.MD5] $md5 = $null; - try - { - $md5 = [System.Security.Cryptography.MD5]::Create() - $file = [System.IO.File]::OpenRead($filePath) - return [System.BitConverter]::ToString($md5.ComputeHash($file)) - } - finally - { - if ($file -ne $null) - { - $file.Dispose() - } - } -} - -Write-Host "Preparing to run build script..." - -if(!$PSScriptRoot){ - $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} +Write-Output "Windows PowerShell $($Host.Version)" -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" -$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" -$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 } +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -# Should we use mono? -$UseMono = ""; -if($Mono.IsPresent) { - Write-Verbose -Message "Using the Mono based scripting engine." - $UseMono = "-mono" -} +########################################################################### +# CONFIGURATION +########################################################################### -# Should we use the new Roslyn? -$UseExperimental = ""; -if($Experimental.IsPresent -and !($Mono.IsPresent)) { - Write-Verbose -Message "Using experimental version of Roslyn." - $UseExperimental = "-experimental" -} +$BuildProjectFile = "$PSScriptRoot\nukebuild\_build.csproj" +$TempDirectory = "$PSScriptRoot\\.tmp" -# Is this a dry run? -$UseDryRun = ""; -if($WhatIf.IsPresent) { - $UseDryRun = "-dryrun" -} +$DotNetGlobalFile = "$PSScriptRoot\\global.json" +$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1" +$DotNetChannel = "Current" -# Is this a dry run? -$UseSkipTests = ""; -if($SkipTests.IsPresent) { - $UseSkipTests = "-skip-tests" -} +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:NUGET_XMLDOC_MODE = "skip" -# Make sure tools folder exists -if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { - Write-Verbose -Message "Creating tools directory..." - New-Item -Path $TOOLS_DIR -Type directory | out-null -} +########################################################################### +# EXECUTION +########################################################################### -# Make sure that packages.config exist. -if (!(Test-Path $PACKAGES_CONFIG)) { - Write-Verbose -Message "Downloading packages.config..." - try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { - Throw "Could not download packages.config." - } +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } } -# Try find NuGet.exe in path if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Trying to find nuget.exe in PATH..." - $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } - $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 - if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { - Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." - $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName +# If global.json exists, load expected version +if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version } } -# Try download NuGet.exe if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Downloading NuGet.exe..." - try { - (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) - } catch { - Throw "Could not download NuGet.exe." - } +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and ` + (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path } - -# Save nuget.exe path to environment to be available to child processed -$ENV:NUGET_EXE = $NUGET_EXE - -# Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) { - Push-Location - Set-Location $TOOLS_DIR - - # Check for changes in packages.config and remove installed tools if true. - [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) - if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or - ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { - Write-Verbose -Message "Missing or changed package.config hash..." - Remove-Item * -Recurse -Exclude packages.config,nuget.exe +else { + $DotNetDirectory = "$TempDirectory\dotnet-win" + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" + + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + md -force $TempDirectory > $null + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # Install by channel or version + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } } - - Write-Verbose -Message "Restoring tools from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet tools." - } - else - { - $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" - } - Write-Verbose -Message ($NuGetOutput | out-string) - Pop-Location } -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe at $CAKE_EXE" -} +Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" -# Start Cake -Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -platform=`"$Platform`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseSkipTests $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE \ No newline at end of file +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments } diff --git a/build.sh b/build.sh index 206a55d171..40b1c225a6 100755 --- a/build.sh +++ b/build.sh @@ -1,105 +1,72 @@ #!/usr/bin/env bash -########################################################################## -# This is the Cake bootstrapper script for Linux and OS X. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## +echo $(bash --version 2>&1 | head -n 1) -# Define directories. -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -TOOLS_DIR=$SCRIPT_DIR/tools -NUGET_EXE=$TOOLS_DIR/nuget.exe -CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe -PACKAGES_CONFIG=$TOOLS_DIR/packages.config -PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum - -# Define md5sum or md5 depending on Linux/OSX -MD5_EXE= -if [[ "$(uname -s)" == "Darwin" ]]; then - MD5_EXE="md5 -r" -else - MD5_EXE="md5sum" -fi - -# Define default arguments. -SCRIPT="build.cake" -TARGET="Default" -CONFIGURATION="Release" -PLATFORM="Any CPU" -VERBOSITY="verbose" -DRYRUN= -SKIP_TESTS= -SHOW_VERSION=false -SCRIPT_ARGUMENTS=() - -# Parse arguments. +#CUSTOMPARAM=0 +BUILD_ARGUMENTS=() for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - -t|--target) TARGET="$2"; shift ;; - -p|--platform) PLATFORM="$2"; shift ;; - -c|--configuration) CONFIGURATION="$2"; shift ;; - --skip-tests) SKIP_TESTS="-skip-tests"; shift ;; - -v|--verbosity) VERBOSITY="$2"; shift ;; - -d|--dryrun) DRYRUN="-dryrun" ;; - --version) SHOW_VERSION=true ;; - --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; - *) SCRIPT_ARGUMENTS+=("$1") ;; + case $(echo $1 | awk '{print tolower($0)}') in + # -custom-param) CUSTOMPARAM=1;; + *) BUILD_ARGUMENTS+=("$1") ;; esac shift done -# Make sure the tools folder exist. -if [ ! -d "$TOOLS_DIR" ]; then - mkdir "$TOOLS_DIR" -fi +set -eo pipefail +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) -# Make sure that packages.config exist. -if [ ! -f "$TOOLS_DIR/packages.config" ]; then - echo "Downloading packages.config..." - curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages - if [ $? -ne 0 ]; then - echo "An error occured while downloading packages.config." - exit 1 - fi -fi +########################################################################### +# CONFIGURATION +########################################################################### -# Download NuGet if it does not exist. -if [ ! -f "$NUGET_EXE" ]; then - echo "Downloading NuGet..." - curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - if [ $? -ne 0 ]; then - echo "An error occured while downloading nuget.exe." - exit 1 - fi -fi +BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj" +TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" -# Restore tools from NuGet. -pushd "$TOOLS_DIR" >/dev/null -if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then - find . -type d ! -name . | xargs rm -rf -fi +DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" +DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh" +DOTNET_CHANNEL="Current" -mono "$NUGET_EXE" install -ExcludeVersion -if [ $? -ne 0 ]; then - echo "Could not restore NuGet packages." - exit 1 -fi +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export NUGET_XMLDOC_MODE="skip" -$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 +########################################################################### +# EXECUTION +########################################################################### -popd >/dev/null +function FirstJsonValue { + perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2} +} -# Make sure that Cake has been installed. -if [ ! -f "$CAKE_EXE" ]; then - echo "Could not find Cake.exe at '$CAKE_EXE'." - exit 1 +# If global.json exists, load expected version +if [ -f "$DOTNET_GLOBAL_FILE" ]; then + DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE")) + if [ "$DOTNET_VERSION" == "" ]; then + unset DOTNET_VERSION + fi fi -# Start Cake -if $SHOW_VERSION; then - exec mono "$CAKE_EXE" -version +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then + export DOTNET_EXE="$(command -v dotnet)" else - exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -platform="$PLATFORM" -configuration="$CONFIGURATION" -target=$TARGET $DRYRUN $SKIP_TESTS "${SCRIPT_ARGUMENTS[@]}" + DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" + export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" + + # Download install script + DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" + mkdir -p "$TEMP_DIRECTORY" + curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" + chmod +x "$DOTNET_INSTALL_FILE" + + # Install by channel or version + if [ -z ${DOTNET_VERSION+x} ]; then + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path + else + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path + fi fi + +echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" + +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} diff --git a/build/Magick.NET-Q16-AnyCPU.props b/build/Magick.NET-Q16-AnyCPU.props index 4e600a1c11..21d9cdcb1f 100644 --- a/build/Magick.NET-Q16-AnyCPU.props +++ b/build/Magick.NET-Q16-AnyCPU.props @@ -1,5 +1,5 @@ - + diff --git a/build/NetFX.props b/build/NetFX.props index d8b2daf13a..4d2841714b 100644 --- a/build/NetFX.props +++ b/build/NetFX.props @@ -3,4 +3,9 @@ /usr/lib/mono/4.6.1-api /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api + + /usr/lib/mono/4.7-api/ + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api + + diff --git a/build/ReferenceCoreLibraries.props b/build/ReferenceCoreLibraries.props index bd9d6ad843..cffc2e0324 100644 --- a/build/ReferenceCoreLibraries.props +++ b/build/ReferenceCoreLibraries.props @@ -1,5 +1,4 @@ - diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 35c979a95e..a43c99e978 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/build/UnitTests.NetFX.props b/build/UnitTests.NetFX.props new file mode 100644 index 0000000000..e9a29d80fe --- /dev/null +++ b/build/UnitTests.NetFX.props @@ -0,0 +1,8 @@ + + + + + PreserveNewest + + + diff --git a/build/xunit.runner.mono.json b/build/xunit.runner.mono.json new file mode 100644 index 0000000000..4fb88b54c6 --- /dev/null +++ b/build/xunit.runner.mono.json @@ -0,0 +1 @@ +{ "appDomain": "denied" } diff --git a/cake.config b/cake.config deleted file mode 100644 index 8089cd4084..0000000000 --- a/cake.config +++ /dev/null @@ -1,15 +0,0 @@ -; This is the default configuration file for Cake. -; This file was downloaded from https://github.com/cake-build/resources - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=false - -[Paths] -Tools=./tools -Addins=./tools/Addins -Modules=./tools/Modules - -[Settings] -SkipVerification=false diff --git a/global.json b/global.json index d11e78bd7f..6d12c28846 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", - "MSBuild.Sdk.Extras": "1.6.46", + "MSBuild.Sdk.Extras": "1.6.65", "AggregatePackage.NuGet.Sdk" : "0.1.12" } } diff --git a/native/Avalonia.Native/src/OSX/gl.mm b/native/Avalonia.Native/src/OSX/gl.mm index a62098a074..083adc927d 100644 --- a/native/Avalonia.Native/src/OSX/gl.mm +++ b/native/Avalonia.Native/src/OSX/gl.mm @@ -208,9 +208,8 @@ public: virtual ~AvnGlRenderingSession() { - glFlush(); [_context flushBuffer]; - [_context setView:nil]; + [NSOpenGLContext clearCurrentContext]; CGLUnlockContext([_context CGLContextObj]); [_view unlockFocus]; } @@ -241,9 +240,8 @@ public: auto gl = _context; CGLLockContext([_context CGLContextObj]); [gl setView: _view]; + [gl update]; [gl makeCurrentContext]; - auto frame = [_view frame]; - *ret = new AvnGlRenderingSession(_window, _view, gl); return S_OK; } diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index ae00bde780..ef5dcb5b2e 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -425,7 +425,7 @@ private: [[Window parentWindow] removeChildWindow:Window]; WindowBaseImpl::Show(); - return SetWindowState(_lastWindowState); + return SetWindowState(Normal); } } @@ -1184,6 +1184,25 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } +- (void)windowDidMiniaturize:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->WindowStateChanged(); + } +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification +{ + auto parent = dynamic_cast(_parent.operator->()); + + if(parent != nullptr) + { + parent->WindowStateChanged(); + } +} - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame { diff --git a/nukebuild/.editorconfig b/nukebuild/.editorconfig new file mode 100644 index 0000000000..d6009b3c0f --- /dev/null +++ b/nukebuild/.editorconfig @@ -0,0 +1,8 @@ +# editorconfig.org + +# top-most EditorConfig file +root = false + +# C# files +[*.cs] +dotnet_style_require_accessibility_modifiers = never diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs new file mode 100644 index 0000000000..bb31034299 --- /dev/null +++ b/nukebuild/Build.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Xml.Linq; +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.MSBuild; +using Nuke.Common.Utilities; +using static Nuke.Common.EnvironmentInfo; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Nuke.Common.Tools.Xunit.XunitTasks; + + +/* + Before editing this file, install support plugin for your IDE, + running and debugging a particular target (optionally without deps) would be way easier + ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support + VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support + + */ + +partial class Build : NukeBuild +{ + BuildParameters Parameters { get; set; } + protected override void OnBuildInitialized() + { + Parameters = new BuildParameters(this); + Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.", + Parameters.Version, + Parameters.Configuration, + typeof(NukeBuild).Assembly.GetName().Version.ToString()); + + if (Parameters.IsLocalBuild) + { + Information("Repository Name: " + Parameters.RepositoryName); + Information("Repository Branch: " + Parameters.RepositoryBranch); + } + Information("Configuration: " + Parameters.Configuration); + Information("IsLocalBuild: " + Parameters.IsLocalBuild); + Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix); + Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows); + Information("IsRunningOnAzure:" + Parameters.IsRunningOnAzure); + Information("IsPullRequest: " + Parameters.IsPullRequest); + Information("IsMainRepo: " + Parameters.IsMainRepo); + Information("IsMasterBranch: " + Parameters.IsMasterBranch); + Information("IsReleaseBranch: " + Parameters.IsReleaseBranch); + Information("IsReleasable: " + Parameters.IsReleasable); + Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); + Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); + + void ExecWait(string preamble, string command, string args) + { + Console.WriteLine(preamble); + Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit(); + } + ExecWait("dotnet version:", "dotnet", "--version"); + if (Parameters.IsRunningOnUnix) + ExecWait("Mono version:", "mono", "--version"); + + + } + + Target Clean => _ => _.Executes(() => + { + DeleteDirectories(Parameters.BuildDirs); + EnsureCleanDirectories(Parameters.BuildDirs); + EnsureCleanDirectory(Parameters.ArtifactsDir); + EnsureCleanDirectory(Parameters.NugetIntermediateRoot); + EnsureCleanDirectory(Parameters.NugetRoot); + EnsureCleanDirectory(Parameters.ZipRoot); + EnsureCleanDirectory(Parameters.TestResultsRoot); + }); + + Target Compile => _ => _ + .DependsOn(Clean) + .Executes(() => + { + + if (Parameters.IsRunningOnWindows) + MSBuild(Parameters.MSBuildSolution, c => c + .SetArgumentConfigurator(a => a.Add("/r")) + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Build") + ); + + else + DotNetBuild(Parameters.MSBuildSolution, c => c + .AddProperty("PackageVersion", Parameters.Version) + .SetConfiguration(Parameters.Configuration) + ); + }); + + void RunCoreTest(string project) + { + if(!project.EndsWith(".csproj")) + project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); + Information("Running tests from " + project); + XDocument xdoc; + using (var s = File.OpenRead(project)) + xdoc = XDocument.Load(s); + + List frameworks = null; + var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault(); + if (targets != null) + frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList(); + else + frameworks = new List {xdoc.Root.Descendants("TargetFramework").First().Value}; + + foreach(var fw in frameworks) + { + Information("Running for " + fw); + DotNetTest(c => + { + c = c + .SetProjectFile(project) + .SetConfiguration(Parameters.Configuration) + .SetFramework(fw) + .EnableNoBuild() + .EnableNoRestore(); + // NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient + if (Parameters.PublishTestResults) + c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot); + return c; + }); + } + } + + Target RunCoreLibsTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.Animation.UnitTests"); + RunCoreTest("./tests/Avalonia.Base.UnitTests"); + RunCoreTest("./tests/Avalonia.Controls.UnitTests"); + RunCoreTest("./tests/Avalonia.Input.UnitTests"); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); + RunCoreTest("./tests/Avalonia.Layout.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests"); + RunCoreTest("./tests/Avalonia.Styling.UnitTests"); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests"); + RunCoreTest("./tests/Avalonia.Skia.UnitTests"); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests"); + }); + + Target RunRenderTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj"); + }); + + Target RunDesignerTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests"); + }); + + [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; + + Target RunLeakTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"; + DotMemoryUnit( + $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}", + timeout: 120_000); + }); + + Target ZipFiles => _ => _ + .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package) + .Executes(() => + { + var data = Parameters; + Zip(data.ZipCoreArtifacts, data.BinRoot); + Zip(data.ZipNuGetArtifacts, data.NugetRoot); + Zip(data.ZipTargetControlCatalogDesktopDir, + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe"))); + }); + + Target CreateIntermediateNugetPackages => _ => _ + .DependsOn(Compile) + .After(RunTests) + .Executes(() => + { + if (Parameters.IsRunningOnWindows) + + MSBuild(Parameters.MSBuildSolution, c => c + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Pack")); + else + DotNetPack(Parameters.MSBuildSolution, c => + c.SetConfiguration(Parameters.Configuration) + .AddProperty("PackageVersion", Parameters.Version)); + }); + + Target CreateNugetPackages => _ => _ + .DependsOn(CreateIntermediateNugetPackages) + .Executes(() => + { + var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config"); + EnsureCleanDirectory(Parameters.NugetRoot); + if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config, + new NumergeNukeLogger())) + throw new Exception("Package merge failed"); + }); + + Target RunTests => _ => _ + .DependsOn(RunCoreLibsTests) + .DependsOn(RunRenderTests) + .DependsOn(RunDesignerTests) + .DependsOn(RunLeakTests); + + Target Package => _ => _ + .DependsOn(RunTests) + .DependsOn(CreateNugetPackages); + + Target CiAzureLinux => _ => _ + .DependsOn(RunTests); + + Target CiAzureOSX => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + Target CiAzureWindows => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + + public static int Main() => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Execute(x => x.Package) + : Execute(x => x.RunTests); + +} diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs new file mode 100644 index 0000000000..afd1950859 --- /dev/null +++ b/nukebuild/BuildParameters.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using Nuke.Common; +using Nuke.Common.BuildServers; +using Nuke.Common.Execution; +using Nuke.Common.IO; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; + +public partial class Build +{ + [Parameter("configuration")] + public string Configuration { get; set; } + + [Parameter("skip-tests")] + public bool SkipTests { get; set; } + + [Parameter("force-nuget-version")] + public string ForceNugetVersion { get; set; } + + public class BuildParameters + { + public string Configuration { get; } + public bool SkipTests { get; } + public string MainRepo { get; } + public string MasterBranch { get; } + public string RepositoryName { get; } + public string RepositoryBranch { get; } + public string ReleaseConfiguration { get; } + public string ReleaseBranchPrefix { get; } + public string MSBuildSolution { get; } + public bool IsLocalBuild { get; } + public bool IsRunningOnUnix { get; } + public bool IsRunningOnWindows { get; } + public bool IsRunningOnAzure { get; } + public bool IsPullRequest { get; } + public bool IsMainRepo { get; } + public bool IsMasterBranch { get; } + public bool IsReleaseBranch { get; } + public bool IsReleasable { get; } + public bool IsMyGetRelease { get; } + public bool IsNuGetRelease { get; } + public bool PublishTestResults { get; } + public string Version { get; } + public AbsolutePath ArtifactsDir { get; } + public AbsolutePath NugetIntermediateRoot { get; } + public AbsolutePath NugetRoot { get; } + public AbsolutePath ZipRoot { get; } + public AbsolutePath BinRoot { get; } + public AbsolutePath TestResultsRoot { get; } + public string DirSuffix { get; } + public List BuildDirs { get; } + public string FileZipSuffix { get; } + public AbsolutePath ZipCoreArtifacts { get; } + public AbsolutePath ZipNuGetArtifacts { get; } + public AbsolutePath ZipSourceControlCatalogDesktopDir { get; } + public AbsolutePath ZipTargetControlCatalogDesktopDir { get; } + + + public BuildParameters(Build b) + { + // ARGUMENTS + Configuration = b.Configuration ?? "Release"; + SkipTests = b.SkipTests; + + // CONFIGURATION + MainRepo = "https://github.com/AvaloniaUI/Avalonia"; + MasterBranch = "refs/heads/master"; + ReleaseBranchPrefix = "refs/heads/release/"; + ReleaseConfiguration = "Release"; + MSBuildSolution = RootDirectory / "dirs.proj"; + + // PARAMETERS + IsLocalBuild = Host == HostType.Console; + IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || + Environment.OSVersion.Platform == PlatformID.MacOSX; + IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsRunningOnAzure = Host == HostType.TeamServices || + Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; + + if (IsRunningOnAzure) + { + RepositoryName = TeamServices.Instance.RepositoryUri; + RepositoryBranch = TeamServices.Instance.SourceBranch; + IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; + IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); + } + IsMainRepo = + StringComparer.OrdinalIgnoreCase.Equals(MainRepo, + RepositoryName); + IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, + RepositoryBranch); + IsReleaseBranch = RepositoryBranch?.StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase) == + true; + + IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); + IsMyGetRelease = IsReleasable; + IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; + + // VERSION + Version = b.ForceNugetVersion ?? GetVersion(); + + if (IsRunningOnAzure) + { + if (!IsNuGetRelease) + { + // Use AssemblyVersion with Build as version + Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta"; + } + + PublishTestResults = true; + } + + // DIRECTORIES + ArtifactsDir = RootDirectory / "artifacts"; + NugetRoot = ArtifactsDir / "nuget"; + NugetIntermediateRoot = RootDirectory / "build-intermediate" / "nuget"; + ZipRoot = ArtifactsDir / "zip"; + BinRoot = ArtifactsDir / "bin"; + TestResultsRoot = ArtifactsDir / "test-results"; + BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList(); + DirSuffix = Configuration; + FileZipSuffix = Version + ".zip"; + ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix); + ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix); + ZipSourceControlCatalogDesktopDir = + RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); + ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix); + } + + string GetVersion() + { + var xdoc = XDocument.Load(RootDirectory / "build/SharedVersion.props"); + return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value; + } + } + +} diff --git a/nukebuild/Numerge b/nukebuild/Numerge new file mode 160000 index 0000000000..4464343aef --- /dev/null +++ b/nukebuild/Numerge @@ -0,0 +1 @@ +Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs new file mode 100644 index 0000000000..461d617643 --- /dev/null +++ b/nukebuild/Shims.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; +using Numerge; + +public partial class Build +{ + static void Information(string info) + { + Logger.Info(info); + } + + static void Information(string info, params object[] args) + { + Logger.Info(info, args); + } + + private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable()); + + private void Zip(PathConstruction.AbsolutePath target, IEnumerable paths) + { + var targetPath = target.ToString(); + bool finished = false, atLeastOneFileAdded = false; + try + { + using (var targetStream = File.Create(targetPath)) + using(var archive = new System.IO.Compression.ZipArchive(targetStream, ZipArchiveMode.Create)) + { + void AddFile(string path, string relativePath) + { + var e = archive.CreateEntry(relativePath.Replace("\\", "/"), CompressionLevel.Optimal); + using (var entryStream = e.Open()) + using (var fileStream = File.OpenRead(path)) + fileStream.CopyTo(entryStream); + atLeastOneFileAdded = true; + } + + foreach (var path in paths) + { + if (Directory.Exists(path)) + { + var dirInfo = new DirectoryInfo(path); + var rootPath = Path.GetDirectoryName(dirInfo.FullName); + foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + { + if (fsEntry is FileInfo) + { + var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName); + AddFile(fsEntry.FullName, relPath); + } + } + } + else if(File.Exists(path)) + { + var name = Path.GetFileName(path); + AddFile(path, name); + } + } + } + + finished = true; + } + finally + { + try + { + if (!finished || !atLeastOneFileAdded) + File.Delete(targetPath); + } + catch + { + //Ignore + } + } + } + + class NumergeNukeLogger : INumergeLogger + { + public void Log(NumergeLogLevel level, string message) + { + if(level == NumergeLogLevel.Error) + Logger.Error(message); + else if (level == NumergeLogLevel.Warning) + Logger.Warn(message); + else + Logger.Info(message); + } + } +} diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj new file mode 100644 index 0000000000..e02acff007 --- /dev/null +++ b/nukebuild/_build.csproj @@ -0,0 +1,37 @@ + + + + Exe + netcoreapp2.0 + false + + False + CS0649;CS0169 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nukebuild/_build.csproj.DotSettings b/nukebuild/_build.csproj.DotSettings new file mode 100644 index 0000000000..9aac7d8e8d --- /dev/null +++ b/nukebuild/_build.csproj.DotSettings @@ -0,0 +1,24 @@ + + False + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True diff --git a/nukebuild/numerge.config b/nukebuild/numerge.config new file mode 100644 index 0000000000..e4e15d693d --- /dev/null +++ b/nukebuild/numerge.config @@ -0,0 +1,23 @@ +{ + "Packages": + [ + { + "Id": "Avalonia", + "MergeAll": true, + "Exclude": ["Avalonia.Remote.Protocol"], + "IncomingIncludeAssetsOverride": "", + "Merge": [ + { + "Id": "Avalonia.Build.Tasks", + "IgnoreMissingFrameworkBinaries": true, + "DoNotMergeDependencies": true + }, + { + "Id": "Avalonia.DesktopRuntime", + "IgnoreMissingFrameworkBinaries": true, + "IgnoreMissingFrameworkDependencies": true + } + ] + } + ] +} diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 3033064594..489cb228aa 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,10 +1,10 @@ - + netstandard2.0;net461;netcoreapp2.0 - + @@ -40,10 +40,6 @@ true build\ - - true - tools\ - diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index 30e3f93b5c..6f21971d3d 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -2,7 +2,7 @@ $(MSBuildThisFileDirectory)\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll $(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe - $(MSBuildThisFileDirectory)\..\tools\Avalonia.Build.Tasks.dll + $(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Avalonia.Build.Tasks.dll false diff --git a/parameters.cake b/parameters.cake deleted file mode 100644 index 4ef7e8e05a..0000000000 --- a/parameters.cake +++ /dev/null @@ -1,129 +0,0 @@ -using System.Xml.Linq; -using System.Linq; - -public class Parameters -{ - public string Configuration { get; private set; } - public bool SkipTests { get; private set; } - public string MainRepo { get; private set; } - public string MasterBranch { get; private set; } - public string ReleasePlatform { get; private set; } - public string ReleaseConfiguration { get; private set; } - public string ReleaseBranchPrefix { get; private set; } - public string MSBuildSolution { get; private set; } - public bool IsLocalBuild { get; private set; } - public bool IsRunningOnUnix { get; private set; } - public bool IsRunningOnWindows { get; private set; } - public bool IsRunningOnAppVeyor { get; private set; } - public bool IsRunningOnAzure { get; private set; } - public bool IsPullRequest { get; private set; } - public bool IsMainRepo { get; private set; } - public bool IsMasterBranch { get; private set; } - public bool IsReleaseBranch { get; private set; } - public bool IsTagged { get; private set; } - public bool IsReleasable { get; private set; } - public bool IsMyGetRelease { get; private set; } - public bool IsNuGetRelease { get; private set; } - public bool PublishTestResults { get; private set; } - public string Version { get; private set; } - public DirectoryPath ArtifactsDir { get; private set; } - public DirectoryPath NugetRoot { get; private set; } - public DirectoryPath ZipRoot { get; private set; } - public DirectoryPath BinRoot { get; private set; } - public DirectoryPath TestResultsRoot { get; private set; } - public string DirSuffix { get; private set; } - public DirectoryPathCollection BuildDirs { get; private set; } - public string FileZipSuffix { get; private set; } - public FilePath ZipCoreArtifacts { get; private set; } - public FilePath ZipNuGetArtifacts { get; private set; } - public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; } - public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; } - - public Parameters(ICakeContext context) - { - var buildSystem = context.BuildSystem(); - - // ARGUMENTS - Configuration = context.Argument("configuration", "Release"); - SkipTests = context.HasArgument("skip-tests"); - - // CONFIGURATION - MainRepo = "https://github.com/AvaloniaUI/Avalonia"; - MasterBranch = "master"; - ReleaseBranchPrefix = "refs/heads/release/"; - ReleaseConfiguration = "Release"; - MSBuildSolution = "./dirs.proj"; - - // PARAMETERS - IsLocalBuild = buildSystem.IsLocalBuild; - IsRunningOnUnix = context.IsRunningOnUnix(); - IsRunningOnWindows = context.IsRunningOnWindows(); - IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor; - IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts"; - - IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest; - IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI")); - IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME")); - IsReleaseBranch = (context.EnvironmentVariable("BUILD_SOURCEBRANCH")??"").StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase); - IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag - && !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name); - IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); - IsMyGetRelease = !IsTagged && IsReleasable; - IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; - - // VERSION - Version = context.Argument("force-nuget-version", GetVersion()); - - if (IsRunningOnAppVeyor) - { - string tagVersion = null; - if (IsTagged) - { - var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name; - var nugetReleasePrefix = "nuget-release-"; - IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); - if(IsNuGetRelease) - tagVersion = tag.Substring(nugetReleasePrefix.Length); - } - if(tagVersion != null) - { - Version = tagVersion; - } - else - { - // Use AssemblyVersion with Build as version - Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta"; - } - } - else if (IsRunningOnAzure) - { - if(!IsNuGetRelease) - { - // Use AssemblyVersion with Build as version - Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta"; - } - - PublishTestResults = true; - } - - // DIRECTORIES - ArtifactsDir = (DirectoryPath)context.Directory("./artifacts"); - NugetRoot = ArtifactsDir.Combine("nuget"); - ZipRoot = ArtifactsDir.Combine("zip"); - BinRoot = ArtifactsDir.Combine("bin"); - TestResultsRoot = ArtifactsDir.Combine("test-results"); - BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj"); - DirSuffix = Configuration; - FileZipSuffix = Version + ".zip"; - ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix); - ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix); - ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); - ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix); - } - - private static string GetVersion() - { - var xdoc = XDocument.Load("./build/SharedVersion.props"); - return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value; - } -} diff --git a/readme.md b/readme.md index 9d113cf2ef..12f683bd55 100644 --- a/readme.md +++ b/readme.md @@ -2,9 +2,9 @@ # Avalonia -| Gitter Chat | Build Status (Win, Linux, OSX) | Appveyor Build Status | Open Collective | -|---|---|---|---| -| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | +| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet | +|---|---|---|---|---| +| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) | [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) | ## About diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 7e2c707e91..589f41c06b 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -9,6 +9,7 @@ + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 1f53dedc14..57c8b700df 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -23,6 +23,7 @@ namespace ControlCatalog.NetCore break; } } + if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { @@ -30,7 +31,12 @@ namespace ControlCatalog.NetCore System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + static void AppMain(Application app, string[] args) + { + app.Run(new MainWindow()); } /// @@ -46,4 +52,4 @@ namespace ControlCatalog.NetCore Console.ReadKey(true); } } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 37014255cc..cb6016b324 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,9 +1,8 @@ - + xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> @@ -20,7 +19,13 @@ - Hello world! + + Hello world! + + Decorated + + CanResize + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 0f7f2e80a8..2ddb5887e5 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -32,10 +32,10 @@ + - diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 1e4e9fef1c..2bd9a39300 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -6,6 +6,7 @@ + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index e380c677c9..e2e9fbd21c 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -31,15 +31,22 @@ namespace ControlCatalog.Pages }.ShowAsync(GetWindow()); }; this.FindControl + public class ColorAnimator : Animator + { + // Opto-electronic conversion function for the sRGB color space + // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value + private static double OECF_sRGB(double linear) + { + // IEC 61966-2-1:1999 + return linear <= 0.0031308d ? linear * 12.92d : (double)(Math.Pow(linear, 1.0d / 2.4d) * 1.055d - 0.055d); + } + + // Electro-optical conversion function for the sRGB color space + // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value + private static double EOCF_sRGB(double srgb) + { + // IEC 61966-2-1:1999 + return srgb <= 0.04045d ? srgb / 12.92d : (double)Math.Pow((srgb + 0.055d) / 1.055d, 2.4d); + } + + public override Color Interpolate(double progress, Color oldValue, Color newValue) + { + // normalize sRGB values. + var oldA = oldValue.A / 255d; + var oldR = oldValue.R / 255d; + var oldG = oldValue.G / 255d; + var oldB = oldValue.B / 255d; + + var newA = newValue.A / 255d; + var newR = newValue.R / 255d; + var newG = newValue.G / 255d; + var newB = newValue.B / 255d; + + // convert from sRGB to linear + oldR = EOCF_sRGB(oldR); + oldG = EOCF_sRGB(oldG); + oldB = EOCF_sRGB(oldB); + + newR = EOCF_sRGB(newR); + newG = EOCF_sRGB(newG); + newB = EOCF_sRGB(newB); + + // compute the interpolated color in linear space + var a = oldA + progress * (newA - oldA); + var r = oldR + progress * (newR - oldR); + var g = oldG + progress * (newG - oldG); + var b = oldB + progress * (newB - oldB); + + // convert back to sRGB in the [0..255] range + a = a * 255d; + r = OECF_sRGB(r) * 255d; + g = OECF_sRGB(g) * 255d; + b = OECF_sRGB(b) * 255d; + + return new Color((byte)Math.Round(a), (byte)Math.Round(r), (byte)Math.Round(g), (byte)Math.Round(b)); + } + } +} diff --git a/src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs new file mode 100644 index 0000000000..8129bd38de --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs @@ -0,0 +1,27 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class CornerRadiusAnimator : Animator + { + public override CornerRadius Interpolate(double progress, CornerRadius oldValue, CornerRadius newValue) + { + var deltaTL = newValue.TopLeft - oldValue.TopLeft; + var deltaTR = newValue.TopRight - oldValue.TopRight; + var deltaBR = newValue.BottomRight - oldValue.BottomRight; + var deltaBL = newValue.BottomLeft - oldValue.BottomLeft; + + var nTL = progress * deltaTL + oldValue.TopLeft; + var nTR = progress * deltaTR + oldValue.TopRight; + var nBR = progress * deltaBR + oldValue.BottomRight; + var nBL = progress * deltaBL + oldValue.BottomLeft; + + return new CornerRadius(nTL, nTR, nBR, nBL); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs new file mode 100644 index 0000000000..793691e5ac --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class PointAnimator : Animator + { + public override Point Interpolate(double progress, Point oldValue, Point newValue) + { + return ((newValue - oldValue) * progress) + oldValue; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs new file mode 100644 index 0000000000..b2d3ec2996 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs @@ -0,0 +1,23 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class RectAnimator : Animator + { + public override Rect Interpolate(double progress, Rect oldValue, Rect newValue) + { + var deltaPos = newValue.Position - oldValue.Position; + var deltaSize = newValue.Size - oldValue.Size; + + var newPos = (deltaPos * progress) + oldValue.Position; + var newSize = (deltaSize * progress) + oldValue.Size; + + return new Rect(newPos, newSize); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs new file mode 100644 index 0000000000..bb4f343e4a --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class SizeAnimator : Animator + { + public override Size Interpolate(double progress, Size oldValue, Size newValue) + { + return ((newValue - oldValue) * progress) + oldValue; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs new file mode 100644 index 0000000000..8776d3a7b7 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -0,0 +1,74 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Logging; +using Avalonia.Media; +using Avalonia.Media.Immutable; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles . + /// + public class SolidColorBrushAnimator : Animator + { + ColorAnimator _colorAnimator; + + void InitializeColorAnimator() + { + _colorAnimator = new ColorAnimator(); + + foreach (AnimatorKeyFrame keyframe in this) + { + _colorAnimator.Add(keyframe); + } + + _colorAnimator.Property = SolidColorBrush.ColorProperty; + } + + public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + { + foreach (var keyframe in this) + { + if (keyframe.Value as ISolidColorBrush == null) + return Disposable.Empty; + + // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. + if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush)) + { + keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; + } + } + + // Add SCB if the target prop is empty. + if (control.GetValue(Property) == null) + control.SetValue(Property, new SolidColorBrush(Colors.Transparent)); + + var targetVal = control.GetValue(Property); + + // Continue if target prop is not empty & is a SolidColorBrush derivative. + if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) + { + if (_colorAnimator == null) + InitializeColorAnimator(); + + SolidColorBrush finalTarget; + + // If it's ISCB, change it back to SCB. + if (targetVal.GetType() == typeof(ImmutableSolidColorBrush)) + { + var col = (ImmutableSolidColorBrush)targetVal; + targetVal = new SolidColorBrush(col.Color); + control.SetValue(Property, targetVal); + } + + finalTarget = targetVal as SolidColorBrush; + + return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); + } + + return Disposable.Empty; + } + + public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; + } +} diff --git a/src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs new file mode 100644 index 0000000000..a6ae95ce3f --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class ThicknessAnimator : Animator + { + public override Thickness Interpolate(double progress, Thickness oldValue, Thickness newValue) + { + return ((newValue - oldValue) * progress) + oldValue; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs similarity index 75% rename from src/Avalonia.Visuals/Animation/TransformAnimator.cs rename to src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 64b3ebd626..e7be272f13 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -2,14 +2,14 @@ using Avalonia.Logging; using Avalonia.Media; -namespace Avalonia.Animation +namespace Avalonia.Animation.Animators { /// /// Animator that handles properties. /// public class TransformAnimator : Animator { - DoubleAnimator childAnimator; + DoubleAnimator _doubleAnimator; /// public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) @@ -27,7 +27,7 @@ namespace Avalonia.Animation // default RenderTransform order. normalTransform.Children.Add(new ScaleTransform()); - normalTransform.Children.Add(new SkewTransform()); + normalTransform.Children.Add(new SkewTransform()); normalTransform.Children.Add(new RotateTransform()); normalTransform.Children.Add(new TranslateTransform()); @@ -36,15 +36,22 @@ namespace Avalonia.Animation var renderTransformType = ctrl.RenderTransform.GetType(); - if (childAnimator == null) + if (_doubleAnimator == null) { - InitializeChildAnimator(); + _doubleAnimator = new DoubleAnimator(); + + foreach (AnimatorKeyFrame keyframe in this) + { + _doubleAnimator.Add(keyframe); + } + + _doubleAnimator.Property = Property; } // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); + return _doubleAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -53,7 +60,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); + return _doubleAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); } } } @@ -73,19 +80,7 @@ namespace Avalonia.Animation return null; } - void InitializeChildAnimator() - { - childAnimator = new DoubleAnimator(); - - foreach (AnimatorKeyFrame keyframe in this) - { - childAnimator.Add(keyframe); - } - - childAnimator.Property = Property; - } - - /// - protected override double DoInterpolation(double time, double neutralValue) => 0; + /// + public override double Interpolate(double p, double o, double n) => 0; } } diff --git a/src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs new file mode 100644 index 0000000000..e1ffb60971 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class VectorAnimator : Animator + { + public override Vector Interpolate(double progress, Vector oldValue, Vector newValue) + { + return ((newValue - oldValue) * progress) + oldValue; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index d5ddf1c7f5..6b8cb8b755 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -21,7 +21,7 @@ namespace Avalonia.Animation /// Initializes a new instance of the class. /// public CrossFade() - :this(TimeSpan.Zero) + : this(TimeSpan.Zero) { } @@ -33,30 +33,40 @@ namespace Avalonia.Animation { _fadeOutAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame() { - Property = Visual.OpacityProperty, - Value = 0d + Setters = + { + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + }, + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) + } }; _fadeInAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame() { - Property = Visual.OpacityProperty, - Value = 0d + Setters = + { + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + }, + Cue = new Cue(0d) } - ) - { - Cue = new Cue(0d) + } }; _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index c5d43068c1..0aa85035ae 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -74,34 +74,36 @@ namespace Avalonia.Animation var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; - - // TODO: Implement relevant transition logic here (or discard this class) - // in favor of XAML based transition for pages if (from != null) { var animation = new Animation { - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = 0d - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame { - Property = translateProperty, - Value = forward ? -distance : distance - } - ) - { - Cue = new Cue(1d) + Setters = + { + new Setter + { + Property = translateProperty, + Value = 0d + } + }, + Cue = new Cue(0d) + }, + new KeyFrame + { + Setters = + { + new Setter + { + Property = translateProperty, + Value = forward ? -distance : distance + } + }, + Cue = new Cue(1d) + } } }; animation.Duration = Duration; @@ -113,29 +115,34 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { + Children = + { - new KeyFrame - ( - new Setter + new KeyFrame { - Property = translateProperty, - Value = forward ? distance : -distance - } - ) - { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + Setters = + { + new Setter + { + Property = translateProperty, + Value = forward ? distance : -distance + } + }, + Cue = new Cue(0d) + }, + new KeyFrame { - Property = translateProperty, - Value = 0d + Setters = + { + new Setter + { + Property = translateProperty, + Value = 0d + } + }, + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) - }, + } }; animation.Duration = Duration; tasks.Add(animation.RunAsync(to)); diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs new file mode 100644 index 0000000000..4b6703133d --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs @@ -0,0 +1,36 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Linq; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class CornerRadiusTransition : Transition + { + /// + public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue) + { + return progress + .Select(p => + { + var f = Easing.Ease(p); + + var deltaTL = newValue.TopLeft - oldValue.TopLeft; + var deltaTR = newValue.TopRight - oldValue.TopRight; + var deltaBR = newValue.BottomRight - oldValue.BottomRight; + var deltaBL = newValue.BottomLeft - oldValue.BottomLeft; + + var nTL = f * deltaTL + oldValue.TopLeft; + var nTR = f * deltaTR + oldValue.TopRight; + var nBR = f * deltaBR + oldValue.BottomRight; + var nBL = f * deltaBL + oldValue.BottomLeft; + + return new CornerRadius(nTL, nTR, nBR, nBL); + }); + } + } +} diff --git a/src/Avalonia.Visuals/Animation/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs similarity index 74% rename from src/Avalonia.Visuals/Animation/PointTransition.cs rename to src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs index a883c517dd..ea0000f960 100644 --- a/src/Avalonia.Visuals/Animation/PointTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs @@ -14,16 +14,11 @@ namespace Avalonia.Animation /// public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue) { - var deltaX = newValue.X - oldValue.Y; - var deltaY = newValue.X - oldValue.Y; - return progress .Select(p => { var f = Easing.Ease(p); - var nX = f * deltaX + oldValue.X; - var nY = f * deltaY + oldValue.Y; - return new Point(nX, nY); + return ((newValue - oldValue) * f) + oldValue; }); } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs new file mode 100644 index 0000000000..5de59edc53 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs @@ -0,0 +1,25 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Linq; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class SizeTransition : Transition + { + /// + public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue) + { + return progress + .Select(p => + { + var f = Easing.Ease(p); + return ((newValue - oldValue) * f) + oldValue; + }); + } + } +} diff --git a/src/Avalonia.Visuals/Animation/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs similarity index 57% rename from src/Avalonia.Visuals/Animation/ThicknessTransition.cs rename to src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs index 6c2ca22b74..e6b5e8904b 100644 --- a/src/Avalonia.Visuals/Animation/ThicknessTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs @@ -14,20 +14,11 @@ namespace Avalonia.Animation /// public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue) { - var deltaL = newValue.Left - oldValue.Left; - var deltaT = newValue.Top - oldValue.Top; - var deltaR = newValue.Right - oldValue.Right; - var deltaB = newValue.Bottom - oldValue.Bottom; - return progress - .Select(p => + .Select(p => { var f = Easing.Ease(p); - var nL = f * deltaL + oldValue.Left; - var nT = f * deltaT + oldValue.Right; - var nR = f * deltaR + oldValue.Top; - var nB = f * deltaB + oldValue.Bottom; - return new Thickness(nL, nT, nR, nB); + return ((newValue - oldValue) * f) + oldValue; }); } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs new file mode 100644 index 0000000000..5271c1378b --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs @@ -0,0 +1,25 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Linq; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class VectorTransition : Transition + { + /// + public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue) + { + return progress + .Select(p => + { + var f = Easing.Ease(p); + return ((newValue - oldValue) * f) + oldValue; + }); + } + } +} diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 85836a785f..2cc7741bbb 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -2,7 +2,6 @@ netstandard2.0 Avalonia - false diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index 2d41acb5bc..09163a2dac 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -3,12 +3,18 @@ using System; using System.Globalization; +using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia { public struct CornerRadius { + static CornerRadius() + { + Animation.Animation.RegisterAnimator(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType)); + } + public CornerRadius(double uniformRadius) { TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius; diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index cdaaa2b4b4..d083a2aaf8 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -293,13 +293,13 @@ namespace Avalonia /// The inverted matrix. public Matrix Invert() { - if (GetDeterminant() == 0) + double d = GetDeterminant(); + + if (d == 0) { throw new InvalidOperationException("Transform is not invertible."); } - double d = GetDeterminant(); - return new Matrix( _m22 / d, -_m12 / d, diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index c2c041f073..f36cd88537 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using Avalonia.Animation; namespace Avalonia.Media { @@ -10,7 +11,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : AvaloniaObject, IMutableBrush + public abstract class Brush : Animatable, IMutableBrush { /// /// Defines the property. diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 8294934751..a37463a0f0 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -3,6 +3,8 @@ using System; using System.Globalization; +using Avalonia.Animation; +using Avalonia.Animation.Animators; namespace Avalonia.Media { @@ -11,6 +13,11 @@ namespace Avalonia.Media /// public readonly struct Color { + static Color() + { + Animation.Animation.RegisterAnimator(prop => typeof(Color).IsAssignableFrom(prop.PropertyType)); + } + /// /// Gets or sets the Alpha component of the color. /// diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 6b19167381..e7d6df5a93 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -2,23 +2,26 @@ using System; using System.Collections.Generic; using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media { public sealed class DrawingContext : IDisposable { + private readonly bool _ownsImpl; private int _currentLevel; - static readonly Stack> StateStackPool = new Stack>(); - static readonly Stack> TransformStackPool = new Stack>(); + private static ThreadSafeObjectPool> StateStackPool { get; } = + ThreadSafeObjectPool>.Default; - private Stack _states = StateStackPool.Count == 0 ? new Stack() : StateStackPool.Pop(); + private static ThreadSafeObjectPool> TransformStackPool { get; } = + ThreadSafeObjectPool>.Default; - private Stack _transformContainers = TransformStackPool.Count == 0 - ? new Stack() - : TransformStackPool.Pop(); + private Stack _states = StateStackPool.Get(); + + private Stack _transformContainers = TransformStackPool.Get(); readonly struct TransformContainer { @@ -35,6 +38,13 @@ namespace Avalonia.Media public DrawingContext(IDrawingContextImpl impl) { PlatformImpl = impl; + _ownsImpl = true; + } + + public DrawingContext(IDrawingContextImpl impl, bool ownsImpl) + { + _ownsImpl = ownsImpl; + PlatformImpl = impl; } public IDrawingContextImpl PlatformImpl { get; } @@ -299,11 +309,14 @@ namespace Avalonia.Media { while (_states.Count != 0) _states.Peek().Dispose(); - StateStackPool.Push(_states); + StateStackPool.Return(_states); _states = null; - TransformStackPool.Push(_transformContainers); + if (_transformContainers.Count != 0) + throw new InvalidOperationException("Transform container stack is non-empty"); + TransformStackPool.Return(_transformContainers); _transformContainers = null; - PlatformImpl.Dispose(); + if (_ownsImpl) + PlatformImpl.Dispose(); } private static bool PenIsVisible(Pen pen) diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 16dba573fd..5c152cd8a0 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Media.Fonts; +using Avalonia.Platform; namespace Avalonia.Media { @@ -40,9 +41,10 @@ namespace Avalonia.Media /// /// The name of the . /// The source of font resources. - public FontFamily(string name, Uri source) : this(name) + /// + public FontFamily(string name, Uri source, Uri baseUri = null) : this(name) { - Key = new FontFamilyKey(source); + Key = new FontFamilyKey(source, baseUri); } /// @@ -50,6 +52,12 @@ namespace Avalonia.Media /// public static FontFamily Default => new FontFamily(String.Empty); + /// + /// Represents all font families in the system. This can be an expensive call depending on platform implementation. + /// + public static IEnumerable SystemFontFamilies => + AvaloniaLocator.Current.GetService().InstalledFontNames.Select(name => new FontFamily(name)); + /// /// Gets the primary family name of the font family. /// @@ -87,11 +95,12 @@ namespace Avalonia.Media /// Parses a string. /// /// The string. + /// /// /// /// Specified family is not supported. /// - public static FontFamily Parse(string s) + public static FontFamily Parse(string s, Uri baseUri = null) { if (string.IsNullOrEmpty(s)) { @@ -112,7 +121,13 @@ namespace Avalonia.Media case 2: { - return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute)); + var uri = segments[0].StartsWith("/") + ? new Uri(segments[0], UriKind.Relative) + : new Uri(segments[0], UriKind.RelativeOrAbsolute); + + return uri.IsAbsoluteUri + ? new FontFamily(segments[1], uri) + : new FontFamily(segments[1], uri, baseUri); } default: diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 90ccac0e46..76ee2b7aad 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; namespace Avalonia.Media.Fonts { @@ -12,48 +11,26 @@ namespace Avalonia.Media.Fonts public class FontFamilyKey { /// - /// Creates a new instance of and extracts and from given + /// Creates a new instance of /// /// - public FontFamilyKey(Uri source) + /// + public FontFamilyKey(Uri source, Uri baseUri = null) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + Source = source ?? throw new ArgumentNullException(nameof(source)); - if (source.AbsolutePath.Contains(".ttf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".ttf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - if (source.AbsolutePath.Contains(".otf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".otf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - Location = source; - } - } + BaseUri = baseUri; } /// - /// Location of stored font asset that belongs to a + /// Source of stored font asset that belongs to a /// - public Uri Location { get; } + public Uri Source { get; } /// - /// Optional filename for a font asset that belongs to a + /// A base URI to use if is relative /// - public string FileName { get; } + public Uri BaseUri { get; } /// /// Returns a hash code for this instance. @@ -67,14 +44,14 @@ namespace Avalonia.Media.Fonts { var hash = (int)2166136261; - if (Location != null) + if (Source != null) { - hash = (hash * 16777619) ^ Location.GetHashCode(); + hash = (hash * 16777619) ^ Source.GetHashCode(); } - if (FileName != null) + if (BaseUri != null) { - hash = (hash * 16777619) ^ FileName.GetHashCode(); + hash = (hash * 16777619) ^ BaseUri.GetHashCode(); } return hash; @@ -95,12 +72,12 @@ namespace Avalonia.Media.Fonts return false; } - if (Location != other.Location) + if (Source != other.Source) { return false; } - if (FileName != other.FileName) + if (BaseUri != other.BaseUri) { return false; } @@ -116,16 +93,12 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - if (FileName == null) + if (!Source.IsAbsoluteUri && BaseUri != null) { - return Location.PathAndQuery; + return BaseUri.Authority + Source; } - var builder = new UriBuilder(Location); - - builder.Path += "." + FileName; - - return builder.ToString(); + return Source.ToString(); } } } diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 166eb4a661..063fe8f20d 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Avalonia.Platform; namespace Avalonia.Media.Fonts @@ -18,23 +17,35 @@ namespace Avalonia.Media.Fonts s_assetLoader = AvaloniaLocator.Current.GetService(); } + /// + /// Loads all font assets that belong to the specified + /// + /// + /// public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { - return fontFamilyKey.FileName != null - ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) - : GetFontAssetsByLocation(fontFamilyKey.Location); + var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); + + if (sourceWithoutArguments.EndsWith(".ttf") + || sourceWithoutArguments.EndsWith(".otf")) + { + return GetFontAssetsByExpression(fontFamilyKey); + } + + return GetFontAssetsBySource(fontFamilyKey); } /// /// Searches for font assets at a given location and returns a quantity of found assets /// - /// + /// /// - private static IEnumerable GetFontAssetsByLocation(Uri location) + private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) { - var availableAssets = s_assetLoader.GetAssets(location, null); + var availableAssets = s_assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); - var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); + var matchingAssets = + availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); return matchingAssets; } @@ -43,20 +54,73 @@ namespace Avalonia.Media.Fonts /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// File names can target multiple files with * wildcard. For example "FontFile*.ttf" /// - /// - /// + /// /// - private static IEnumerable GetFontAssetsByFileName(Uri location, string fileName) + private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) { - var availableResources = s_assetLoader.GetAssets(location, null); + var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); - var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + var availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri); - var matchingResources = - availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"))); + string compareTo; + + if (fontFamilyKey.Source.IsAbsoluteUri) + { + if (fontFamilyKey.Source.Scheme == "resm") + { + compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + + var matchingResources = availableResources.Where( + x => x.AbsolutePath.Contains(compareTo) + && x.AbsolutePath.EndsWith(fileExtension)); return matchingResources; } + private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location) + { + if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm") + { + fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault(); + + var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault(); + + location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute); + + return fileName; + } + + var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); + + var fileNameWithExtension = pathSegments.Last(); + + var fileNameSegments = fileNameWithExtension.Split('.'); + + fileExtension = "." + fileNameSegments.Last(); + + if (fontFamilyKey.BaseUri != null) + { + var relativePath = fontFamilyKey.Source.OriginalString + .Replace(fileNameWithExtension, string.Empty); + + location = new Uri(fontFamilyKey.BaseUri, relativePath); + } + else + { + location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty)); + } + + return fileNameSegments.First(); + } } } diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs index 00d96a0b3c..25a6eb10dc 100644 --- a/src/Avalonia.Visuals/Media/GradientStop.cs +++ b/src/Avalonia.Visuals/Media/GradientStop.cs @@ -11,13 +11,13 @@ namespace Avalonia.Media /// /// Describes the property. /// - public static StyledProperty OffsetProperty = + public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); /// /// Describes the property. /// - public static StyledProperty ColorProperty = + public static readonly StyledProperty ColorProperty = AvaloniaProperty.Register(nameof(Color)); /// diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 0ad826f5a9..8dd75d2374 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -75,7 +75,7 @@ namespace Avalonia.Media.Imaging public Vector Dpi => PlatformImpl.Item.Dpi; /// - public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi); + public Size Size => PlatformImpl.Item.PixelSize.ToSizeWithDpi(Dpi); /// public PixelSize PixelSize => PlatformImpl.Item.PixelSize; diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs new file mode 100644 index 0000000000..995781ee9f --- /dev/null +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -0,0 +1,201 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Globalization; +using Avalonia.Utilities; + +namespace Avalonia +{ + /// + /// Represents a point in device pixels. + /// + public readonly struct PixelPoint + { + /// + /// A point representing 0,0. + /// + public static readonly PixelPoint Origin = new PixelPoint(0, 0); + + /// + /// Initializes a new instance of the structure. + /// + /// The X co-ordinate. + /// The Y co-ordinate. + public PixelPoint(int x, int y) + { + X = x; + Y = y; + } + + /// + /// Gets the X co-ordinate. + /// + public int X { get; } + + /// + /// Gets the Y co-ordinate. + /// + public int Y { get; } + + /// + /// Checks for equality between two s. + /// + /// The first point. + /// The second point. + /// True if the points are equal; otherwise false. + public static bool operator ==(PixelPoint left, PixelPoint right) + { + return left.X == right.X && left.Y == right.Y; + } + + /// + /// Checks for inequality between two s. + /// + /// The first point. + /// The second point. + /// True if the points are unequal; otherwise false. + public static bool operator !=(PixelPoint left, PixelPoint right) + { + return !(left == right); + } + + /// + /// Parses a string. + /// + /// The string. + /// The . + public static PixelPoint Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint")) + { + return new PixelPoint( + tokenizer.ReadInt32(), + tokenizer.ReadInt32()); + } + } + + /// + /// Checks for equality between a point and an object. + /// + /// The object. + /// + /// True if is a point that equals the current point. + /// + public override bool Equals(object obj) + { + if (obj is PixelPoint other) + { + return this == other; + } + + return false; + } + + /// + /// Returns a hash code for a . + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + X.GetHashCode(); + hash = (hash * 23) + Y.GetHashCode(); + return hash; + } + } + + /// + /// Returns a new with the same Y co-ordinate and the specified X co-ordinate. + /// + /// The X co-ordinate. + /// The new . + public PixelPoint WithX(int x) => new PixelPoint(x, Y); + + /// + /// Returns a new with the same X co-ordinate and the specified Y co-ordinate. + /// + /// The Y co-ordinate. + /// The new . + public PixelPoint WithY(int y) => new PixelPoint(X, y); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent point. + public Point ToPoint(double scale) => new Point(X / scale, Y / scale); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent point. + public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent point. + public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent point. + public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The point. + /// The scaling factor. + /// The device-independent point. + public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint( + (int)(point.X * scale), + (int)(point.Y * scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The point. + /// The scaling factor. + /// The device-independent point. + public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint( + (int)(point.X * scale.X), + (int)(point.Y * scale.Y)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The point. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The point. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Returns the string representation of the point. + /// + /// The string representation of the point. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y); + } + } +} diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs new file mode 100644 index 0000000000..9c8e5ad1c4 --- /dev/null +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -0,0 +1,436 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Globalization; +using Avalonia.Utilities; + +namespace Avalonia +{ + /// + /// Represents a rectangle in device pixels. + /// + public readonly struct PixelRect + { + /// + /// An empty rectangle. + /// + public static readonly PixelRect Empty = default; + + /// + /// Initializes a new instance of the structure. + /// + /// The X position. + /// The Y position. + /// The width. + /// The height. + public PixelRect(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The size of the rectangle. + public PixelRect(PixelSize size) + { + X = 0; + Y = 0; + Width = size.Width; + Height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The position of the rectangle. + /// The size of the rectangle. + public PixelRect(PixelPoint position, PixelSize size) + { + X = position.X; + Y = position.Y; + Width = size.Width; + Height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The top left position of the rectangle. + /// The bottom right position of the rectangle. + public PixelRect(PixelPoint topLeft, PixelPoint bottomRight) + { + X = topLeft.X; + Y = topLeft.Y; + Width = bottomRight.X - topLeft.X; + Height = bottomRight.Y - topLeft.Y; + } + + /// + /// Gets the X position. + /// + public int X { get; } + + /// + /// Gets the Y position. + /// + public int Y { get; } + + /// + /// Gets the width. + /// + public int Width { get; } + + /// + /// Gets the height. + /// + public int Height { get; } + + /// + /// Gets the position of the rectangle. + /// + public PixelPoint Position => new PixelPoint(X, Y); + + /// + /// Gets the size of the rectangle. + /// + public PixelSize Size => new PixelSize(Width, Height); + + /// + /// Gets the right position of the rectangle. + /// + public int Right => X + Width; + + /// + /// Gets the bottom position of the rectangle. + /// + public int Bottom => Y + Height; + + /// + /// Gets the top left point of the rectangle. + /// + public PixelPoint TopLeft => new PixelPoint(X, Y); + + /// + /// Gets the top right point of the rectangle. + /// + public PixelPoint TopRight => new PixelPoint(Right, Y); + + /// + /// Gets the bottom left point of the rectangle. + /// + public PixelPoint BottomLeft => new PixelPoint(X, Bottom); + + /// + /// Gets the bottom right point of the rectangle. + /// + public PixelPoint BottomRight => new PixelPoint(Right, Bottom); + + /// + /// Gets the center point of the rectangle. + /// + public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); + + /// + /// Gets a value that indicates whether the rectangle is empty. + /// + public bool IsEmpty => Width == 0 && Height == 0; + + /// + /// Checks for equality between two s. + /// + /// The first rect. + /// The second rect. + /// True if the rects are equal; otherwise false. + public static bool operator ==(PixelRect left, PixelRect right) + { + return left.Position == right.Position && left.Size == right.Size; + } + + /// + /// Checks for inequality between two s. + /// + /// The first rect. + /// The second rect. + /// True if the rects are unequal; otherwise false. + public static bool operator !=(PixelRect left, PixelRect right) + { + return !(left == right); + } + + /// + /// Determines whether a point in in the bounds of the rectangle. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool Contains(PixelPoint p) + { + return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom; + } + + /// + /// Determines whether the rectangle fully contains another rectangle. + /// + /// The rectangle. + /// true if the rectangle is fully contained; otherwise false. + public bool Contains(PixelRect r) + { + return Contains(r.TopLeft) && Contains(r.BottomRight); + } + + /// + /// Centers another rectangle in this rectangle. + /// + /// The rectangle to center. + /// The centered rectangle. + public PixelRect CenterRect(PixelRect rect) + { + return new PixelRect( + X + ((Width - rect.Width) / 2), + Y + ((Height - rect.Height) / 2), + rect.Width, + rect.Height); + } + + /// + /// Returns a boolean indicating whether the given object is equal to this rectangle. + /// + /// The object to compare against. + /// True if the object is equal to this rectangle; false otherwise. + public override bool Equals(object obj) + { + if (obj is PixelRect other) + { + return this == other; + } + + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + X.GetHashCode(); + hash = (hash * 23) + Y.GetHashCode(); + hash = (hash * 23) + Width.GetHashCode(); + hash = (hash * 23) + Height.GetHashCode(); + return hash; + } + } + + /// + /// Gets the intersection of two rectangles. + /// + /// The other rectangle. + /// The intersection. + public PixelRect Intersect(PixelRect rect) + { + var newLeft = (rect.X > X) ? rect.X : X; + var newTop = (rect.Y > Y) ? rect.Y : Y; + var newRight = (rect.Right < Right) ? rect.Right : Right; + var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom; + + if ((newRight > newLeft) && (newBottom > newTop)) + { + return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop); + } + else + { + return Empty; + } + } + + /// + /// Determines whether a rectangle intersects with this rectangle. + /// + /// The other rectangle. + /// + /// True if the specified rectangle intersects with this one; otherwise false. + /// + public bool Intersects(PixelRect rect) + { + return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom); + } + + /// + /// Gets the union of two rectangles. + /// + /// The other rectangle. + /// The union. + public PixelRect Union(PixelRect rect) + { + if (IsEmpty) + { + return rect; + } + else if (rect.IsEmpty) + { + return this; + } + else + { + var x1 = Math.Min(X, rect.X); + var x2 = Math.Max(Right, rect.Right); + var y1 = Math.Min(Y, rect.Y); + var y2 = Math.Max(Bottom, rect.Bottom); + + return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2)); + } + } + + /// + /// Returns a new with the specified X position. + /// + /// The x position. + /// The new . + public PixelRect WithX(int x) + { + return new PixelRect(x, Y, Width, Height); + } + + /// + /// Returns a new with the specified Y position. + /// + /// The y position. + /// The new . + public PixelRect WithY(int y) + { + return new PixelRect(X, y, Width, Height); + } + + /// + /// Returns a new with the specified width. + /// + /// The width. + /// The new . + public PixelRect WithWidth(int width) + { + return new PixelRect(X, Y, width, Height); + } + + /// + /// Returns a new with the specified height. + /// + /// The height. + /// The new . + public PixelRect WithHeight(int height) + { + return new PixelRect(X, Y, Width, Height); + } + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent rect. + public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent rect. + public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent rect. + public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent rect. + public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The rect. + /// The scaling factor. + /// The device-independent rect. + public static PixelRect FromRect(Rect rect, double scale) => new PixelRect( + PixelPoint.FromPoint(rect.Position, scale), + PixelSize.FromSize(rect.Size, scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The rect. + /// The scaling factor. + /// The device-independent point. + public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect( + PixelPoint.FromPoint(rect.Position, scale), + PixelSize.FromSize(rect.Size, scale)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The rect. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect( + PixelPoint.FromPointWithDpi(rect.Position, dpi), + PixelSize.FromSizeWithDpi(rect.Size, dpi)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The rect. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect( + PixelPoint.FromPointWithDpi(rect.Position, dpi), + PixelSize.FromSizeWithDpi(rect.Size, dpi)); + + /// + /// Returns the string representation of the rectangle. + /// + /// The string representation of the rectangle. + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}, {1}, {2}, {3}", + X, + Y, + Width, + Height); + } + + /// + /// Parses a string. + /// + /// The string. + /// The parsed . + public static PixelRect Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect")) + { + return new PixelRect( + tokenizer.ReadInt32(), + tokenizer.ReadInt32(), + tokenizer.ReadInt32(), + tokenizer.ReadInt32() + ); + } + } + } +} diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index 6785b51716..b903b804f9 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -72,7 +72,7 @@ namespace Avalonia /// The . public static PixelSize Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize")) { return new PixelSize( tokenizer.ReadInt32(), @@ -126,13 +126,29 @@ namespace Avalonia /// The new . public PixelSize WithHeight(int height) => new PixelSize(Width, height); + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent size. + public Size ToSize(double scale) => new Size(Width / scale, Height / scale); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent size. + public Size ToSize(Vector scale) => new Size(Width / scale.X, Height / scale.Y); + /// /// Converts the to a device-independent using the /// specified dots per inch (DPI). /// /// The dots per inch. /// The device-independent size. - public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96)); + public Size ToSizeWithDpi(double dpi) => ToSize(dpi / 96); /// /// Converts the to a device-independent using the @@ -140,7 +156,27 @@ namespace Avalonia /// /// The dots per inch. /// The device-independent size. - public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96)); + public Size ToSizeWithDpi(Vector dpi) => ToSize(new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The size. + /// The scaling factor. + /// The device-independent size. + public static PixelSize FromSize(Size size, double scale) => new PixelSize( + (int)Math.Ceiling(size.Width * scale), + (int)Math.Ceiling(size.Height * scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The size. + /// The scaling factor. + /// The device-independent size. + public static PixelSize FromSize(Size size, Vector scale) => new PixelSize( + (int)Math.Ceiling(size.Width * scale.X), + (int)Math.Ceiling(size.Height * scale.Y)); /// /// Converts a to device pixels using the specified dots per inch (DPI). @@ -148,9 +184,7 @@ namespace Avalonia /// The size. /// The dots per inch. /// The device-independent size. - public static PixelSize FromSize(Size size, double dpi) => new PixelSize( - (int)(size.Width * (dpi / 96)), - (int)(size.Height * (dpi / 96))); + public static PixelSize FromSizeWithDpi(Size size, double dpi) => FromSize(size, dpi / 96); /// /// Converts a to device pixels using the specified dots per inch (DPI). @@ -158,9 +192,7 @@ namespace Avalonia /// The size. /// The dots per inch. /// The device-independent size. - public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize( - (int)Math.Ceiling(size.Width * (dpi.X / 96)), - (int)Math.Ceiling(size.Height * (dpi.Y / 96))); + public static PixelSize FromSizeWithDpi(Size size, Vector dpi) => FromSize(size, new Vector(dpi.X / 96, dpi.Y / 96)); /// /// Returns the string representation of the size. diff --git a/src/Avalonia.Visuals/Media/SolidColorBrush.cs b/src/Avalonia.Visuals/Media/SolidColorBrush.cs index 32b87df56b..bacb9e4ad7 100644 --- a/src/Avalonia.Visuals/Media/SolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/SolidColorBrush.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Animation; +using Avalonia.Animation.Animators; using Avalonia.Media.Immutable; namespace Avalonia.Media @@ -18,6 +20,7 @@ namespace Avalonia.Media static SolidColorBrush() { + Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType)); AffectsRender(ColorProperty); } diff --git a/src/Avalonia.Visuals/Media/Transform.cs b/src/Avalonia.Visuals/Media/Transform.cs index 677514763c..7a70657ce0 100644 --- a/src/Avalonia.Visuals/Media/Transform.cs +++ b/src/Avalonia.Visuals/Media/Transform.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Animation; +using Avalonia.Animation.Animators; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 6dde2bb591..37ac0953bf 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// public class Typeface { - public static Typeface Default = new Typeface(FontFamily.Default); + public static readonly Typeface Default = new Typeface(FontFamily.Default); /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index aacdef0538..3a1f79e32a 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// public interface IPlatformRenderInterface { + /// + /// Get all installed fonts in the system + /// + IEnumerable InstalledFontNames { get; } + /// /// Creates a formatted text implementation. /// diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index e74094fe50..0d3e354615 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Globalization; +using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia @@ -11,6 +12,11 @@ namespace Avalonia /// public readonly struct Point { + static Point() + { + Animation.Animation.RegisterAnimator(prop => typeof(Point).IsAssignableFrom(prop.PropertyType)); + } + /// /// The X position. /// @@ -133,7 +139,7 @@ namespace Avalonia /// Point to multiply /// Factor /// Points having its coordinates multiplied - public static Point operator *(Point p, double k) => new Point(p.X*k, p.Y*k); + public static Point operator *(Point p, double k) => new Point(p.X * k, p.Y * k); /// /// Multiplies a point by a factor coordinate-wise @@ -141,7 +147,7 @@ namespace Avalonia /// Point to multiply /// Factor /// Points having its coordinates multiplied - public static Point operator *(double k, Point p) => new Point(p.X*k, p.Y*k); + public static Point operator *(double k, Point p) => new Point(p.X * k, p.Y * k); /// /// Divides a point by a factor coordinate-wise @@ -149,8 +155,8 @@ namespace Avalonia /// Point to divide by /// Factor /// Points having its coordinates divided - public static Point operator /(Point p, double k) => new Point(p.X/k, p.Y/k); - + public static Point operator /(Point p, double k) => new Point(p.X / k, p.Y / k); + /// /// Applies a matrix to a point. /// diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 63d34b474b..530a47729f 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia @@ -12,6 +13,11 @@ namespace Avalonia /// public readonly struct Rect { + static Rect() + { + Animation.Animation.RegisterAnimator(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType)); + } + /// /// An empty rectangle. /// diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index b48efaa34e..60e624948e 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -29,6 +29,7 @@ namespace Avalonia.Rendering private readonly ISceneBuilder _sceneBuilder; private bool _running; + private bool _disposed; private volatile IRef _scene; private DirtyVisuals _dirty; private IRef _overlay; @@ -99,6 +100,9 @@ namespace Avalonia.Rendering /// public string DebugFramesPath { get; set; } + /// + public event EventHandler SceneInvalidated; + /// /// Gets the render layers. /// @@ -122,15 +126,34 @@ namespace Avalonia.Rendering { lock (_sceneLock) { + if (_disposed) + return; + _disposed = true; var scene = _scene; _scene = null; scene?.Dispose(); } Stop(); + DisposeRenderTarget(); + } + + void DisposeRenderTarget() + { + using (var l = _lock.TryLock()) + { + if(l == null) + { + // We are still trying to render on the render thread, try again a bit later + DispatcherTimer.RunOnce(DisposeRenderTarget, TimeSpan.FromMilliseconds(50), + DispatcherPriority.Background); + return; + } - Layers.Clear(); - RenderTarget?.Dispose(); + Layers.Clear(); + RenderTarget?.Dispose(); + RenderTarget = null; + } } /// @@ -152,7 +175,8 @@ namespace Avalonia.Rendering var t = (IRenderLoopTask)this; if(t.NeedsUpdate) UpdateScene(); - Render(true); + if(_scene?.Item != null) + Render(true); } /// @@ -336,16 +360,34 @@ namespace Avalonia.Rendering private void RenderToLayers(Scene scene) { - if (scene.Layers.HasDirty) + foreach (var layer in scene.Layers) { - foreach (var layer in scene.Layers) - { - var renderTarget = Layers[layer.LayerRoot].Bitmap; - var node = (VisualNode)scene.FindNode(layer.LayerRoot); + var renderLayer = Layers[layer.LayerRoot]; + if (layer.Dirty.IsEmpty && !renderLayer.IsEmpty) + continue; + var renderTarget = renderLayer.Bitmap; + var node = (VisualNode)scene.FindNode(layer.LayerRoot); - if (node != null) + if (node != null) + { + using (var context = renderTarget.Item.CreateDrawingContext(this)) { - using (var context = renderTarget.Item.CreateDrawingContext(this)) + if (renderLayer.IsEmpty) + { + // Render entire layer root node + context.Clear(Colors.Transparent); + context.Transform = Matrix.Identity; + context.PushClip(node.ClipBounds); + Render(context, node, layer.LayerRoot, node.ClipBounds); + context.PopClip(); + if (DrawDirtyRects) + { + _dirtyRectsDisplay.Add(node.ClipBounds); + } + + renderLayer.IsEmpty = false; + } + else { foreach (var rect in layer.Dirty) { @@ -364,6 +406,7 @@ namespace Avalonia.Rendering } } } + } private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent) @@ -442,6 +485,8 @@ namespace Avalonia.Rendering Dispatcher.UIThread.VerifyAccess(); lock (_sceneLock) { + if (_disposed) + return; if (_scene?.Item.Generation > _lastSceneId) return; } @@ -470,6 +515,21 @@ namespace Avalonia.Rendering oldScene?.Dispose(); } + if (SceneInvalidated != null) + { + var rect = new Rect(); + + foreach (var layer in scene.Layers) + { + foreach (var dirty in layer.Dirty) + { + rect = rect.Union(dirty); + } + } + + SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); + } + _dirty.Clear(); } else diff --git a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs index 2b364e5d22..044911ca95 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs @@ -41,15 +41,15 @@ namespace Avalonia.Rendering /// /// Converts a point from screen to client coordinates. /// - /// The point in screen coordinates. + /// The point in screen device coordinates. /// The point in client coordinates. - Point PointToClient(Point point); + Point PointToClient(PixelPoint point); /// /// Converts a point from client to screen coordinates. /// /// The point in client coordinates. - /// The point in screen coordinates. - Point PointToScreen(Point point); + /// The point in screen device coordinates. + PixelPoint PointToScreen(Point point); } } diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index 9085e63aa9..36a1f7d220 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -18,11 +18,20 @@ namespace Avalonia.Rendering bool DrawFps { get; set; } /// - /// Gets or sets a value indicating whether the renderer should a visual representation + /// Gets or sets a value indicating whether the renderer should draw a visual representation /// of its dirty rectangles. /// bool DrawDirtyRects { get; set; } + /// + /// Raised when a portion of the scene has been invalidated. + /// + /// + /// Indicates that the underlying low-level scene information has been updated. Used to + /// signal that an update to the current pointer-over state may be required. + /// + event EventHandler SceneInvalidated; + /// /// Mark a visual as dirty and needing re-rendering. /// @@ -63,4 +72,4 @@ namespace Avalonia.Rendering /// void Stop(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 08f3803e9b..21129e38af 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -42,6 +42,9 @@ namespace Avalonia.Rendering /// public bool DrawDirtyRects { get; set; } + /// + public event EventHandler SceneInvalidated; + /// public void Paint(Rect rect) { @@ -81,6 +84,8 @@ namespace Avalonia.Rendering _renderTarget.Dispose(); _renderTarget = null; } + + SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); } /// @@ -266,7 +271,14 @@ namespace Avalonia.Rendering if (clipToBounds) { - clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + if (visual.RenderTransform != null) + { + clipRect = new Rect(visual.Bounds.Size); + } + else + { + clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + } } using (context.PushPostTransform(m)) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index cd09bdee10..d6676e25ff 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -7,39 +7,39 @@ namespace Avalonia.Rendering { public class RenderLayer { - private readonly IDrawingContextImpl _drawingContext; - public RenderLayer( IDrawingContextImpl drawingContext, Size size, double scaling, IVisual layerRoot) { - _drawingContext = drawingContext; Bitmap = RefCountable.Create(drawingContext.CreateLayer(size)); Size = size; Scaling = scaling; LayerRoot = layerRoot; + IsEmpty = true; } public IRef Bitmap { get; private set; } + public bool IsEmpty { get; set; } public double Scaling { get; private set; } public Size Size { get; private set; } public IVisual LayerRoot { get; } - public void ResizeBitmap(Size size, double scaling) + public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling) { if (Size != size || Scaling != scaling) { - var resized = RefCountable.Create(_drawingContext.CreateLayer(size)); + var resized = RefCountable.Create(drawingContext.CreateLayer(size)); using (var context = resized.Item.CreateDrawingContext(null)) { context.Clear(Colors.Transparent); - context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size)); Bitmap.Dispose(); Bitmap = resized; + Scaling = scaling; Size = size; + IsEmpty = true; } } } diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index 9b0ce944e7..0ff7862ab6 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -29,11 +29,11 @@ namespace Avalonia.Rendering } else { - layer.ResizeBitmap(scene.Size, scene.Scaling); + layer.RecreateBitmap(context, scene.Size, scene.Scaling); } } - for (var i = _inner.Count - 1; i >= 0; --i) + for (var i = 0; i < _inner.Count;) { var layer = _inner[i]; @@ -43,6 +43,8 @@ namespace Avalonia.Rendering _inner.RemoveAt(i); _index.Remove(layer.LayerRoot); } + else + i++; } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index ffa0b0bcc5..ad4c475d89 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph private IEnumerable HitTest(IVisualNode node, Point p, Rect? clip, Func filter) { - if (filter?.Invoke(node.Visual) != false) + if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) { var clipped = false; @@ -186,7 +186,7 @@ namespace Avalonia.Rendering.SceneGraph } } - if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree) + if (node.HitTest(p)) { yield return node.Visual; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 8000d413ea..159c3cd0fa 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -220,6 +220,7 @@ namespace Avalonia.Rendering.SceneGraph Transform = Transform, ClipBounds = ClipBounds, ClipToBounds = ClipToBounds, + LayoutBounds = LayoutBounds, GeometryClip = GeometryClip, _opacity = Opacity, OpacityMask = OpacityMask, diff --git a/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs new file mode 100644 index 0000000000..40cb9f3356 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs @@ -0,0 +1,36 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Rendering +{ + /// + /// Provides data for the event. + /// + public class SceneInvalidatedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The render root that has been updated. + /// The updated area. + public SceneInvalidatedEventArgs( + IRenderRoot root, + Rect dirtyRect) + { + RenderRoot = root; + DirtyRect = dirtyRect; + } + + /// + /// Gets the invalidated area. + /// + public Rect DirtyRect { get; } + + /// + /// Gets the render root that has been invalidated. + /// + public IRenderRoot RenderRoot { get; } + } +} diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 8057571bf1..782c5ea67b 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia @@ -12,6 +13,11 @@ namespace Avalonia /// public readonly struct Size { + static Size() + { + Animation.Animation.RegisterAnimator(prop => typeof(Size).IsAssignableFrom(prop.PropertyType)); + } + /// /// A size representing infinity. /// diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index 3e70ecafb1..830ee4666e 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -3,6 +3,8 @@ using System; using System.Globalization; +using Avalonia.Animation; +using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia @@ -12,6 +14,11 @@ namespace Avalonia /// public readonly struct Thickness { + static Thickness() + { + Animation.Animation.RegisterAnimator(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType)); + } + /// /// The thickness on the left. /// @@ -134,6 +141,36 @@ namespace Avalonia a.Bottom + b.Bottom); } + /// + /// Subtracts two Thicknesses. + /// + /// The first thickness. + /// The second thickness. + /// The equality. + public static Thickness operator -(Thickness a, Thickness b) + { + return new Thickness( + a.Left - b.Left, + a.Top - b.Top, + a.Right - b.Right, + a.Bottom - b.Bottom); + } + + /// + /// Multiplies a Thickness to a scalar. + /// + /// The thickness. + /// The scalar. + /// The equality. + public static Thickness operator *(Thickness a, double b) + { + return new Thickness( + a.Left * b, + a.Top * b, + a.Right * b, + a.Bottom * b); + } + /// /// Adds a Thickness to a Size. /// @@ -169,7 +206,7 @@ namespace Avalonia { using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) { - if(tokenizer.TryReadDouble(out var a)) + if (tokenizer.TryReadDouble(out var a)) { if (tokenizer.TryReadDouble(out var b)) { diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index c2db17cd86..2f1690184d 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using Avalonia.Animation.Animators; using JetBrains.Annotations; namespace Avalonia @@ -12,6 +13,11 @@ namespace Avalonia /// public readonly struct Vector { + static Vector() + { + Animation.Animation.RegisterAnimator(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType)); + } + /// /// The X vector. /// @@ -60,7 +66,7 @@ namespace Avalonia /// The dot product public static double operator *(Vector a, Vector b) { - return a.X*b.X + a.Y*b.Y; + return a.X * b.X + a.Y * b.Y; } /// @@ -88,7 +94,7 @@ namespace Avalonia /// /// Length of the vector /// - public double Length => Math.Sqrt(X*X + Y*Y); + public double Length => Math.Sqrt(X * X + Y * Y); /// /// Negates a vector. diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index 7c5d011ce8..650921a985 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -18,10 +18,12 @@ namespace Avalonia /// The visual. /// The point in screen coordinates. /// The point in client coordinates. - public static Point PointToClient(this IVisual visual, Point point) + public static Point PointToClient(this IVisual visual, PixelPoint point) { - var p = GetRootAndPosition(visual); - return p.Item1.PointToClient(point - p.Item2); + var (root, offset) = GetRootAndPosition(visual); + var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling); + var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y); + return root.PointToClient(screenPoint); } /// @@ -30,7 +32,7 @@ namespace Avalonia /// The visual. /// The point in client coordinates. /// The point in screen coordinates. - public static Point PointToScreen(this IVisual visual, Point point) + public static PixelPoint PointToScreen(this IVisual visual, Point point) { var p = GetRootAndPosition(visual); return p.Item1.PointToScreen(point + p.Item2); diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj new file mode 100644 index 0000000000..087ba017ae --- /dev/null +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + true + + + + + + + + + diff --git a/src/Avalonia.X11/Keysyms.cs b/src/Avalonia.X11/Keysyms.cs new file mode 100644 index 0000000000..651a06f574 --- /dev/null +++ b/src/Avalonia.X11/Keysyms.cs @@ -0,0 +1,2108 @@ +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +namespace Avalonia.X11 +{ + enum X11Key + { + VoidSymbol = 0xffffff /* Void symbol */, + BackSpace = 0xff08 /* Back space, back char */, + Tab = 0xff09, + Linefeed = 0xff0a /* Linefeed, LF */, + Clear = 0xff0b, + Return = 0xff0d /* Return, enter */, + Pause = 0xff13 /* Pause, hold */, + Scroll_Lock = 0xff14, + Sys_Req = 0xff15, + Escape = 0xff1b, + Delete = 0xffff /* Delete, rubout */, + Multi_key = 0xff20 /* Multi-key character compose */, + Codeinput = 0xff37, + SingleCandidate = 0xff3c, + MultipleCandidate = 0xff3d, + PreviousCandidate = 0xff3e, + Kanji = 0xff21 /* Kanji, Kanji convert */, + Muhenkan = 0xff22 /* Cancel Conversion */, + Henkan_Mode = 0xff23 /* Start/Stop Conversion */, + Henkan = 0xff23 /* Alias for Henkan_Mode */, + Romaji = 0xff24 /* to Romaji */, + Hiragana = 0xff25 /* to Hiragana */, + Katakana = 0xff26 /* to Katakana */, + Hiragana_Katakana = 0xff27 /* Hiragana/Katakana toggle */, + Zenkaku = 0xff28 /* to Zenkaku */, + Hankaku = 0xff29 /* to Hankaku */, + Zenkaku_Hankaku = 0xff2a /* Zenkaku/Hankaku toggle */, + Touroku = 0xff2b /* Add to Dictionary */, + Massyo = 0xff2c /* Delete from Dictionary */, + Kana_Lock = 0xff2d /* Kana Lock */, + Kana_Shift = 0xff2e /* Kana Shift */, + Eisu_Shift = 0xff2f /* Alphanumeric Shift */, + Eisu_toggle = 0xff30 /* Alphanumeric toggle */, + Kanji_Bangou = 0xff37 /* Codeinput */, + Zen_Koho = 0xff3d /* Multiple/All Candidate(s) */, + Mae_Koho = 0xff3e /* Previous Candidate */, + Home = 0xff50, + Left = 0xff51 /* Move left, left arrow */, + Up = 0xff52 /* Move up, up arrow */, + Right = 0xff53 /* Move right, right arrow */, + Down = 0xff54 /* Move down, down arrow */, + Prior = 0xff55 /* Prior, previous */, + Page_Up = 0xff55, + Next = 0xff56 /* Next */, + Page_Down = 0xff56, + End = 0xff57 /* EOL */, + Begin = 0xff58 /* BOL */, + Select = 0xff60 /* Select, mark */, + Print = 0xff61, + Execute = 0xff62 /* Execute, run, do */, + Insert = 0xff63 /* Insert, insert here */, + Undo = 0xff65, + Redo = 0xff66 /* Redo, again */, + Menu = 0xff67, + Find = 0xff68 /* Find, search */, + Cancel = 0xff69 /* Cancel, stop, abort, exit */, + Help = 0xff6a /* Help */, + Break = 0xff6b, + Mode_switch = 0xff7e /* Character set switch */, + script_switch = 0xff7e /* Alias for mode_switch */, + Num_Lock = 0xff7f, + KP_Space = 0xff80 /* Space */, + KP_Tab = 0xff89, + KP_Enter = 0xff8d /* Enter */, + KP_F1 = 0xff91 /* PF1, KP_A, ... */, + KP_F2 = 0xff92, + KP_F3 = 0xff93, + KP_F4 = 0xff94, + KP_Home = 0xff95, + KP_Left = 0xff96, + KP_Up = 0xff97, + KP_Right = 0xff98, + KP_Down = 0xff99, + KP_Prior = 0xff9a, + KP_Page_Up = 0xff9a, + KP_Next = 0xff9b, + KP_Page_Down = 0xff9b, + KP_End = 0xff9c, + KP_Begin = 0xff9d, + KP_Insert = 0xff9e, + KP_Delete = 0xff9f, + KP_Equal = 0xffbd /* Equals */, + KP_Multiply = 0xffaa, + KP_Add = 0xffab, + KP_Separator = 0xffac /* Separator, often comma */, + KP_Subtract = 0xffad, + KP_Decimal = 0xffae, + KP_Divide = 0xffaf, + KP_0 = 0xffb0, + KP_1 = 0xffb1, + KP_2 = 0xffb2, + KP_3 = 0xffb3, + KP_4 = 0xffb4, + KP_5 = 0xffb5, + KP_6 = 0xffb6, + KP_7 = 0xffb7, + KP_8 = 0xffb8, + KP_9 = 0xffb9, + F1 = 0xffbe, + F2 = 0xffbf, + F3 = 0xffc0, + F4 = 0xffc1, + F5 = 0xffc2, + F6 = 0xffc3, + F7 = 0xffc4, + F8 = 0xffc5, + F9 = 0xffc6, + F10 = 0xffc7, + F11 = 0xffc8, + L1 = 0xffc8, + F12 = 0xffc9, + L2 = 0xffc9, + F13 = 0xffca, + L3 = 0xffca, + F14 = 0xffcb, + L4 = 0xffcb, + F15 = 0xffcc, + L5 = 0xffcc, + F16 = 0xffcd, + L6 = 0xffcd, + F17 = 0xffce, + L7 = 0xffce, + F18 = 0xffcf, + L8 = 0xffcf, + F19 = 0xffd0, + L9 = 0xffd0, + F20 = 0xffd1, + L10 = 0xffd1, + F21 = 0xffd2, + R1 = 0xffd2, + F22 = 0xffd3, + R2 = 0xffd3, + F23 = 0xffd4, + R3 = 0xffd4, + F24 = 0xffd5, + R4 = 0xffd5, + F25 = 0xffd6, + R5 = 0xffd6, + F26 = 0xffd7, + R6 = 0xffd7, + F27 = 0xffd8, + R7 = 0xffd8, + F28 = 0xffd9, + R8 = 0xffd9, + F29 = 0xffda, + R9 = 0xffda, + F30 = 0xffdb, + R10 = 0xffdb, + F31 = 0xffdc, + R11 = 0xffdc, + F32 = 0xffdd, + R12 = 0xffdd, + F33 = 0xffde, + R13 = 0xffde, + F34 = 0xffdf, + R14 = 0xffdf, + F35 = 0xffe0, + R15 = 0xffe0, + Shift_L = 0xffe1 /* Left shift */, + Shift_R = 0xffe2 /* Right shift */, + Control_L = 0xffe3 /* Left control */, + Control_R = 0xffe4 /* Right control */, + Caps_Lock = 0xffe5 /* Caps lock */, + Shift_Lock = 0xffe6 /* Shift lock */, + Meta_L = 0xffe7 /* Left meta */, + Meta_R = 0xffe8 /* Right meta */, + Alt_L = 0xffe9 /* Left alt */, + Alt_R = 0xffea /* Right alt */, + Super_L = 0xffeb /* Left super */, + Super_R = 0xffec /* Right super */, + Hyper_L = 0xffed /* Left hyper */, + Hyper_R = 0xffee /* Right hyper */, + ISO_Lock = 0xfe01, + ISO_Level2_Latch = 0xfe02, + ISO_Level3_Shift = 0xfe03, + ISO_Level3_Latch = 0xfe04, + ISO_Level3_Lock = 0xfe05, + ISO_Level5_Shift = 0xfe11, + ISO_Level5_Latch = 0xfe12, + ISO_Level5_Lock = 0xfe13, + ISO_Group_Shift = 0xff7e /* Alias for mode_switch */, + ISO_Group_Latch = 0xfe06, + ISO_Group_Lock = 0xfe07, + ISO_Next_Group = 0xfe08, + ISO_Next_Group_Lock = 0xfe09, + ISO_Prev_Group = 0xfe0a, + ISO_Prev_Group_Lock = 0xfe0b, + ISO_First_Group = 0xfe0c, + ISO_First_Group_Lock = 0xfe0d, + ISO_Last_Group = 0xfe0e, + ISO_Last_Group_Lock = 0xfe0f, + ISO_Left_Tab = 0xfe20, + ISO_Move_Line_Up = 0xfe21, + ISO_Move_Line_Down = 0xfe22, + ISO_Partial_Line_Up = 0xfe23, + ISO_Partial_Line_Down = 0xfe24, + ISO_Partial_Space_Left = 0xfe25, + ISO_Partial_Space_Right = 0xfe26, + ISO_Set_Margin_Left = 0xfe27, + ISO_Set_Margin_Right = 0xfe28, + ISO_Release_Margin_Left = 0xfe29, + ISO_Release_Margin_Right = 0xfe2a, + ISO_Release_Both_Margins = 0xfe2b, + ISO_Fast_Cursor_Left = 0xfe2c, + ISO_Fast_Cursor_Right = 0xfe2d, + ISO_Fast_Cursor_Up = 0xfe2e, + ISO_Fast_Cursor_Down = 0xfe2f, + ISO_Continuous_Underline = 0xfe30, + ISO_Discontinuous_Underline = 0xfe31, + ISO_Emphasize = 0xfe32, + ISO_Center_Object = 0xfe33, + ISO_Enter = 0xfe34, + dead_grave = 0xfe50, + dead_acute = 0xfe51, + dead_circumflex = 0xfe52, + dead_tilde = 0xfe53, + dead_perispomeni = 0xfe53 /* alias for dead_tilde */, + dead_macron = 0xfe54, + dead_breve = 0xfe55, + dead_abovedot = 0xfe56, + dead_diaeresis = 0xfe57, + dead_abovering = 0xfe58, + dead_doubleacute = 0xfe59, + dead_caron = 0xfe5a, + dead_cedilla = 0xfe5b, + dead_ogonek = 0xfe5c, + dead_iota = 0xfe5d, + dead_voiced_sound = 0xfe5e, + dead_semivoiced_sound = 0xfe5f, + dead_belowdot = 0xfe60, + dead_hook = 0xfe61, + dead_horn = 0xfe62, + dead_stroke = 0xfe63, + dead_abovecomma = 0xfe64, + dead_psili = 0xfe64 /* alias for dead_abovecomma */, + dead_abovereversedcomma = 0xfe65, + dead_dasia = 0xfe65 /* alias for dead_abovereversedcomma */, + dead_doublegrave = 0xfe66, + dead_belowring = 0xfe67, + dead_belowmacron = 0xfe68, + dead_belowcircumflex = 0xfe69, + dead_belowtilde = 0xfe6a, + dead_belowbreve = 0xfe6b, + dead_belowdiaeresis = 0xfe6c, + dead_invertedbreve = 0xfe6d, + dead_belowcomma = 0xfe6e, + dead_currency = 0xfe6f, + dead_lowline = 0xfe90, + dead_aboveverticalline = 0xfe91, + dead_belowverticalline = 0xfe92, + dead_longsolidusoverlay = 0xfe93, + dead_a = 0xfe80, + dead_A = 0xfe81, + dead_e = 0xfe82, + dead_E = 0xfe83, + dead_i = 0xfe84, + dead_I = 0xfe85, + dead_o = 0xfe86, + dead_O = 0xfe87, + dead_u = 0xfe88, + dead_U = 0xfe89, + dead_small_schwa = 0xfe8a, + dead_capital_schwa = 0xfe8b, + dead_greek = 0xfe8c, + First_Virtual_Screen = 0xfed0, + Prev_Virtual_Screen = 0xfed1, + Next_Virtual_Screen = 0xfed2, + Last_Virtual_Screen = 0xfed4, + Terminate_Server = 0xfed5, + AccessX_Enable = 0xfe70, + AccessX_Feedback_Enable = 0xfe71, + RepeatKeys_Enable = 0xfe72, + SlowKeys_Enable = 0xfe73, + BounceKeys_Enable = 0xfe74, + StickyKeys_Enable = 0xfe75, + MouseKeys_Enable = 0xfe76, + MouseKeys_Accel_Enable = 0xfe77, + Overlay1_Enable = 0xfe78, + Overlay2_Enable = 0xfe79, + AudibleBell_Enable = 0xfe7a, + Pointer_Left = 0xfee0, + Pointer_Right = 0xfee1, + Pointer_Up = 0xfee2, + Pointer_Down = 0xfee3, + Pointer_UpLeft = 0xfee4, + Pointer_UpRight = 0xfee5, + Pointer_DownLeft = 0xfee6, + Pointer_DownRight = 0xfee7, + Pointer_Button_Dflt = 0xfee8, + Pointer_Button1 = 0xfee9, + Pointer_Button2 = 0xfeea, + Pointer_Button3 = 0xfeeb, + Pointer_Button4 = 0xfeec, + Pointer_Button5 = 0xfeed, + Pointer_DblClick_Dflt = 0xfeee, + Pointer_DblClick1 = 0xfeef, + Pointer_DblClick2 = 0xfef0, + Pointer_DblClick3 = 0xfef1, + Pointer_DblClick4 = 0xfef2, + Pointer_DblClick5 = 0xfef3, + Pointer_Drag_Dflt = 0xfef4, + Pointer_Drag1 = 0xfef5, + Pointer_Drag2 = 0xfef6, + Pointer_Drag3 = 0xfef7, + Pointer_Drag4 = 0xfef8, + Pointer_Drag5 = 0xfefd, + Pointer_EnableKeys = 0xfef9, + Pointer_Accelerate = 0xfefa, + Pointer_DfltBtnNext = 0xfefb, + Pointer_DfltBtnPrev = 0xfefc, + ch = 0xfea0, + Ch = 0xfea1, + CH = 0xfea2, + c_h = 0xfea3, + C_h = 0xfea4, + C_H = 0xfea5, + XK_3270_Duplicate = 0xfd01, + XK_3270_FieldMark = 0xfd02, + XK_3270_Right2 = 0xfd03, + XK_3270_Left2 = 0xfd04, + XK_3270_BackTab = 0xfd05, + XK_3270_EraseEOF = 0xfd06, + XK_3270_EraseInput = 0xfd07, + XK_3270_Reset = 0xfd08, + XK_3270_Quit = 0xfd09, + XK_3270_PA1 = 0xfd0a, + XK_3270_PA2 = 0xfd0b, + XK_3270_PA3 = 0xfd0c, + XK_3270_Test = 0xfd0d, + XK_3270_Attn = 0xfd0e, + XK_3270_CursorBlink = 0xfd0f, + XK_3270_AltCursor = 0xfd10, + XK_3270_KeyClick = 0xfd11, + XK_3270_Jump = 0xfd12, + XK_3270_Ident = 0xfd13, + XK_3270_Rule = 0xfd14, + XK_3270_Copy = 0xfd15, + XK_3270_Play = 0xfd16, + XK_3270_Setup = 0xfd17, + XK_3270_Record = 0xfd18, + XK_3270_ChangeScreen = 0xfd19, + XK_3270_DeleteWord = 0xfd1a, + XK_3270_ExSelect = 0xfd1b, + XK_3270_CursorSelect = 0xfd1c, + XK_3270_PrintScreen = 0xfd1d, + XK_3270_Enter = 0xfd1e, + space = 0x0020 /* U+0020 SPACE */, + exclam = 0x0021 /* U+0021 EXCLAMATION MARK */, + quotedbl = 0x0022 /* U+0022 QUOTATION MARK */, + numbersign = 0x0023 /* U+0023 NUMBER SIGN */, + dollar = 0x0024 /* U+0024 DOLLAR SIGN */, + percent = 0x0025 /* U+0025 PERCENT SIGN */, + ampersand = 0x0026 /* U+0026 AMPERSAND */, + apostrophe = 0x0027 /* U+0027 APOSTROPHE */, + quoteright = 0x0027 /* deprecated */, + parenleft = 0x0028 /* U+0028 LEFT PARENTHESIS */, + parenright = 0x0029 /* U+0029 RIGHT PARENTHESIS */, + asterisk = 0x002a /* U+002A ASTERISK */, + plus = 0x002b /* U+002B PLUS SIGN */, + comma = 0x002c /* U+002C COMMA */, + minus = 0x002d /* U+002D HYPHEN-MINUS */, + period = 0x002e /* U+002E FULL STOP */, + slash = 0x002f /* U+002F SOLIDUS */, + XK_0 = 0x0030 /* U+0030 DIGIT ZERO */, + XK_1 = 0x0031 /* U+0031 DIGIT ONE */, + XK_2 = 0x0032 /* U+0032 DIGIT TWO */, + XK_3 = 0x0033 /* U+0033 DIGIT THREE */, + XK_4 = 0x0034 /* U+0034 DIGIT FOUR */, + XK_5 = 0x0035 /* U+0035 DIGIT FIVE */, + XK_6 = 0x0036 /* U+0036 DIGIT SIX */, + XK_7 = 0x0037 /* U+0037 DIGIT SEVEN */, + XK_8 = 0x0038 /* U+0038 DIGIT EIGHT */, + XK_9 = 0x0039 /* U+0039 DIGIT NINE */, + colon = 0x003a /* U+003A COLON */, + semicolon = 0x003b /* U+003B SEMICOLON */, + less = 0x003c /* U+003C LESS-THAN SIGN */, + equal = 0x003d /* U+003D EQUALS SIGN */, + greater = 0x003e /* U+003E GREATER-THAN SIGN */, + question = 0x003f /* U+003F QUESTION MARK */, + at = 0x0040 /* U+0040 COMMERCIAL AT */, + A = 0x0041 /* U+0041 LATIN CAPITAL LETTER A */, + B = 0x0042 /* U+0042 LATIN CAPITAL LETTER B */, + C = 0x0043 /* U+0043 LATIN CAPITAL LETTER C */, + D = 0x0044 /* U+0044 LATIN CAPITAL LETTER D */, + E = 0x0045 /* U+0045 LATIN CAPITAL LETTER E */, + F = 0x0046 /* U+0046 LATIN CAPITAL LETTER F */, + G = 0x0047 /* U+0047 LATIN CAPITAL LETTER G */, + H = 0x0048 /* U+0048 LATIN CAPITAL LETTER H */, + I = 0x0049 /* U+0049 LATIN CAPITAL LETTER I */, + J = 0x004a /* U+004A LATIN CAPITAL LETTER J */, + K = 0x004b /* U+004B LATIN CAPITAL LETTER K */, + L = 0x004c /* U+004C LATIN CAPITAL LETTER L */, + M = 0x004d /* U+004D LATIN CAPITAL LETTER M */, + N = 0x004e /* U+004E LATIN CAPITAL LETTER N */, + O = 0x004f /* U+004F LATIN CAPITAL LETTER O */, + P = 0x0050 /* U+0050 LATIN CAPITAL LETTER P */, + Q = 0x0051 /* U+0051 LATIN CAPITAL LETTER Q */, + R = 0x0052 /* U+0052 LATIN CAPITAL LETTER R */, + S = 0x0053 /* U+0053 LATIN CAPITAL LETTER S */, + T = 0x0054 /* U+0054 LATIN CAPITAL LETTER T */, + U = 0x0055 /* U+0055 LATIN CAPITAL LETTER U */, + V = 0x0056 /* U+0056 LATIN CAPITAL LETTER V */, + W = 0x0057 /* U+0057 LATIN CAPITAL LETTER W */, + X = 0x0058 /* U+0058 LATIN CAPITAL LETTER X */, + Y = 0x0059 /* U+0059 LATIN CAPITAL LETTER Y */, + Z = 0x005a /* U+005A LATIN CAPITAL LETTER Z */, + bracketleft = 0x005b /* U+005B LEFT SQUARE BRACKET */, + backslash = 0x005c /* U+005C REVERSE SOLIDUS */, + bracketright = 0x005d /* U+005D RIGHT SQUARE BRACKET */, + asciicircum = 0x005e /* U+005E CIRCUMFLEX ACCENT */, + underscore = 0x005f /* U+005F LOW LINE */, + grave = 0x0060 /* U+0060 GRAVE ACCENT */, + quoteleft = 0x0060 /* deprecated */, + a = 0x0061 /* U+0061 LATIN SMALL LETTER A */, + b = 0x0062 /* U+0062 LATIN SMALL LETTER B */, + c = 0x0063 /* U+0063 LATIN SMALL LETTER C */, + d = 0x0064 /* U+0064 LATIN SMALL LETTER D */, + e = 0x0065 /* U+0065 LATIN SMALL LETTER E */, + f = 0x0066 /* U+0066 LATIN SMALL LETTER F */, + g = 0x0067 /* U+0067 LATIN SMALL LETTER G */, + h = 0x0068 /* U+0068 LATIN SMALL LETTER H */, + i = 0x0069 /* U+0069 LATIN SMALL LETTER I */, + j = 0x006a /* U+006A LATIN SMALL LETTER J */, + k = 0x006b /* U+006B LATIN SMALL LETTER K */, + l = 0x006c /* U+006C LATIN SMALL LETTER L */, + m = 0x006d /* U+006D LATIN SMALL LETTER M */, + n = 0x006e /* U+006E LATIN SMALL LETTER N */, + o = 0x006f /* U+006F LATIN SMALL LETTER O */, + p = 0x0070 /* U+0070 LATIN SMALL LETTER P */, + q = 0x0071 /* U+0071 LATIN SMALL LETTER Q */, + r = 0x0072 /* U+0072 LATIN SMALL LETTER R */, + s = 0x0073 /* U+0073 LATIN SMALL LETTER S */, + t = 0x0074 /* U+0074 LATIN SMALL LETTER T */, + u = 0x0075 /* U+0075 LATIN SMALL LETTER U */, + v = 0x0076 /* U+0076 LATIN SMALL LETTER V */, + w = 0x0077 /* U+0077 LATIN SMALL LETTER W */, + x = 0x0078 /* U+0078 LATIN SMALL LETTER X */, + y = 0x0079 /* U+0079 LATIN SMALL LETTER Y */, + z = 0x007a /* U+007A LATIN SMALL LETTER Z */, + braceleft = 0x007b /* U+007B LEFT CURLY BRACKET */, + bar = 0x007c /* U+007C VERTICAL LINE */, + braceright = 0x007d /* U+007D RIGHT CURLY BRACKET */, + asciitilde = 0x007e /* U+007E TILDE */, + nobreakspace = 0x00a0 /* U+00A0 NO-BREAK SPACE */, + exclamdown = 0x00a1 /* U+00A1 INVERTED EXCLAMATION MARK */, + cent = 0x00a2 /* U+00A2 CENT SIGN */, + sterling = 0x00a3 /* U+00A3 POUND SIGN */, + currency = 0x00a4 /* U+00A4 CURRENCY SIGN */, + yen = 0x00a5 /* U+00A5 YEN SIGN */, + brokenbar = 0x00a6 /* U+00A6 BROKEN BAR */, + section = 0x00a7 /* U+00A7 SECTION SIGN */, + diaeresis = 0x00a8 /* U+00A8 DIAERESIS */, + copyright = 0x00a9 /* U+00A9 COPYRIGHT SIGN */, + ordfeminine = 0x00aa /* U+00AA FEMININE ORDINAL INDICATOR */, + guillemotleft = 0x00ab /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */, + notsign = 0x00ac /* U+00AC NOT SIGN */, + hyphen = 0x00ad /* U+00AD SOFT HYPHEN */, + registered = 0x00ae /* U+00AE REGISTERED SIGN */, + macron = 0x00af /* U+00AF MACRON */, + degree = 0x00b0 /* U+00B0 DEGREE SIGN */, + plusminus = 0x00b1 /* U+00B1 PLUS-MINUS SIGN */, + twosuperior = 0x00b2 /* U+00B2 SUPERSCRIPT TWO */, + threesuperior = 0x00b3 /* U+00B3 SUPERSCRIPT THREE */, + acute = 0x00b4 /* U+00B4 ACUTE ACCENT */, + mu = 0x00b5 /* U+00B5 MICRO SIGN */, + paragraph = 0x00b6 /* U+00B6 PILCROW SIGN */, + periodcentered = 0x00b7 /* U+00B7 MIDDLE DOT */, + cedilla = 0x00b8 /* U+00B8 CEDILLA */, + onesuperior = 0x00b9 /* U+00B9 SUPERSCRIPT ONE */, + masculine = 0x00ba /* U+00BA MASCULINE ORDINAL INDICATOR */, + guillemotright = 0x00bb /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */, + onequarter = 0x00bc /* U+00BC VULGAR FRACTION ONE QUARTER */, + onehalf = 0x00bd /* U+00BD VULGAR FRACTION ONE HALF */, + threequarters = 0x00be /* U+00BE VULGAR FRACTION THREE QUARTERS */, + questiondown = 0x00bf /* U+00BF INVERTED QUESTION MARK */, + Agrave = 0x00c0 /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */, + Aacute = 0x00c1 /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */, + Acircumflex = 0x00c2 /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */, + Atilde = 0x00c3 /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */, + Adiaeresis = 0x00c4 /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */, + Aring = 0x00c5 /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */, + AE = 0x00c6 /* U+00C6 LATIN CAPITAL LETTER AE */, + Ccedilla = 0x00c7 /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */, + Egrave = 0x00c8 /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */, + Eacute = 0x00c9 /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */, + Ecircumflex = 0x00ca /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */, + Ediaeresis = 0x00cb /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */, + Igrave = 0x00cc /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */, + Iacute = 0x00cd /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */, + Icircumflex = 0x00ce /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */, + Idiaeresis = 0x00cf /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */, + ETH = 0x00d0 /* U+00D0 LATIN CAPITAL LETTER ETH */, + Eth = 0x00d0 /* deprecated */, + Ntilde = 0x00d1 /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */, + Ograve = 0x00d2 /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */, + Oacute = 0x00d3 /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */, + Ocircumflex = 0x00d4 /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */, + Otilde = 0x00d5 /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */, + Odiaeresis = 0x00d6 /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */, + multiply = 0x00d7 /* U+00D7 MULTIPLICATION SIGN */, + Oslash = 0x00d8 /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */, + Ooblique = 0x00d8 /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */, + Ugrave = 0x00d9 /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */, + Uacute = 0x00da /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */, + Ucircumflex = 0x00db /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */, + Udiaeresis = 0x00dc /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */, + Yacute = 0x00dd /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */, + THORN = 0x00de /* U+00DE LATIN CAPITAL LETTER THORN */, + Thorn = 0x00de /* deprecated */, + ssharp = 0x00df /* U+00DF LATIN SMALL LETTER SHARP S */, + agrave = 0x00e0 /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */, + aacute = 0x00e1 /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */, + acircumflex = 0x00e2 /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */, + atilde = 0x00e3 /* U+00E3 LATIN SMALL LETTER A WITH TILDE */, + adiaeresis = 0x00e4 /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */, + aring = 0x00e5 /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */, + ae = 0x00e6 /* U+00E6 LATIN SMALL LETTER AE */, + ccedilla = 0x00e7 /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */, + egrave = 0x00e8 /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */, + eacute = 0x00e9 /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */, + ecircumflex = 0x00ea /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */, + ediaeresis = 0x00eb /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */, + igrave = 0x00ec /* U+00EC LATIN SMALL LETTER I WITH GRAVE */, + iacute = 0x00ed /* U+00ED LATIN SMALL LETTER I WITH ACUTE */, + icircumflex = 0x00ee /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */, + idiaeresis = 0x00ef /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */, + eth = 0x00f0 /* U+00F0 LATIN SMALL LETTER ETH */, + ntilde = 0x00f1 /* U+00F1 LATIN SMALL LETTER N WITH TILDE */, + ograve = 0x00f2 /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */, + oacute = 0x00f3 /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */, + ocircumflex = 0x00f4 /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */, + otilde = 0x00f5 /* U+00F5 LATIN SMALL LETTER O WITH TILDE */, + odiaeresis = 0x00f6 /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */, + division = 0x00f7 /* U+00F7 DIVISION SIGN */, + oslash = 0x00f8 /* U+00F8 LATIN SMALL LETTER O WITH STROKE */, + ooblique = 0x00f8 /* U+00F8 LATIN SMALL LETTER O WITH STROKE */, + ugrave = 0x00f9 /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */, + uacute = 0x00fa /* U+00FA LATIN SMALL LETTER U WITH ACUTE */, + ucircumflex = 0x00fb /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */, + udiaeresis = 0x00fc /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */, + yacute = 0x00fd /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */, + thorn = 0x00fe /* U+00FE LATIN SMALL LETTER THORN */, + ydiaeresis = 0x00ff /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */, + Aogonek = 0x01a1 /* U+0104 LATIN CAPITAL LETTER A WITH OGONEK */, + breve = 0x01a2 /* U+02D8 BREVE */, + Lstroke = 0x01a3 /* U+0141 LATIN CAPITAL LETTER L WITH STROKE */, + Lcaron = 0x01a5 /* U+013D LATIN CAPITAL LETTER L WITH CARON */, + Sacute = 0x01a6 /* U+015A LATIN CAPITAL LETTER S WITH ACUTE */, + Scaron = 0x01a9 /* U+0160 LATIN CAPITAL LETTER S WITH CARON */, + Scedilla = 0x01aa /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */, + Tcaron = 0x01ab /* U+0164 LATIN CAPITAL LETTER T WITH CARON */, + Zacute = 0x01ac /* U+0179 LATIN CAPITAL LETTER Z WITH ACUTE */, + Zcaron = 0x01ae /* U+017D LATIN CAPITAL LETTER Z WITH CARON */, + Zabovedot = 0x01af /* U+017B LATIN CAPITAL LETTER Z WITH DOT ABOVE */, + aogonek = 0x01b1 /* U+0105 LATIN SMALL LETTER A WITH OGONEK */, + ogonek = 0x01b2 /* U+02DB OGONEK */, + lstroke = 0x01b3 /* U+0142 LATIN SMALL LETTER L WITH STROKE */, + lcaron = 0x01b5 /* U+013E LATIN SMALL LETTER L WITH CARON */, + sacute = 0x01b6 /* U+015B LATIN SMALL LETTER S WITH ACUTE */, + caron = 0x01b7 /* U+02C7 CARON */, + scaron = 0x01b9 /* U+0161 LATIN SMALL LETTER S WITH CARON */, + scedilla = 0x01ba /* U+015F LATIN SMALL LETTER S WITH CEDILLA */, + tcaron = 0x01bb /* U+0165 LATIN SMALL LETTER T WITH CARON */, + zacute = 0x01bc /* U+017A LATIN SMALL LETTER Z WITH ACUTE */, + doubleacute = 0x01bd /* U+02DD DOUBLE ACUTE ACCENT */, + zcaron = 0x01be /* U+017E LATIN SMALL LETTER Z WITH CARON */, + zabovedot = 0x01bf /* U+017C LATIN SMALL LETTER Z WITH DOT ABOVE */, + Racute = 0x01c0 /* U+0154 LATIN CAPITAL LETTER R WITH ACUTE */, + Abreve = 0x01c3 /* U+0102 LATIN CAPITAL LETTER A WITH BREVE */, + Lacute = 0x01c5 /* U+0139 LATIN CAPITAL LETTER L WITH ACUTE */, + Cacute = 0x01c6 /* U+0106 LATIN CAPITAL LETTER C WITH ACUTE */, + Ccaron = 0x01c8 /* U+010C LATIN CAPITAL LETTER C WITH CARON */, + Eogonek = 0x01ca /* U+0118 LATIN CAPITAL LETTER E WITH OGONEK */, + Ecaron = 0x01cc /* U+011A LATIN CAPITAL LETTER E WITH CARON */, + Dcaron = 0x01cf /* U+010E LATIN CAPITAL LETTER D WITH CARON */, + Dstroke = 0x01d0 /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */, + Nacute = 0x01d1 /* U+0143 LATIN CAPITAL LETTER N WITH ACUTE */, + Ncaron = 0x01d2 /* U+0147 LATIN CAPITAL LETTER N WITH CARON */, + Odoubleacute = 0x01d5 /* U+0150 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */, + Rcaron = 0x01d8 /* U+0158 LATIN CAPITAL LETTER R WITH CARON */, + Uring = 0x01d9 /* U+016E LATIN CAPITAL LETTER U WITH RING ABOVE */, + Udoubleacute = 0x01db /* U+0170 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */, + Tcedilla = 0x01de /* U+0162 LATIN CAPITAL LETTER T WITH CEDILLA */, + racute = 0x01e0 /* U+0155 LATIN SMALL LETTER R WITH ACUTE */, + abreve = 0x01e3 /* U+0103 LATIN SMALL LETTER A WITH BREVE */, + lacute = 0x01e5 /* U+013A LATIN SMALL LETTER L WITH ACUTE */, + cacute = 0x01e6 /* U+0107 LATIN SMALL LETTER C WITH ACUTE */, + ccaron = 0x01e8 /* U+010D LATIN SMALL LETTER C WITH CARON */, + eogonek = 0x01ea /* U+0119 LATIN SMALL LETTER E WITH OGONEK */, + ecaron = 0x01ec /* U+011B LATIN SMALL LETTER E WITH CARON */, + dcaron = 0x01ef /* U+010F LATIN SMALL LETTER D WITH CARON */, + dstroke = 0x01f0 /* U+0111 LATIN SMALL LETTER D WITH STROKE */, + nacute = 0x01f1 /* U+0144 LATIN SMALL LETTER N WITH ACUTE */, + ncaron = 0x01f2 /* U+0148 LATIN SMALL LETTER N WITH CARON */, + odoubleacute = 0x01f5 /* U+0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE */, + rcaron = 0x01f8 /* U+0159 LATIN SMALL LETTER R WITH CARON */, + uring = 0x01f9 /* U+016F LATIN SMALL LETTER U WITH RING ABOVE */, + udoubleacute = 0x01fb /* U+0171 LATIN SMALL LETTER U WITH DOUBLE ACUTE */, + tcedilla = 0x01fe /* U+0163 LATIN SMALL LETTER T WITH CEDILLA */, + abovedot = 0x01ff /* U+02D9 DOT ABOVE */, + Hstroke = 0x02a1 /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */, + Hcircumflex = 0x02a6 /* U+0124 LATIN CAPITAL LETTER H WITH CIRCUMFLEX */, + Iabovedot = 0x02a9 /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */, + Gbreve = 0x02ab /* U+011E LATIN CAPITAL LETTER G WITH BREVE */, + Jcircumflex = 0x02ac /* U+0134 LATIN CAPITAL LETTER J WITH CIRCUMFLEX */, + hstroke = 0x02b1 /* U+0127 LATIN SMALL LETTER H WITH STROKE */, + hcircumflex = 0x02b6 /* U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX */, + idotless = 0x02b9 /* U+0131 LATIN SMALL LETTER DOTLESS I */, + gbreve = 0x02bb /* U+011F LATIN SMALL LETTER G WITH BREVE */, + jcircumflex = 0x02bc /* U+0135 LATIN SMALL LETTER J WITH CIRCUMFLEX */, + Cabovedot = 0x02c5 /* U+010A LATIN CAPITAL LETTER C WITH DOT ABOVE */, + Ccircumflex = 0x02c6 /* U+0108 LATIN CAPITAL LETTER C WITH CIRCUMFLEX */, + Gabovedot = 0x02d5 /* U+0120 LATIN CAPITAL LETTER G WITH DOT ABOVE */, + Gcircumflex = 0x02d8 /* U+011C LATIN CAPITAL LETTER G WITH CIRCUMFLEX */, + Ubreve = 0x02dd /* U+016C LATIN CAPITAL LETTER U WITH BREVE */, + Scircumflex = 0x02de /* U+015C LATIN CAPITAL LETTER S WITH CIRCUMFLEX */, + cabovedot = 0x02e5 /* U+010B LATIN SMALL LETTER C WITH DOT ABOVE */, + ccircumflex = 0x02e6 /* U+0109 LATIN SMALL LETTER C WITH CIRCUMFLEX */, + gabovedot = 0x02f5 /* U+0121 LATIN SMALL LETTER G WITH DOT ABOVE */, + gcircumflex = 0x02f8 /* U+011D LATIN SMALL LETTER G WITH CIRCUMFLEX */, + ubreve = 0x02fd /* U+016D LATIN SMALL LETTER U WITH BREVE */, + scircumflex = 0x02fe /* U+015D LATIN SMALL LETTER S WITH CIRCUMFLEX */, + kra = 0x03a2 /* U+0138 LATIN SMALL LETTER KRA */, + kappa = 0x03a2 /* deprecated */, + Rcedilla = 0x03a3 /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */, + Itilde = 0x03a5 /* U+0128 LATIN CAPITAL LETTER I WITH TILDE */, + Lcedilla = 0x03a6 /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */, + Emacron = 0x03aa /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */, + Gcedilla = 0x03ab /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */, + Tslash = 0x03ac /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */, + rcedilla = 0x03b3 /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */, + itilde = 0x03b5 /* U+0129 LATIN SMALL LETTER I WITH TILDE */, + lcedilla = 0x03b6 /* U+013C LATIN SMALL LETTER L WITH CEDILLA */, + emacron = 0x03ba /* U+0113 LATIN SMALL LETTER E WITH MACRON */, + gcedilla = 0x03bb /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */, + tslash = 0x03bc /* U+0167 LATIN SMALL LETTER T WITH STROKE */, + ENG = 0x03bd /* U+014A LATIN CAPITAL LETTER ENG */, + eng = 0x03bf /* U+014B LATIN SMALL LETTER ENG */, + Amacron = 0x03c0 /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */, + Iogonek = 0x03c7 /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */, + Eabovedot = 0x03cc /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */, + Imacron = 0x03cf /* U+012A LATIN CAPITAL LETTER I WITH MACRON */, + Ncedilla = 0x03d1 /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */, + Omacron = 0x03d2 /* U+014C LATIN CAPITAL LETTER O WITH MACRON */, + Kcedilla = 0x03d3 /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */, + Uogonek = 0x03d9 /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */, + Utilde = 0x03dd /* U+0168 LATIN CAPITAL LETTER U WITH TILDE */, + Umacron = 0x03de /* U+016A LATIN CAPITAL LETTER U WITH MACRON */, + amacron = 0x03e0 /* U+0101 LATIN SMALL LETTER A WITH MACRON */, + iogonek = 0x03e7 /* U+012F LATIN SMALL LETTER I WITH OGONEK */, + eabovedot = 0x03ec /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */, + imacron = 0x03ef /* U+012B LATIN SMALL LETTER I WITH MACRON */, + ncedilla = 0x03f1 /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */, + omacron = 0x03f2 /* U+014D LATIN SMALL LETTER O WITH MACRON */, + kcedilla = 0x03f3 /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */, + uogonek = 0x03f9 /* U+0173 LATIN SMALL LETTER U WITH OGONEK */, + utilde = 0x03fd /* U+0169 LATIN SMALL LETTER U WITH TILDE */, + umacron = 0x03fe /* U+016B LATIN SMALL LETTER U WITH MACRON */, + Wcircumflex = 0x1000174 /* U+0174 LATIN CAPITAL LETTER W WITH CIRCUMFLEX */, + wcircumflex = 0x1000175 /* U+0175 LATIN SMALL LETTER W WITH CIRCUMFLEX */, + Ycircumflex = 0x1000176 /* U+0176 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */, + ycircumflex = 0x1000177 /* U+0177 LATIN SMALL LETTER Y WITH CIRCUMFLEX */, + Babovedot = 0x1001e02 /* U+1E02 LATIN CAPITAL LETTER B WITH DOT ABOVE */, + babovedot = 0x1001e03 /* U+1E03 LATIN SMALL LETTER B WITH DOT ABOVE */, + Dabovedot = 0x1001e0a /* U+1E0A LATIN CAPITAL LETTER D WITH DOT ABOVE */, + dabovedot = 0x1001e0b /* U+1E0B LATIN SMALL LETTER D WITH DOT ABOVE */, + Fabovedot = 0x1001e1e /* U+1E1E LATIN CAPITAL LETTER F WITH DOT ABOVE */, + fabovedot = 0x1001e1f /* U+1E1F LATIN SMALL LETTER F WITH DOT ABOVE */, + Mabovedot = 0x1001e40 /* U+1E40 LATIN CAPITAL LETTER M WITH DOT ABOVE */, + mabovedot = 0x1001e41 /* U+1E41 LATIN SMALL LETTER M WITH DOT ABOVE */, + Pabovedot = 0x1001e56 /* U+1E56 LATIN CAPITAL LETTER P WITH DOT ABOVE */, + pabovedot = 0x1001e57 /* U+1E57 LATIN SMALL LETTER P WITH DOT ABOVE */, + Sabovedot = 0x1001e60 /* U+1E60 LATIN CAPITAL LETTER S WITH DOT ABOVE */, + sabovedot = 0x1001e61 /* U+1E61 LATIN SMALL LETTER S WITH DOT ABOVE */, + Tabovedot = 0x1001e6a /* U+1E6A LATIN CAPITAL LETTER T WITH DOT ABOVE */, + tabovedot = 0x1001e6b /* U+1E6B LATIN SMALL LETTER T WITH DOT ABOVE */, + Wgrave = 0x1001e80 /* U+1E80 LATIN CAPITAL LETTER W WITH GRAVE */, + wgrave = 0x1001e81 /* U+1E81 LATIN SMALL LETTER W WITH GRAVE */, + Wacute = 0x1001e82 /* U+1E82 LATIN CAPITAL LETTER W WITH ACUTE */, + wacute = 0x1001e83 /* U+1E83 LATIN SMALL LETTER W WITH ACUTE */, + Wdiaeresis = 0x1001e84 /* U+1E84 LATIN CAPITAL LETTER W WITH DIAERESIS */, + wdiaeresis = 0x1001e85 /* U+1E85 LATIN SMALL LETTER W WITH DIAERESIS */, + Ygrave = 0x1001ef2 /* U+1EF2 LATIN CAPITAL LETTER Y WITH GRAVE */, + ygrave = 0x1001ef3 /* U+1EF3 LATIN SMALL LETTER Y WITH GRAVE */, + OE = 0x13bc /* U+0152 LATIN CAPITAL LIGATURE OE */, + oe = 0x13bd /* U+0153 LATIN SMALL LIGATURE OE */, + Ydiaeresis = 0x13be /* U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS */, + overline = 0x047e /* U+203E OVERLINE */, + kana_fullstop = 0x04a1 /* U+3002 IDEOGRAPHIC FULL STOP */, + kana_openingbracket = 0x04a2 /* U+300C LEFT CORNER BRACKET */, + kana_closingbracket = 0x04a3 /* U+300D RIGHT CORNER BRACKET */, + kana_comma = 0x04a4 /* U+3001 IDEOGRAPHIC COMMA */, + kana_conjunctive = 0x04a5 /* U+30FB KATAKANA MIDDLE DOT */, + kana_middledot = 0x04a5 /* deprecated */, + kana_WO = 0x04a6 /* U+30F2 KATAKANA LETTER WO */, + kana_a = 0x04a7 /* U+30A1 KATAKANA LETTER SMALL A */, + kana_i = 0x04a8 /* U+30A3 KATAKANA LETTER SMALL I */, + kana_u = 0x04a9 /* U+30A5 KATAKANA LETTER SMALL U */, + kana_e = 0x04aa /* U+30A7 KATAKANA LETTER SMALL E */, + kana_o = 0x04ab /* U+30A9 KATAKANA LETTER SMALL O */, + kana_ya = 0x04ac /* U+30E3 KATAKANA LETTER SMALL YA */, + kana_yu = 0x04ad /* U+30E5 KATAKANA LETTER SMALL YU */, + kana_yo = 0x04ae /* U+30E7 KATAKANA LETTER SMALL YO */, + kana_tsu = 0x04af /* U+30C3 KATAKANA LETTER SMALL TU */, + kana_tu = 0x04af /* deprecated */, + prolongedsound = 0x04b0 /* U+30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK */, + kana_A = 0x04b1 /* U+30A2 KATAKANA LETTER A */, + kana_I = 0x04b2 /* U+30A4 KATAKANA LETTER I */, + kana_U = 0x04b3 /* U+30A6 KATAKANA LETTER U */, + kana_E = 0x04b4 /* U+30A8 KATAKANA LETTER E */, + kana_O = 0x04b5 /* U+30AA KATAKANA LETTER O */, + kana_KA = 0x04b6 /* U+30AB KATAKANA LETTER KA */, + kana_KI = 0x04b7 /* U+30AD KATAKANA LETTER KI */, + kana_KU = 0x04b8 /* U+30AF KATAKANA LETTER KU */, + kana_KE = 0x04b9 /* U+30B1 KATAKANA LETTER KE */, + kana_KO = 0x04ba /* U+30B3 KATAKANA LETTER KO */, + kana_SA = 0x04bb /* U+30B5 KATAKANA LETTER SA */, + kana_SHI = 0x04bc /* U+30B7 KATAKANA LETTER SI */, + kana_SU = 0x04bd /* U+30B9 KATAKANA LETTER SU */, + kana_SE = 0x04be /* U+30BB KATAKANA LETTER SE */, + kana_SO = 0x04bf /* U+30BD KATAKANA LETTER SO */, + kana_TA = 0x04c0 /* U+30BF KATAKANA LETTER TA */, + kana_CHI = 0x04c1 /* U+30C1 KATAKANA LETTER TI */, + kana_TI = 0x04c1 /* deprecated */, + kana_TSU = 0x04c2 /* U+30C4 KATAKANA LETTER TU */, + kana_TU = 0x04c2 /* deprecated */, + kana_TE = 0x04c3 /* U+30C6 KATAKANA LETTER TE */, + kana_TO = 0x04c4 /* U+30C8 KATAKANA LETTER TO */, + kana_NA = 0x04c5 /* U+30CA KATAKANA LETTER NA */, + kana_NI = 0x04c6 /* U+30CB KATAKANA LETTER NI */, + kana_NU = 0x04c7 /* U+30CC KATAKANA LETTER NU */, + kana_NE = 0x04c8 /* U+30CD KATAKANA LETTER NE */, + kana_NO = 0x04c9 /* U+30CE KATAKANA LETTER NO */, + kana_HA = 0x04ca /* U+30CF KATAKANA LETTER HA */, + kana_HI = 0x04cb /* U+30D2 KATAKANA LETTER HI */, + kana_FU = 0x04cc /* U+30D5 KATAKANA LETTER HU */, + kana_HU = 0x04cc /* deprecated */, + kana_HE = 0x04cd /* U+30D8 KATAKANA LETTER HE */, + kana_HO = 0x04ce /* U+30DB KATAKANA LETTER HO */, + kana_MA = 0x04cf /* U+30DE KATAKANA LETTER MA */, + kana_MI = 0x04d0 /* U+30DF KATAKANA LETTER MI */, + kana_MU = 0x04d1 /* U+30E0 KATAKANA LETTER MU */, + kana_ME = 0x04d2 /* U+30E1 KATAKANA LETTER ME */, + kana_MO = 0x04d3 /* U+30E2 KATAKANA LETTER MO */, + kana_YA = 0x04d4 /* U+30E4 KATAKANA LETTER YA */, + kana_YU = 0x04d5 /* U+30E6 KATAKANA LETTER YU */, + kana_YO = 0x04d6 /* U+30E8 KATAKANA LETTER YO */, + kana_RA = 0x04d7 /* U+30E9 KATAKANA LETTER RA */, + kana_RI = 0x04d8 /* U+30EA KATAKANA LETTER RI */, + kana_RU = 0x04d9 /* U+30EB KATAKANA LETTER RU */, + kana_RE = 0x04da /* U+30EC KATAKANA LETTER RE */, + kana_RO = 0x04db /* U+30ED KATAKANA LETTER RO */, + kana_WA = 0x04dc /* U+30EF KATAKANA LETTER WA */, + kana_N = 0x04dd /* U+30F3 KATAKANA LETTER N */, + voicedsound = 0x04de /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */, + semivoicedsound = 0x04df /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */, + kana_switch = 0xff7e /* Alias for mode_switch */, + Farsi_0 = 0x10006f0 /* U+06F0 EXTENDED ARABIC-INDIC DIGIT ZERO */, + Farsi_1 = 0x10006f1 /* U+06F1 EXTENDED ARABIC-INDIC DIGIT ONE */, + Farsi_2 = 0x10006f2 /* U+06F2 EXTENDED ARABIC-INDIC DIGIT TWO */, + Farsi_3 = 0x10006f3 /* U+06F3 EXTENDED ARABIC-INDIC DIGIT THREE */, + Farsi_4 = 0x10006f4 /* U+06F4 EXTENDED ARABIC-INDIC DIGIT FOUR */, + Farsi_5 = 0x10006f5 /* U+06F5 EXTENDED ARABIC-INDIC DIGIT FIVE */, + Farsi_6 = 0x10006f6 /* U+06F6 EXTENDED ARABIC-INDIC DIGIT SIX */, + Farsi_7 = 0x10006f7 /* U+06F7 EXTENDED ARABIC-INDIC DIGIT SEVEN */, + Farsi_8 = 0x10006f8 /* U+06F8 EXTENDED ARABIC-INDIC DIGIT EIGHT */, + Farsi_9 = 0x10006f9 /* U+06F9 EXTENDED ARABIC-INDIC DIGIT NINE */, + Arabic_percent = 0x100066a /* U+066A ARABIC PERCENT SIGN */, + Arabic_superscript_alef = 0x1000670 /* U+0670 ARABIC LETTER SUPERSCRIPT ALEF */, + Arabic_tteh = 0x1000679 /* U+0679 ARABIC LETTER TTEH */, + Arabic_peh = 0x100067e /* U+067E ARABIC LETTER PEH */, + Arabic_tcheh = 0x1000686 /* U+0686 ARABIC LETTER TCHEH */, + Arabic_ddal = 0x1000688 /* U+0688 ARABIC LETTER DDAL */, + Arabic_rreh = 0x1000691 /* U+0691 ARABIC LETTER RREH */, + Arabic_comma = 0x05ac /* U+060C ARABIC COMMA */, + Arabic_fullstop = 0x10006d4 /* U+06D4 ARABIC FULL STOP */, + Arabic_0 = 0x1000660 /* U+0660 ARABIC-INDIC DIGIT ZERO */, + Arabic_1 = 0x1000661 /* U+0661 ARABIC-INDIC DIGIT ONE */, + Arabic_2 = 0x1000662 /* U+0662 ARABIC-INDIC DIGIT TWO */, + Arabic_3 = 0x1000663 /* U+0663 ARABIC-INDIC DIGIT THREE */, + Arabic_4 = 0x1000664 /* U+0664 ARABIC-INDIC DIGIT FOUR */, + Arabic_5 = 0x1000665 /* U+0665 ARABIC-INDIC DIGIT FIVE */, + Arabic_6 = 0x1000666 /* U+0666 ARABIC-INDIC DIGIT SIX */, + Arabic_7 = 0x1000667 /* U+0667 ARABIC-INDIC DIGIT SEVEN */, + Arabic_8 = 0x1000668 /* U+0668 ARABIC-INDIC DIGIT EIGHT */, + Arabic_9 = 0x1000669 /* U+0669 ARABIC-INDIC DIGIT NINE */, + Arabic_semicolon = 0x05bb /* U+061B ARABIC SEMICOLON */, + Arabic_question_mark = 0x05bf /* U+061F ARABIC QUESTION MARK */, + Arabic_hamza = 0x05c1 /* U+0621 ARABIC LETTER HAMZA */, + Arabic_maddaonalef = 0x05c2 /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */, + Arabic_hamzaonalef = 0x05c3 /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */, + Arabic_hamzaonwaw = 0x05c4 /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */, + Arabic_hamzaunderalef = 0x05c5 /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */, + Arabic_hamzaonyeh = 0x05c6 /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */, + Arabic_alef = 0x05c7 /* U+0627 ARABIC LETTER ALEF */, + Arabic_beh = 0x05c8 /* U+0628 ARABIC LETTER BEH */, + Arabic_tehmarbuta = 0x05c9 /* U+0629 ARABIC LETTER TEH MARBUTA */, + Arabic_teh = 0x05ca /* U+062A ARABIC LETTER TEH */, + Arabic_theh = 0x05cb /* U+062B ARABIC LETTER THEH */, + Arabic_jeem = 0x05cc /* U+062C ARABIC LETTER JEEM */, + Arabic_hah = 0x05cd /* U+062D ARABIC LETTER HAH */, + Arabic_khah = 0x05ce /* U+062E ARABIC LETTER KHAH */, + Arabic_dal = 0x05cf /* U+062F ARABIC LETTER DAL */, + Arabic_thal = 0x05d0 /* U+0630 ARABIC LETTER THAL */, + Arabic_ra = 0x05d1 /* U+0631 ARABIC LETTER REH */, + Arabic_zain = 0x05d2 /* U+0632 ARABIC LETTER ZAIN */, + Arabic_seen = 0x05d3 /* U+0633 ARABIC LETTER SEEN */, + Arabic_sheen = 0x05d4 /* U+0634 ARABIC LETTER SHEEN */, + Arabic_sad = 0x05d5 /* U+0635 ARABIC LETTER SAD */, + Arabic_dad = 0x05d6 /* U+0636 ARABIC LETTER DAD */, + Arabic_tah = 0x05d7 /* U+0637 ARABIC LETTER TAH */, + Arabic_zah = 0x05d8 /* U+0638 ARABIC LETTER ZAH */, + Arabic_ain = 0x05d9 /* U+0639 ARABIC LETTER AIN */, + Arabic_ghain = 0x05da /* U+063A ARABIC LETTER GHAIN */, + Arabic_tatweel = 0x05e0 /* U+0640 ARABIC TATWEEL */, + Arabic_feh = 0x05e1 /* U+0641 ARABIC LETTER FEH */, + Arabic_qaf = 0x05e2 /* U+0642 ARABIC LETTER QAF */, + Arabic_kaf = 0x05e3 /* U+0643 ARABIC LETTER KAF */, + Arabic_lam = 0x05e4 /* U+0644 ARABIC LETTER LAM */, + Arabic_meem = 0x05e5 /* U+0645 ARABIC LETTER MEEM */, + Arabic_noon = 0x05e6 /* U+0646 ARABIC LETTER NOON */, + Arabic_ha = 0x05e7 /* U+0647 ARABIC LETTER HEH */, + Arabic_heh = 0x05e7 /* deprecated */, + Arabic_waw = 0x05e8 /* U+0648 ARABIC LETTER WAW */, + Arabic_alefmaksura = 0x05e9 /* U+0649 ARABIC LETTER ALEF MAKSURA */, + Arabic_yeh = 0x05ea /* U+064A ARABIC LETTER YEH */, + Arabic_fathatan = 0x05eb /* U+064B ARABIC FATHATAN */, + Arabic_dammatan = 0x05ec /* U+064C ARABIC DAMMATAN */, + Arabic_kasratan = 0x05ed /* U+064D ARABIC KASRATAN */, + Arabic_fatha = 0x05ee /* U+064E ARABIC FATHA */, + Arabic_damma = 0x05ef /* U+064F ARABIC DAMMA */, + Arabic_kasra = 0x05f0 /* U+0650 ARABIC KASRA */, + Arabic_shadda = 0x05f1 /* U+0651 ARABIC SHADDA */, + Arabic_sukun = 0x05f2 /* U+0652 ARABIC SUKUN */, + Arabic_madda_above = 0x1000653 /* U+0653 ARABIC MADDAH ABOVE */, + Arabic_hamza_above = 0x1000654 /* U+0654 ARABIC HAMZA ABOVE */, + Arabic_hamza_below = 0x1000655 /* U+0655 ARABIC HAMZA BELOW */, + Arabic_jeh = 0x1000698 /* U+0698 ARABIC LETTER JEH */, + Arabic_veh = 0x10006a4 /* U+06A4 ARABIC LETTER VEH */, + Arabic_keheh = 0x10006a9 /* U+06A9 ARABIC LETTER KEHEH */, + Arabic_gaf = 0x10006af /* U+06AF ARABIC LETTER GAF */, + Arabic_noon_ghunna = 0x10006ba /* U+06BA ARABIC LETTER NOON GHUNNA */, + Arabic_heh_doachashmee = 0x10006be /* U+06BE ARABIC LETTER HEH DOACHASHMEE */, + Farsi_yeh = 0x10006cc /* U+06CC ARABIC LETTER FARSI YEH */, + Arabic_farsi_yeh = 0x10006cc /* U+06CC ARABIC LETTER FARSI YEH */, + Arabic_yeh_baree = 0x10006d2 /* U+06D2 ARABIC LETTER YEH BARREE */, + Arabic_heh_goal = 0x10006c1 /* U+06C1 ARABIC LETTER HEH GOAL */, + Arabic_switch = 0xff7e /* Alias for mode_switch */, + Cyrillic_GHE_bar = 0x1000492 /* U+0492 CYRILLIC CAPITAL LETTER GHE WITH STROKE */, + Cyrillic_ghe_bar = 0x1000493 /* U+0493 CYRILLIC SMALL LETTER GHE WITH STROKE */, + Cyrillic_ZHE_descender = 0x1000496 /* U+0496 CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER */, + Cyrillic_zhe_descender = 0x1000497 /* U+0497 CYRILLIC SMALL LETTER ZHE WITH DESCENDER */, + Cyrillic_KA_descender = 0x100049a /* U+049A CYRILLIC CAPITAL LETTER KA WITH DESCENDER */, + Cyrillic_ka_descender = 0x100049b /* U+049B CYRILLIC SMALL LETTER KA WITH DESCENDER */, + Cyrillic_KA_vertstroke = 0x100049c /* U+049C CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE */, + Cyrillic_ka_vertstroke = 0x100049d /* U+049D CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE */, + Cyrillic_EN_descender = 0x10004a2 /* U+04A2 CYRILLIC CAPITAL LETTER EN WITH DESCENDER */, + Cyrillic_en_descender = 0x10004a3 /* U+04A3 CYRILLIC SMALL LETTER EN WITH DESCENDER */, + Cyrillic_U_straight = 0x10004ae /* U+04AE CYRILLIC CAPITAL LETTER STRAIGHT U */, + Cyrillic_u_straight = 0x10004af /* U+04AF CYRILLIC SMALL LETTER STRAIGHT U */, + Cyrillic_U_straight_bar = 0x10004b0 /* U+04B0 CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE */, + Cyrillic_u_straight_bar = 0x10004b1 /* U+04B1 CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE */, + Cyrillic_HA_descender = 0x10004b2 /* U+04B2 CYRILLIC CAPITAL LETTER HA WITH DESCENDER */, + Cyrillic_ha_descender = 0x10004b3 /* U+04B3 CYRILLIC SMALL LETTER HA WITH DESCENDER */, + Cyrillic_CHE_descender = 0x10004b6 /* U+04B6 CYRILLIC CAPITAL LETTER CHE WITH DESCENDER */, + Cyrillic_che_descender = 0x10004b7 /* U+04B7 CYRILLIC SMALL LETTER CHE WITH DESCENDER */, + Cyrillic_CHE_vertstroke = 0x10004b8 /* U+04B8 CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE */, + Cyrillic_che_vertstroke = 0x10004b9 /* U+04B9 CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE */, + Cyrillic_SHHA = 0x10004ba /* U+04BA CYRILLIC CAPITAL LETTER SHHA */, + Cyrillic_shha = 0x10004bb /* U+04BB CYRILLIC SMALL LETTER SHHA */, + Cyrillic_SCHWA = 0x10004d8 /* U+04D8 CYRILLIC CAPITAL LETTER SCHWA */, + Cyrillic_schwa = 0x10004d9 /* U+04D9 CYRILLIC SMALL LETTER SCHWA */, + Cyrillic_I_macron = 0x10004e2 /* U+04E2 CYRILLIC CAPITAL LETTER I WITH MACRON */, + Cyrillic_i_macron = 0x10004e3 /* U+04E3 CYRILLIC SMALL LETTER I WITH MACRON */, + Cyrillic_O_bar = 0x10004e8 /* U+04E8 CYRILLIC CAPITAL LETTER BARRED O */, + Cyrillic_o_bar = 0x10004e9 /* U+04E9 CYRILLIC SMALL LETTER BARRED O */, + Cyrillic_U_macron = 0x10004ee /* U+04EE CYRILLIC CAPITAL LETTER U WITH MACRON */, + Cyrillic_u_macron = 0x10004ef /* U+04EF CYRILLIC SMALL LETTER U WITH MACRON */, + Serbian_dje = 0x06a1 /* U+0452 CYRILLIC SMALL LETTER DJE */, + Macedonia_gje = 0x06a2 /* U+0453 CYRILLIC SMALL LETTER GJE */, + Cyrillic_io = 0x06a3 /* U+0451 CYRILLIC SMALL LETTER IO */, + Ukrainian_ie = 0x06a4 /* U+0454 CYRILLIC SMALL LETTER UKRAINIAN IE */, + Ukranian_je = 0x06a4 /* deprecated */, + Macedonia_dse = 0x06a5 /* U+0455 CYRILLIC SMALL LETTER DZE */, + Ukrainian_i = 0x06a6 /* U+0456 CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */, + Ukranian_i = 0x06a6 /* deprecated */, + Ukrainian_yi = 0x06a7 /* U+0457 CYRILLIC SMALL LETTER YI */, + Ukranian_yi = 0x06a7 /* deprecated */, + Cyrillic_je = 0x06a8 /* U+0458 CYRILLIC SMALL LETTER JE */, + Serbian_je = 0x06a8 /* deprecated */, + Cyrillic_lje = 0x06a9 /* U+0459 CYRILLIC SMALL LETTER LJE */, + Serbian_lje = 0x06a9 /* deprecated */, + Cyrillic_nje = 0x06aa /* U+045A CYRILLIC SMALL LETTER NJE */, + Serbian_nje = 0x06aa /* deprecated */, + Serbian_tshe = 0x06ab /* U+045B CYRILLIC SMALL LETTER TSHE */, + Macedonia_kje = 0x06ac /* U+045C CYRILLIC SMALL LETTER KJE */, + Ukrainian_ghe_with_upturn = 0x06ad /* U+0491 CYRILLIC SMALL LETTER GHE WITH UPTURN */, + Byelorussian_shortu = 0x06ae /* U+045E CYRILLIC SMALL LETTER SHORT U */, + Cyrillic_dzhe = 0x06af /* U+045F CYRILLIC SMALL LETTER DZHE */, + Serbian_dze = 0x06af /* deprecated */, + numerosign = 0x06b0 /* U+2116 NUMERO SIGN */, + Serbian_DJE = 0x06b1 /* U+0402 CYRILLIC CAPITAL LETTER DJE */, + Macedonia_GJE = 0x06b2 /* U+0403 CYRILLIC CAPITAL LETTER GJE */, + Cyrillic_IO = 0x06b3 /* U+0401 CYRILLIC CAPITAL LETTER IO */, + Ukrainian_IE = 0x06b4 /* U+0404 CYRILLIC CAPITAL LETTER UKRAINIAN IE */, + Ukranian_JE = 0x06b4 /* deprecated */, + Macedonia_DSE = 0x06b5 /* U+0405 CYRILLIC CAPITAL LETTER DZE */, + Ukrainian_I = 0x06b6 /* U+0406 CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */, + Ukranian_I = 0x06b6 /* deprecated */, + Ukrainian_YI = 0x06b7 /* U+0407 CYRILLIC CAPITAL LETTER YI */, + Ukranian_YI = 0x06b7 /* deprecated */, + Cyrillic_JE = 0x06b8 /* U+0408 CYRILLIC CAPITAL LETTER JE */, + Serbian_JE = 0x06b8 /* deprecated */, + Cyrillic_LJE = 0x06b9 /* U+0409 CYRILLIC CAPITAL LETTER LJE */, + Serbian_LJE = 0x06b9 /* deprecated */, + Cyrillic_NJE = 0x06ba /* U+040A CYRILLIC CAPITAL LETTER NJE */, + Serbian_NJE = 0x06ba /* deprecated */, + Serbian_TSHE = 0x06bb /* U+040B CYRILLIC CAPITAL LETTER TSHE */, + Macedonia_KJE = 0x06bc /* U+040C CYRILLIC CAPITAL LETTER KJE */, + Ukrainian_GHE_WITH_UPTURN = 0x06bd /* U+0490 CYRILLIC CAPITAL LETTER GHE WITH UPTURN */, + Byelorussian_SHORTU = 0x06be /* U+040E CYRILLIC CAPITAL LETTER SHORT U */, + Cyrillic_DZHE = 0x06bf /* U+040F CYRILLIC CAPITAL LETTER DZHE */, + Serbian_DZE = 0x06bf /* deprecated */, + Cyrillic_yu = 0x06c0 /* U+044E CYRILLIC SMALL LETTER YU */, + Cyrillic_a = 0x06c1 /* U+0430 CYRILLIC SMALL LETTER A */, + Cyrillic_be = 0x06c2 /* U+0431 CYRILLIC SMALL LETTER BE */, + Cyrillic_tse = 0x06c3 /* U+0446 CYRILLIC SMALL LETTER TSE */, + Cyrillic_de = 0x06c4 /* U+0434 CYRILLIC SMALL LETTER DE */, + Cyrillic_ie = 0x06c5 /* U+0435 CYRILLIC SMALL LETTER IE */, + Cyrillic_ef = 0x06c6 /* U+0444 CYRILLIC SMALL LETTER EF */, + Cyrillic_ghe = 0x06c7 /* U+0433 CYRILLIC SMALL LETTER GHE */, + Cyrillic_ha = 0x06c8 /* U+0445 CYRILLIC SMALL LETTER HA */, + Cyrillic_i = 0x06c9 /* U+0438 CYRILLIC SMALL LETTER I */, + Cyrillic_shorti = 0x06ca /* U+0439 CYRILLIC SMALL LETTER SHORT I */, + Cyrillic_ka = 0x06cb /* U+043A CYRILLIC SMALL LETTER KA */, + Cyrillic_el = 0x06cc /* U+043B CYRILLIC SMALL LETTER EL */, + Cyrillic_em = 0x06cd /* U+043C CYRILLIC SMALL LETTER EM */, + Cyrillic_en = 0x06ce /* U+043D CYRILLIC SMALL LETTER EN */, + Cyrillic_o = 0x06cf /* U+043E CYRILLIC SMALL LETTER O */, + Cyrillic_pe = 0x06d0 /* U+043F CYRILLIC SMALL LETTER PE */, + Cyrillic_ya = 0x06d1 /* U+044F CYRILLIC SMALL LETTER YA */, + Cyrillic_er = 0x06d2 /* U+0440 CYRILLIC SMALL LETTER ER */, + Cyrillic_es = 0x06d3 /* U+0441 CYRILLIC SMALL LETTER ES */, + Cyrillic_te = 0x06d4 /* U+0442 CYRILLIC SMALL LETTER TE */, + Cyrillic_u = 0x06d5 /* U+0443 CYRILLIC SMALL LETTER U */, + Cyrillic_zhe = 0x06d6 /* U+0436 CYRILLIC SMALL LETTER ZHE */, + Cyrillic_ve = 0x06d7 /* U+0432 CYRILLIC SMALL LETTER VE */, + Cyrillic_softsign = 0x06d8 /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */, + Cyrillic_yeru = 0x06d9 /* U+044B CYRILLIC SMALL LETTER YERU */, + Cyrillic_ze = 0x06da /* U+0437 CYRILLIC SMALL LETTER ZE */, + Cyrillic_sha = 0x06db /* U+0448 CYRILLIC SMALL LETTER SHA */, + Cyrillic_e = 0x06dc /* U+044D CYRILLIC SMALL LETTER E */, + Cyrillic_shcha = 0x06dd /* U+0449 CYRILLIC SMALL LETTER SHCHA */, + Cyrillic_che = 0x06de /* U+0447 CYRILLIC SMALL LETTER CHE */, + Cyrillic_hardsign = 0x06df /* U+044A CYRILLIC SMALL LETTER HARD SIGN */, + Cyrillic_YU = 0x06e0 /* U+042E CYRILLIC CAPITAL LETTER YU */, + Cyrillic_A = 0x06e1 /* U+0410 CYRILLIC CAPITAL LETTER A */, + Cyrillic_BE = 0x06e2 /* U+0411 CYRILLIC CAPITAL LETTER BE */, + Cyrillic_TSE = 0x06e3 /* U+0426 CYRILLIC CAPITAL LETTER TSE */, + Cyrillic_DE = 0x06e4 /* U+0414 CYRILLIC CAPITAL LETTER DE */, + Cyrillic_IE = 0x06e5 /* U+0415 CYRILLIC CAPITAL LETTER IE */, + Cyrillic_EF = 0x06e6 /* U+0424 CYRILLIC CAPITAL LETTER EF */, + Cyrillic_GHE = 0x06e7 /* U+0413 CYRILLIC CAPITAL LETTER GHE */, + Cyrillic_HA = 0x06e8 /* U+0425 CYRILLIC CAPITAL LETTER HA */, + Cyrillic_I = 0x06e9 /* U+0418 CYRILLIC CAPITAL LETTER I */, + Cyrillic_SHORTI = 0x06ea /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */, + Cyrillic_KA = 0x06eb /* U+041A CYRILLIC CAPITAL LETTER KA */, + Cyrillic_EL = 0x06ec /* U+041B CYRILLIC CAPITAL LETTER EL */, + Cyrillic_EM = 0x06ed /* U+041C CYRILLIC CAPITAL LETTER EM */, + Cyrillic_EN = 0x06ee /* U+041D CYRILLIC CAPITAL LETTER EN */, + Cyrillic_O = 0x06ef /* U+041E CYRILLIC CAPITAL LETTER O */, + Cyrillic_PE = 0x06f0 /* U+041F CYRILLIC CAPITAL LETTER PE */, + Cyrillic_YA = 0x06f1 /* U+042F CYRILLIC CAPITAL LETTER YA */, + Cyrillic_ER = 0x06f2 /* U+0420 CYRILLIC CAPITAL LETTER ER */, + Cyrillic_ES = 0x06f3 /* U+0421 CYRILLIC CAPITAL LETTER ES */, + Cyrillic_TE = 0x06f4 /* U+0422 CYRILLIC CAPITAL LETTER TE */, + Cyrillic_U = 0x06f5 /* U+0423 CYRILLIC CAPITAL LETTER U */, + Cyrillic_ZHE = 0x06f6 /* U+0416 CYRILLIC CAPITAL LETTER ZHE */, + Cyrillic_VE = 0x06f7 /* U+0412 CYRILLIC CAPITAL LETTER VE */, + Cyrillic_SOFTSIGN = 0x06f8 /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */, + Cyrillic_YERU = 0x06f9 /* U+042B CYRILLIC CAPITAL LETTER YERU */, + Cyrillic_ZE = 0x06fa /* U+0417 CYRILLIC CAPITAL LETTER ZE */, + Cyrillic_SHA = 0x06fb /* U+0428 CYRILLIC CAPITAL LETTER SHA */, + Cyrillic_E = 0x06fc /* U+042D CYRILLIC CAPITAL LETTER E */, + Cyrillic_SHCHA = 0x06fd /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */, + Cyrillic_CHE = 0x06fe /* U+0427 CYRILLIC CAPITAL LETTER CHE */, + Cyrillic_HARDSIGN = 0x06ff /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */, + Greek_ALPHAaccent = 0x07a1 /* U+0386 GREEK CAPITAL LETTER ALPHA WITH TONOS */, + Greek_EPSILONaccent = 0x07a2 /* U+0388 GREEK CAPITAL LETTER EPSILON WITH TONOS */, + Greek_ETAaccent = 0x07a3 /* U+0389 GREEK CAPITAL LETTER ETA WITH TONOS */, + Greek_IOTAaccent = 0x07a4 /* U+038A GREEK CAPITAL LETTER IOTA WITH TONOS */, + Greek_IOTAdieresis = 0x07a5 /* U+03AA GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */, + Greek_IOTAdiaeresis = 0x07a5 /* old typo */, + Greek_OMICRONaccent = 0x07a7 /* U+038C GREEK CAPITAL LETTER OMICRON WITH TONOS */, + Greek_UPSILONaccent = 0x07a8 /* U+038E GREEK CAPITAL LETTER UPSILON WITH TONOS */, + Greek_UPSILONdieresis = 0x07a9 /* U+03AB GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */, + Greek_OMEGAaccent = 0x07ab /* U+038F GREEK CAPITAL LETTER OMEGA WITH TONOS */, + Greek_accentdieresis = 0x07ae /* U+0385 GREEK DIALYTIKA TONOS */, + Greek_horizbar = 0x07af /* U+2015 HORIZONTAL BAR */, + Greek_alphaaccent = 0x07b1 /* U+03AC GREEK SMALL LETTER ALPHA WITH TONOS */, + Greek_epsilonaccent = 0x07b2 /* U+03AD GREEK SMALL LETTER EPSILON WITH TONOS */, + Greek_etaaccent = 0x07b3 /* U+03AE GREEK SMALL LETTER ETA WITH TONOS */, + Greek_iotaaccent = 0x07b4 /* U+03AF GREEK SMALL LETTER IOTA WITH TONOS */, + Greek_iotadieresis = 0x07b5 /* U+03CA GREEK SMALL LETTER IOTA WITH DIALYTIKA */, + Greek_iotaaccentdieresis = 0x07b6 /* U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */, + Greek_omicronaccent = 0x07b7 /* U+03CC GREEK SMALL LETTER OMICRON WITH TONOS */, + Greek_upsilonaccent = 0x07b8 /* U+03CD GREEK SMALL LETTER UPSILON WITH TONOS */, + Greek_upsilondieresis = 0x07b9 /* U+03CB GREEK SMALL LETTER UPSILON WITH DIALYTIKA */, + Greek_upsilonaccentdieresis = 0x07ba /* U+03B0 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */, + Greek_omegaaccent = 0x07bb /* U+03CE GREEK SMALL LETTER OMEGA WITH TONOS */, + Greek_ALPHA = 0x07c1 /* U+0391 GREEK CAPITAL LETTER ALPHA */, + Greek_BETA = 0x07c2 /* U+0392 GREEK CAPITAL LETTER BETA */, + Greek_GAMMA = 0x07c3 /* U+0393 GREEK CAPITAL LETTER GAMMA */, + Greek_DELTA = 0x07c4 /* U+0394 GREEK CAPITAL LETTER DELTA */, + Greek_EPSILON = 0x07c5 /* U+0395 GREEK CAPITAL LETTER EPSILON */, + Greek_ZETA = 0x07c6 /* U+0396 GREEK CAPITAL LETTER ZETA */, + Greek_ETA = 0x07c7 /* U+0397 GREEK CAPITAL LETTER ETA */, + Greek_THETA = 0x07c8 /* U+0398 GREEK CAPITAL LETTER THETA */, + Greek_IOTA = 0x07c9 /* U+0399 GREEK CAPITAL LETTER IOTA */, + Greek_KAPPA = 0x07ca /* U+039A GREEK CAPITAL LETTER KAPPA */, + Greek_LAMDA = 0x07cb /* U+039B GREEK CAPITAL LETTER LAMDA */, + Greek_LAMBDA = 0x07cb /* U+039B GREEK CAPITAL LETTER LAMDA */, + Greek_MU = 0x07cc /* U+039C GREEK CAPITAL LETTER MU */, + Greek_NU = 0x07cd /* U+039D GREEK CAPITAL LETTER NU */, + Greek_XI = 0x07ce /* U+039E GREEK CAPITAL LETTER XI */, + Greek_OMICRON = 0x07cf /* U+039F GREEK CAPITAL LETTER OMICRON */, + Greek_PI = 0x07d0 /* U+03A0 GREEK CAPITAL LETTER PI */, + Greek_RHO = 0x07d1 /* U+03A1 GREEK CAPITAL LETTER RHO */, + Greek_SIGMA = 0x07d2 /* U+03A3 GREEK CAPITAL LETTER SIGMA */, + Greek_TAU = 0x07d4 /* U+03A4 GREEK CAPITAL LETTER TAU */, + Greek_UPSILON = 0x07d5 /* U+03A5 GREEK CAPITAL LETTER UPSILON */, + Greek_PHI = 0x07d6 /* U+03A6 GREEK CAPITAL LETTER PHI */, + Greek_CHI = 0x07d7 /* U+03A7 GREEK CAPITAL LETTER CHI */, + Greek_PSI = 0x07d8 /* U+03A8 GREEK CAPITAL LETTER PSI */, + Greek_OMEGA = 0x07d9 /* U+03A9 GREEK CAPITAL LETTER OMEGA */, + Greek_alpha = 0x07e1 /* U+03B1 GREEK SMALL LETTER ALPHA */, + Greek_beta = 0x07e2 /* U+03B2 GREEK SMALL LETTER BETA */, + Greek_gamma = 0x07e3 /* U+03B3 GREEK SMALL LETTER GAMMA */, + Greek_delta = 0x07e4 /* U+03B4 GREEK SMALL LETTER DELTA */, + Greek_epsilon = 0x07e5 /* U+03B5 GREEK SMALL LETTER EPSILON */, + Greek_zeta = 0x07e6 /* U+03B6 GREEK SMALL LETTER ZETA */, + Greek_eta = 0x07e7 /* U+03B7 GREEK SMALL LETTER ETA */, + Greek_theta = 0x07e8 /* U+03B8 GREEK SMALL LETTER THETA */, + Greek_iota = 0x07e9 /* U+03B9 GREEK SMALL LETTER IOTA */, + Greek_kappa = 0x07ea /* U+03BA GREEK SMALL LETTER KAPPA */, + Greek_lamda = 0x07eb /* U+03BB GREEK SMALL LETTER LAMDA */, + Greek_lambda = 0x07eb /* U+03BB GREEK SMALL LETTER LAMDA */, + Greek_mu = 0x07ec /* U+03BC GREEK SMALL LETTER MU */, + Greek_nu = 0x07ed /* U+03BD GREEK SMALL LETTER NU */, + Greek_xi = 0x07ee /* U+03BE GREEK SMALL LETTER XI */, + Greek_omicron = 0x07ef /* U+03BF GREEK SMALL LETTER OMICRON */, + Greek_pi = 0x07f0 /* U+03C0 GREEK SMALL LETTER PI */, + Greek_rho = 0x07f1 /* U+03C1 GREEK SMALL LETTER RHO */, + Greek_sigma = 0x07f2 /* U+03C3 GREEK SMALL LETTER SIGMA */, + Greek_finalsmallsigma = 0x07f3 /* U+03C2 GREEK SMALL LETTER FINAL SIGMA */, + Greek_tau = 0x07f4 /* U+03C4 GREEK SMALL LETTER TAU */, + Greek_upsilon = 0x07f5 /* U+03C5 GREEK SMALL LETTER UPSILON */, + Greek_phi = 0x07f6 /* U+03C6 GREEK SMALL LETTER PHI */, + Greek_chi = 0x07f7 /* U+03C7 GREEK SMALL LETTER CHI */, + Greek_psi = 0x07f8 /* U+03C8 GREEK SMALL LETTER PSI */, + Greek_omega = 0x07f9 /* U+03C9 GREEK SMALL LETTER OMEGA */, + Greek_switch = 0xff7e /* Alias for mode_switch */, + leftradical = 0x08a1 /* U+23B7 RADICAL SYMBOL BOTTOM */, + topleftradical = 0x08a2 /*(U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT)*/, + horizconnector = 0x08a3 /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/, + topintegral = 0x08a4 /* U+2320 TOP HALF INTEGRAL */, + botintegral = 0x08a5 /* U+2321 BOTTOM HALF INTEGRAL */, + vertconnector = 0x08a6 /*(U+2502 BOX DRAWINGS LIGHT VERTICAL)*/, + topleftsqbracket = 0x08a7 /* U+23A1 LEFT SQUARE BRACKET UPPER CORNER */, + botleftsqbracket = 0x08a8 /* U+23A3 LEFT SQUARE BRACKET LOWER CORNER */, + toprightsqbracket = 0x08a9 /* U+23A4 RIGHT SQUARE BRACKET UPPER CORNER */, + botrightsqbracket = 0x08aa /* U+23A6 RIGHT SQUARE BRACKET LOWER CORNER */, + topleftparens = 0x08ab /* U+239B LEFT PARENTHESIS UPPER HOOK */, + botleftparens = 0x08ac /* U+239D LEFT PARENTHESIS LOWER HOOK */, + toprightparens = 0x08ad /* U+239E RIGHT PARENTHESIS UPPER HOOK */, + botrightparens = 0x08ae /* U+23A0 RIGHT PARENTHESIS LOWER HOOK */, + leftmiddlecurlybrace = 0x08af /* U+23A8 LEFT CURLY BRACKET MIDDLE PIECE */, + rightmiddlecurlybrace = 0x08b0 /* U+23AC RIGHT CURLY BRACKET MIDDLE PIECE */, + topleftsummation = 0x08b1, + botleftsummation = 0x08b2, + topvertsummationconnector = 0x08b3, + botvertsummationconnector = 0x08b4, + toprightsummation = 0x08b5, + botrightsummation = 0x08b6, + rightmiddlesummation = 0x08b7, + lessthanequal = 0x08bc /* U+2264 LESS-THAN OR EQUAL TO */, + notequal = 0x08bd /* U+2260 NOT EQUAL TO */, + greaterthanequal = 0x08be /* U+2265 GREATER-THAN OR EQUAL TO */, + integral = 0x08bf /* U+222B INTEGRAL */, + therefore = 0x08c0 /* U+2234 THEREFORE */, + variation = 0x08c1 /* U+221D PROPORTIONAL TO */, + infinity = 0x08c2 /* U+221E INFINITY */, + nabla = 0x08c5 /* U+2207 NABLA */, + approximate = 0x08c8 /* U+223C TILDE OPERATOR */, + similarequal = 0x08c9 /* U+2243 ASYMPTOTICALLY EQUAL TO */, + ifonlyif = 0x08cd /* U+21D4 LEFT RIGHT DOUBLE ARROW */, + implies = 0x08ce /* U+21D2 RIGHTWARDS DOUBLE ARROW */, + identical = 0x08cf /* U+2261 IDENTICAL TO */, + radical = 0x08d6 /* U+221A SQUARE ROOT */, + includedin = 0x08da /* U+2282 SUBSET OF */, + includes = 0x08db /* U+2283 SUPERSET OF */, + intersection = 0x08dc /* U+2229 INTERSECTION */, + union = 0x08dd /* U+222A UNION */, + logicaland = 0x08de /* U+2227 LOGICAL AND */, + logicalor = 0x08df /* U+2228 LOGICAL OR */, + partialderivative = 0x08ef /* U+2202 PARTIAL DIFFERENTIAL */, + function = 0x08f6 /* U+0192 LATIN SMALL LETTER F WITH HOOK */, + leftarrow = 0x08fb /* U+2190 LEFTWARDS ARROW */, + uparrow = 0x08fc /* U+2191 UPWARDS ARROW */, + rightarrow = 0x08fd /* U+2192 RIGHTWARDS ARROW */, + downarrow = 0x08fe /* U+2193 DOWNWARDS ARROW */, + blank = 0x09df, + soliddiamond = 0x09e0 /* U+25C6 BLACK DIAMOND */, + checkerboard = 0x09e1 /* U+2592 MEDIUM SHADE */, + ht = 0x09e2 /* U+2409 SYMBOL FOR HORIZONTAL TABULATION */, + ff = 0x09e3 /* U+240C SYMBOL FOR FORM FEED */, + cr = 0x09e4 /* U+240D SYMBOL FOR CARRIAGE RETURN */, + lf = 0x09e5 /* U+240A SYMBOL FOR LINE FEED */, + nl = 0x09e8 /* U+2424 SYMBOL FOR NEWLINE */, + vt = 0x09e9 /* U+240B SYMBOL FOR VERTICAL TABULATION */, + lowrightcorner = 0x09ea /* U+2518 BOX DRAWINGS LIGHT UP AND LEFT */, + uprightcorner = 0x09eb /* U+2510 BOX DRAWINGS LIGHT DOWN AND LEFT */, + upleftcorner = 0x09ec /* U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT */, + lowleftcorner = 0x09ed /* U+2514 BOX DRAWINGS LIGHT UP AND RIGHT */, + crossinglines = 0x09ee /* U+253C BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */, + horizlinescan1 = 0x09ef /* U+23BA HORIZONTAL SCAN LINE-1 */, + horizlinescan3 = 0x09f0 /* U+23BB HORIZONTAL SCAN LINE-3 */, + horizlinescan5 = 0x09f1 /* U+2500 BOX DRAWINGS LIGHT HORIZONTAL */, + horizlinescan7 = 0x09f2 /* U+23BC HORIZONTAL SCAN LINE-7 */, + horizlinescan9 = 0x09f3 /* U+23BD HORIZONTAL SCAN LINE-9 */, + leftt = 0x09f4 /* U+251C BOX DRAWINGS LIGHT VERTICAL AND RIGHT */, + rightt = 0x09f5 /* U+2524 BOX DRAWINGS LIGHT VERTICAL AND LEFT */, + bott = 0x09f6 /* U+2534 BOX DRAWINGS LIGHT UP AND HORIZONTAL */, + topt = 0x09f7 /* U+252C BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */, + vertbar = 0x09f8 /* U+2502 BOX DRAWINGS LIGHT VERTICAL */, + emspace = 0x0aa1 /* U+2003 EM SPACE */, + enspace = 0x0aa2 /* U+2002 EN SPACE */, + em3space = 0x0aa3 /* U+2004 THREE-PER-EM SPACE */, + em4space = 0x0aa4 /* U+2005 FOUR-PER-EM SPACE */, + digitspace = 0x0aa5 /* U+2007 FIGURE SPACE */, + punctspace = 0x0aa6 /* U+2008 PUNCTUATION SPACE */, + thinspace = 0x0aa7 /* U+2009 THIN SPACE */, + hairspace = 0x0aa8 /* U+200A HAIR SPACE */, + emdash = 0x0aa9 /* U+2014 EM DASH */, + endash = 0x0aaa /* U+2013 EN DASH */, + signifblank = 0x0aac /*(U+2423 OPEN BOX)*/, + ellipsis = 0x0aae /* U+2026 HORIZONTAL ELLIPSIS */, + doubbaselinedot = 0x0aaf /* U+2025 TWO DOT LEADER */, + onethird = 0x0ab0 /* U+2153 VULGAR FRACTION ONE THIRD */, + twothirds = 0x0ab1 /* U+2154 VULGAR FRACTION TWO THIRDS */, + onefifth = 0x0ab2 /* U+2155 VULGAR FRACTION ONE FIFTH */, + twofifths = 0x0ab3 /* U+2156 VULGAR FRACTION TWO FIFTHS */, + threefifths = 0x0ab4 /* U+2157 VULGAR FRACTION THREE FIFTHS */, + fourfifths = 0x0ab5 /* U+2158 VULGAR FRACTION FOUR FIFTHS */, + onesixth = 0x0ab6 /* U+2159 VULGAR FRACTION ONE SIXTH */, + fivesixths = 0x0ab7 /* U+215A VULGAR FRACTION FIVE SIXTHS */, + careof = 0x0ab8 /* U+2105 CARE OF */, + figdash = 0x0abb /* U+2012 FIGURE DASH */, + leftanglebracket = 0x0abc /*(U+27E8 MATHEMATICAL LEFT ANGLE BRACKET)*/, + decimalpoint = 0x0abd /*(U+002E FULL STOP)*/, + rightanglebracket = 0x0abe /*(U+27E9 MATHEMATICAL RIGHT ANGLE BRACKET)*/, + marker = 0x0abf, + oneeighth = 0x0ac3 /* U+215B VULGAR FRACTION ONE EIGHTH */, + threeeighths = 0x0ac4 /* U+215C VULGAR FRACTION THREE EIGHTHS */, + fiveeighths = 0x0ac5 /* U+215D VULGAR FRACTION FIVE EIGHTHS */, + seveneighths = 0x0ac6 /* U+215E VULGAR FRACTION SEVEN EIGHTHS */, + trademark = 0x0ac9 /* U+2122 TRADE MARK SIGN */, + signaturemark = 0x0aca /*(U+2613 SALTIRE)*/, + trademarkincircle = 0x0acb, + leftopentriangle = 0x0acc /*(U+25C1 WHITE LEFT-POINTING TRIANGLE)*/, + rightopentriangle = 0x0acd /*(U+25B7 WHITE RIGHT-POINTING TRIANGLE)*/, + emopencircle = 0x0ace /*(U+25CB WHITE CIRCLE)*/, + emopenrectangle = 0x0acf /*(U+25AF WHITE VERTICAL RECTANGLE)*/, + leftsinglequotemark = 0x0ad0 /* U+2018 LEFT SINGLE QUOTATION MARK */, + rightsinglequotemark = 0x0ad1 /* U+2019 RIGHT SINGLE QUOTATION MARK */, + leftdoublequotemark = 0x0ad2 /* U+201C LEFT DOUBLE QUOTATION MARK */, + rightdoublequotemark = 0x0ad3 /* U+201D RIGHT DOUBLE QUOTATION MARK */, + prescription = 0x0ad4 /* U+211E PRESCRIPTION TAKE */, + permille = 0x0ad5 /* U+2030 PER MILLE SIGN */, + minutes = 0x0ad6 /* U+2032 PRIME */, + seconds = 0x0ad7 /* U+2033 DOUBLE PRIME */, + latincross = 0x0ad9 /* U+271D LATIN CROSS */, + hexagram = 0x0ada, + filledrectbullet = 0x0adb /*(U+25AC BLACK RECTANGLE)*/, + filledlefttribullet = 0x0adc /*(U+25C0 BLACK LEFT-POINTING TRIANGLE)*/, + filledrighttribullet = 0x0add /*(U+25B6 BLACK RIGHT-POINTING TRIANGLE)*/, + emfilledcircle = 0x0ade /*(U+25CF BLACK CIRCLE)*/, + emfilledrect = 0x0adf /*(U+25AE BLACK VERTICAL RECTANGLE)*/, + enopencircbullet = 0x0ae0 /*(U+25E6 WHITE BULLET)*/, + enopensquarebullet = 0x0ae1 /*(U+25AB WHITE SMALL SQUARE)*/, + openrectbullet = 0x0ae2 /*(U+25AD WHITE RECTANGLE)*/, + opentribulletup = 0x0ae3 /*(U+25B3 WHITE UP-POINTING TRIANGLE)*/, + opentribulletdown = 0x0ae4 /*(U+25BD WHITE DOWN-POINTING TRIANGLE)*/, + openstar = 0x0ae5 /*(U+2606 WHITE STAR)*/, + enfilledcircbullet = 0x0ae6 /*(U+2022 BULLET)*/, + enfilledsqbullet = 0x0ae7 /*(U+25AA BLACK SMALL SQUARE)*/, + filledtribulletup = 0x0ae8 /*(U+25B2 BLACK UP-POINTING TRIANGLE)*/, + filledtribulletdown = 0x0ae9 /*(U+25BC BLACK DOWN-POINTING TRIANGLE)*/, + leftpointer = 0x0aea /*(U+261C WHITE LEFT POINTING INDEX)*/, + rightpointer = 0x0aeb /*(U+261E WHITE RIGHT POINTING INDEX)*/, + club = 0x0aec /* U+2663 BLACK CLUB SUIT */, + diamond = 0x0aed /* U+2666 BLACK DIAMOND SUIT */, + heart = 0x0aee /* U+2665 BLACK HEART SUIT */, + maltesecross = 0x0af0 /* U+2720 MALTESE CROSS */, + dagger = 0x0af1 /* U+2020 DAGGER */, + doubledagger = 0x0af2 /* U+2021 DOUBLE DAGGER */, + checkmark = 0x0af3 /* U+2713 CHECK MARK */, + ballotcross = 0x0af4 /* U+2717 BALLOT X */, + musicalsharp = 0x0af5 /* U+266F MUSIC SHARP SIGN */, + musicalflat = 0x0af6 /* U+266D MUSIC FLAT SIGN */, + malesymbol = 0x0af7 /* U+2642 MALE SIGN */, + femalesymbol = 0x0af8 /* U+2640 FEMALE SIGN */, + telephone = 0x0af9 /* U+260E BLACK TELEPHONE */, + telephonerecorder = 0x0afa /* U+2315 TELEPHONE RECORDER */, + phonographcopyright = 0x0afb /* U+2117 SOUND RECORDING COPYRIGHT */, + caret = 0x0afc /* U+2038 CARET */, + singlelowquotemark = 0x0afd /* U+201A SINGLE LOW-9 QUOTATION MARK */, + doublelowquotemark = 0x0afe /* U+201E DOUBLE LOW-9 QUOTATION MARK */, + cursor = 0x0aff, + leftcaret = 0x0ba3 /*(U+003C LESS-THAN SIGN)*/, + rightcaret = 0x0ba6 /*(U+003E GREATER-THAN SIGN)*/, + downcaret = 0x0ba8 /*(U+2228 LOGICAL OR)*/, + upcaret = 0x0ba9 /*(U+2227 LOGICAL AND)*/, + overbar = 0x0bc0 /*(U+00AF MACRON)*/, + downtack = 0x0bc2 /* U+22A4 DOWN TACK */, + upshoe = 0x0bc3 /*(U+2229 INTERSECTION)*/, + downstile = 0x0bc4 /* U+230A LEFT FLOOR */, + underbar = 0x0bc6 /*(U+005F LOW LINE)*/, + jot = 0x0bca /* U+2218 RING OPERATOR */, + quad = 0x0bcc /* U+2395 APL FUNCTIONAL SYMBOL QUAD */, + uptack = 0x0bce /* U+22A5 UP TACK */, + circle = 0x0bcf /* U+25CB WHITE CIRCLE */, + upstile = 0x0bd3 /* U+2308 LEFT CEILING */, + downshoe = 0x0bd6 /*(U+222A UNION)*/, + rightshoe = 0x0bd8 /*(U+2283 SUPERSET OF)*/, + leftshoe = 0x0bda /*(U+2282 SUBSET OF)*/, + lefttack = 0x0bdc /* U+22A3 LEFT TACK */, + righttack = 0x0bfc /* U+22A2 RIGHT TACK */, + hebrew_doublelowline = 0x0cdf /* U+2017 DOUBLE LOW LINE */, + hebrew_aleph = 0x0ce0 /* U+05D0 HEBREW LETTER ALEF */, + hebrew_bet = 0x0ce1 /* U+05D1 HEBREW LETTER BET */, + hebrew_beth = 0x0ce1 /* deprecated */, + hebrew_gimel = 0x0ce2 /* U+05D2 HEBREW LETTER GIMEL */, + hebrew_gimmel = 0x0ce2 /* deprecated */, + hebrew_dalet = 0x0ce3 /* U+05D3 HEBREW LETTER DALET */, + hebrew_daleth = 0x0ce3 /* deprecated */, + hebrew_he = 0x0ce4 /* U+05D4 HEBREW LETTER HE */, + hebrew_waw = 0x0ce5 /* U+05D5 HEBREW LETTER VAV */, + hebrew_zain = 0x0ce6 /* U+05D6 HEBREW LETTER ZAYIN */, + hebrew_zayin = 0x0ce6 /* deprecated */, + hebrew_chet = 0x0ce7 /* U+05D7 HEBREW LETTER HET */, + hebrew_het = 0x0ce7 /* deprecated */, + hebrew_tet = 0x0ce8 /* U+05D8 HEBREW LETTER TET */, + hebrew_teth = 0x0ce8 /* deprecated */, + hebrew_yod = 0x0ce9 /* U+05D9 HEBREW LETTER YOD */, + hebrew_finalkaph = 0x0cea /* U+05DA HEBREW LETTER FINAL KAF */, + hebrew_kaph = 0x0ceb /* U+05DB HEBREW LETTER KAF */, + hebrew_lamed = 0x0cec /* U+05DC HEBREW LETTER LAMED */, + hebrew_finalmem = 0x0ced /* U+05DD HEBREW LETTER FINAL MEM */, + hebrew_mem = 0x0cee /* U+05DE HEBREW LETTER MEM */, + hebrew_finalnun = 0x0cef /* U+05DF HEBREW LETTER FINAL NUN */, + hebrew_nun = 0x0cf0 /* U+05E0 HEBREW LETTER NUN */, + hebrew_samech = 0x0cf1 /* U+05E1 HEBREW LETTER SAMEKH */, + hebrew_samekh = 0x0cf1 /* deprecated */, + hebrew_ayin = 0x0cf2 /* U+05E2 HEBREW LETTER AYIN */, + hebrew_finalpe = 0x0cf3 /* U+05E3 HEBREW LETTER FINAL PE */, + hebrew_pe = 0x0cf4 /* U+05E4 HEBREW LETTER PE */, + hebrew_finalzade = 0x0cf5 /* U+05E5 HEBREW LETTER FINAL TSADI */, + hebrew_finalzadi = 0x0cf5 /* deprecated */, + hebrew_zade = 0x0cf6 /* U+05E6 HEBREW LETTER TSADI */, + hebrew_zadi = 0x0cf6 /* deprecated */, + hebrew_qoph = 0x0cf7 /* U+05E7 HEBREW LETTER QOF */, + hebrew_kuf = 0x0cf7 /* deprecated */, + hebrew_resh = 0x0cf8 /* U+05E8 HEBREW LETTER RESH */, + hebrew_shin = 0x0cf9 /* U+05E9 HEBREW LETTER SHIN */, + hebrew_taw = 0x0cfa /* U+05EA HEBREW LETTER TAV */, + hebrew_taf = 0x0cfa /* deprecated */, + Hebrew_switch = 0xff7e /* Alias for mode_switch */, + Thai_kokai = 0x0da1 /* U+0E01 THAI CHARACTER KO KAI */, + Thai_khokhai = 0x0da2 /* U+0E02 THAI CHARACTER KHO KHAI */, + Thai_khokhuat = 0x0da3 /* U+0E03 THAI CHARACTER KHO KHUAT */, + Thai_khokhwai = 0x0da4 /* U+0E04 THAI CHARACTER KHO KHWAI */, + Thai_khokhon = 0x0da5 /* U+0E05 THAI CHARACTER KHO KHON */, + Thai_khorakhang = 0x0da6 /* U+0E06 THAI CHARACTER KHO RAKHANG */, + Thai_ngongu = 0x0da7 /* U+0E07 THAI CHARACTER NGO NGU */, + Thai_chochan = 0x0da8 /* U+0E08 THAI CHARACTER CHO CHAN */, + Thai_choching = 0x0da9 /* U+0E09 THAI CHARACTER CHO CHING */, + Thai_chochang = 0x0daa /* U+0E0A THAI CHARACTER CHO CHANG */, + Thai_soso = 0x0dab /* U+0E0B THAI CHARACTER SO SO */, + Thai_chochoe = 0x0dac /* U+0E0C THAI CHARACTER CHO CHOE */, + Thai_yoying = 0x0dad /* U+0E0D THAI CHARACTER YO YING */, + Thai_dochada = 0x0dae /* U+0E0E THAI CHARACTER DO CHADA */, + Thai_topatak = 0x0daf /* U+0E0F THAI CHARACTER TO PATAK */, + Thai_thothan = 0x0db0 /* U+0E10 THAI CHARACTER THO THAN */, + Thai_thonangmontho = 0x0db1 /* U+0E11 THAI CHARACTER THO NANGMONTHO */, + Thai_thophuthao = 0x0db2 /* U+0E12 THAI CHARACTER THO PHUTHAO */, + Thai_nonen = 0x0db3 /* U+0E13 THAI CHARACTER NO NEN */, + Thai_dodek = 0x0db4 /* U+0E14 THAI CHARACTER DO DEK */, + Thai_totao = 0x0db5 /* U+0E15 THAI CHARACTER TO TAO */, + Thai_thothung = 0x0db6 /* U+0E16 THAI CHARACTER THO THUNG */, + Thai_thothahan = 0x0db7 /* U+0E17 THAI CHARACTER THO THAHAN */, + Thai_thothong = 0x0db8 /* U+0E18 THAI CHARACTER THO THONG */, + Thai_nonu = 0x0db9 /* U+0E19 THAI CHARACTER NO NU */, + Thai_bobaimai = 0x0dba /* U+0E1A THAI CHARACTER BO BAIMAI */, + Thai_popla = 0x0dbb /* U+0E1B THAI CHARACTER PO PLA */, + Thai_phophung = 0x0dbc /* U+0E1C THAI CHARACTER PHO PHUNG */, + Thai_fofa = 0x0dbd /* U+0E1D THAI CHARACTER FO FA */, + Thai_phophan = 0x0dbe /* U+0E1E THAI CHARACTER PHO PHAN */, + Thai_fofan = 0x0dbf /* U+0E1F THAI CHARACTER FO FAN */, + Thai_phosamphao = 0x0dc0 /* U+0E20 THAI CHARACTER PHO SAMPHAO */, + Thai_moma = 0x0dc1 /* U+0E21 THAI CHARACTER MO MA */, + Thai_yoyak = 0x0dc2 /* U+0E22 THAI CHARACTER YO YAK */, + Thai_rorua = 0x0dc3 /* U+0E23 THAI CHARACTER RO RUA */, + Thai_ru = 0x0dc4 /* U+0E24 THAI CHARACTER RU */, + Thai_loling = 0x0dc5 /* U+0E25 THAI CHARACTER LO LING */, + Thai_lu = 0x0dc6 /* U+0E26 THAI CHARACTER LU */, + Thai_wowaen = 0x0dc7 /* U+0E27 THAI CHARACTER WO WAEN */, + Thai_sosala = 0x0dc8 /* U+0E28 THAI CHARACTER SO SALA */, + Thai_sorusi = 0x0dc9 /* U+0E29 THAI CHARACTER SO RUSI */, + Thai_sosua = 0x0dca /* U+0E2A THAI CHARACTER SO SUA */, + Thai_hohip = 0x0dcb /* U+0E2B THAI CHARACTER HO HIP */, + Thai_lochula = 0x0dcc /* U+0E2C THAI CHARACTER LO CHULA */, + Thai_oang = 0x0dcd /* U+0E2D THAI CHARACTER O ANG */, + Thai_honokhuk = 0x0dce /* U+0E2E THAI CHARACTER HO NOKHUK */, + Thai_paiyannoi = 0x0dcf /* U+0E2F THAI CHARACTER PAIYANNOI */, + Thai_saraa = 0x0dd0 /* U+0E30 THAI CHARACTER SARA A */, + Thai_maihanakat = 0x0dd1 /* U+0E31 THAI CHARACTER MAI HAN-AKAT */, + Thai_saraaa = 0x0dd2 /* U+0E32 THAI CHARACTER SARA AA */, + Thai_saraam = 0x0dd3 /* U+0E33 THAI CHARACTER SARA AM */, + Thai_sarai = 0x0dd4 /* U+0E34 THAI CHARACTER SARA I */, + Thai_saraii = 0x0dd5 /* U+0E35 THAI CHARACTER SARA II */, + Thai_saraue = 0x0dd6 /* U+0E36 THAI CHARACTER SARA UE */, + Thai_sarauee = 0x0dd7 /* U+0E37 THAI CHARACTER SARA UEE */, + Thai_sarau = 0x0dd8 /* U+0E38 THAI CHARACTER SARA U */, + Thai_sarauu = 0x0dd9 /* U+0E39 THAI CHARACTER SARA UU */, + Thai_phinthu = 0x0dda /* U+0E3A THAI CHARACTER PHINTHU */, + Thai_maihanakat_maitho = 0x0dde, + Thai_baht = 0x0ddf /* U+0E3F THAI CURRENCY SYMBOL BAHT */, + Thai_sarae = 0x0de0 /* U+0E40 THAI CHARACTER SARA E */, + Thai_saraae = 0x0de1 /* U+0E41 THAI CHARACTER SARA AE */, + Thai_sarao = 0x0de2 /* U+0E42 THAI CHARACTER SARA O */, + Thai_saraaimaimuan = 0x0de3 /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */, + Thai_saraaimaimalai = 0x0de4 /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */, + Thai_lakkhangyao = 0x0de5 /* U+0E45 THAI CHARACTER LAKKHANGYAO */, + Thai_maiyamok = 0x0de6 /* U+0E46 THAI CHARACTER MAIYAMOK */, + Thai_maitaikhu = 0x0de7 /* U+0E47 THAI CHARACTER MAITAIKHU */, + Thai_maiek = 0x0de8 /* U+0E48 THAI CHARACTER MAI EK */, + Thai_maitho = 0x0de9 /* U+0E49 THAI CHARACTER MAI THO */, + Thai_maitri = 0x0dea /* U+0E4A THAI CHARACTER MAI TRI */, + Thai_maichattawa = 0x0deb /* U+0E4B THAI CHARACTER MAI CHATTAWA */, + Thai_thanthakhat = 0x0dec /* U+0E4C THAI CHARACTER THANTHAKHAT */, + Thai_nikhahit = 0x0ded /* U+0E4D THAI CHARACTER NIKHAHIT */, + Thai_leksun = 0x0df0 /* U+0E50 THAI DIGIT ZERO */, + Thai_leknung = 0x0df1 /* U+0E51 THAI DIGIT ONE */, + Thai_leksong = 0x0df2 /* U+0E52 THAI DIGIT TWO */, + Thai_leksam = 0x0df3 /* U+0E53 THAI DIGIT THREE */, + Thai_leksi = 0x0df4 /* U+0E54 THAI DIGIT FOUR */, + Thai_lekha = 0x0df5 /* U+0E55 THAI DIGIT FIVE */, + Thai_lekhok = 0x0df6 /* U+0E56 THAI DIGIT SIX */, + Thai_lekchet = 0x0df7 /* U+0E57 THAI DIGIT SEVEN */, + Thai_lekpaet = 0x0df8 /* U+0E58 THAI DIGIT EIGHT */, + Thai_lekkao = 0x0df9 /* U+0E59 THAI DIGIT NINE */, + Hangul = 0xff31 /* Hangul start/stop(toggle) */, + Hangul_Start = 0xff32 /* Hangul start */, + Hangul_End = 0xff33 /* Hangul end, English start */, + Hangul_Hanja = 0xff34 /* Start Hangul->Hanja Conversion */, + Hangul_Jamo = 0xff35 /* Hangul Jamo mode */, + Hangul_Romaja = 0xff36 /* Hangul Romaja mode */, + Hangul_Codeinput = 0xff37 /* Hangul code input mode */, + Hangul_Jeonja = 0xff38 /* Jeonja mode */, + Hangul_Banja = 0xff39 /* Banja mode */, + Hangul_PreHanja = 0xff3a /* Pre Hanja conversion */, + Hangul_PostHanja = 0xff3b /* Post Hanja conversion */, + Hangul_SingleCandidate = 0xff3c /* Single candidate */, + Hangul_MultipleCandidate = 0xff3d /* Multiple candidate */, + Hangul_PreviousCandidate = 0xff3e /* Previous candidate */, + Hangul_Special = 0xff3f /* Special symbols */, + Hangul_switch = 0xff7e /* Alias for mode_switch */, + Hangul_Kiyeog = 0x0ea1, + Hangul_SsangKiyeog = 0x0ea2, + Hangul_KiyeogSios = 0x0ea3, + Hangul_Nieun = 0x0ea4, + Hangul_NieunJieuj = 0x0ea5, + Hangul_NieunHieuh = 0x0ea6, + Hangul_Dikeud = 0x0ea7, + Hangul_SsangDikeud = 0x0ea8, + Hangul_Rieul = 0x0ea9, + Hangul_RieulKiyeog = 0x0eaa, + Hangul_RieulMieum = 0x0eab, + Hangul_RieulPieub = 0x0eac, + Hangul_RieulSios = 0x0ead, + Hangul_RieulTieut = 0x0eae, + Hangul_RieulPhieuf = 0x0eaf, + Hangul_RieulHieuh = 0x0eb0, + Hangul_Mieum = 0x0eb1, + Hangul_Pieub = 0x0eb2, + Hangul_SsangPieub = 0x0eb3, + Hangul_PieubSios = 0x0eb4, + Hangul_Sios = 0x0eb5, + Hangul_SsangSios = 0x0eb6, + Hangul_Ieung = 0x0eb7, + Hangul_Jieuj = 0x0eb8, + Hangul_SsangJieuj = 0x0eb9, + Hangul_Cieuc = 0x0eba, + Hangul_Khieuq = 0x0ebb, + Hangul_Tieut = 0x0ebc, + Hangul_Phieuf = 0x0ebd, + Hangul_Hieuh = 0x0ebe, + Hangul_A = 0x0ebf, + Hangul_AE = 0x0ec0, + Hangul_YA = 0x0ec1, + Hangul_YAE = 0x0ec2, + Hangul_EO = 0x0ec3, + Hangul_E = 0x0ec4, + Hangul_YEO = 0x0ec5, + Hangul_YE = 0x0ec6, + Hangul_O = 0x0ec7, + Hangul_WA = 0x0ec8, + Hangul_WAE = 0x0ec9, + Hangul_OE = 0x0eca, + Hangul_YO = 0x0ecb, + Hangul_U = 0x0ecc, + Hangul_WEO = 0x0ecd, + Hangul_WE = 0x0ece, + Hangul_WI = 0x0ecf, + Hangul_YU = 0x0ed0, + Hangul_EU = 0x0ed1, + Hangul_YI = 0x0ed2, + Hangul_I = 0x0ed3, + Hangul_J_Kiyeog = 0x0ed4, + Hangul_J_SsangKiyeog = 0x0ed5, + Hangul_J_KiyeogSios = 0x0ed6, + Hangul_J_Nieun = 0x0ed7, + Hangul_J_NieunJieuj = 0x0ed8, + Hangul_J_NieunHieuh = 0x0ed9, + Hangul_J_Dikeud = 0x0eda, + Hangul_J_Rieul = 0x0edb, + Hangul_J_RieulKiyeog = 0x0edc, + Hangul_J_RieulMieum = 0x0edd, + Hangul_J_RieulPieub = 0x0ede, + Hangul_J_RieulSios = 0x0edf, + Hangul_J_RieulTieut = 0x0ee0, + Hangul_J_RieulPhieuf = 0x0ee1, + Hangul_J_RieulHieuh = 0x0ee2, + Hangul_J_Mieum = 0x0ee3, + Hangul_J_Pieub = 0x0ee4, + Hangul_J_PieubSios = 0x0ee5, + Hangul_J_Sios = 0x0ee6, + Hangul_J_SsangSios = 0x0ee7, + Hangul_J_Ieung = 0x0ee8, + Hangul_J_Jieuj = 0x0ee9, + Hangul_J_Cieuc = 0x0eea, + Hangul_J_Khieuq = 0x0eeb, + Hangul_J_Tieut = 0x0eec, + Hangul_J_Phieuf = 0x0eed, + Hangul_J_Hieuh = 0x0eee, + Hangul_RieulYeorinHieuh = 0x0eef, + Hangul_SunkyeongeumMieum = 0x0ef0, + Hangul_SunkyeongeumPieub = 0x0ef1, + Hangul_PanSios = 0x0ef2, + Hangul_KkogjiDalrinIeung = 0x0ef3, + Hangul_SunkyeongeumPhieuf = 0x0ef4, + Hangul_YeorinHieuh = 0x0ef5, + Hangul_AraeA = 0x0ef6, + Hangul_AraeAE = 0x0ef7, + Hangul_J_PanSios = 0x0ef8, + Hangul_J_KkogjiDalrinIeung = 0x0ef9, + Hangul_J_YeorinHieuh = 0x0efa, + Korean_Won = 0x0eff /*(U+20A9 WON SIGN)*/, + Armenian_ligature_ew = 0x1000587 /* U+0587 ARMENIAN SMALL LIGATURE ECH YIWN */, + Armenian_full_stop = 0x1000589 /* U+0589 ARMENIAN FULL STOP */, + Armenian_verjaket = 0x1000589 /* U+0589 ARMENIAN FULL STOP */, + Armenian_separation_mark = 0x100055d /* U+055D ARMENIAN COMMA */, + Armenian_but = 0x100055d /* U+055D ARMENIAN COMMA */, + Armenian_hyphen = 0x100058a /* U+058A ARMENIAN HYPHEN */, + Armenian_yentamna = 0x100058a /* U+058A ARMENIAN HYPHEN */, + Armenian_exclam = 0x100055c /* U+055C ARMENIAN EXCLAMATION MARK */, + Armenian_amanak = 0x100055c /* U+055C ARMENIAN EXCLAMATION MARK */, + Armenian_accent = 0x100055b /* U+055B ARMENIAN EMPHASIS MARK */, + Armenian_shesht = 0x100055b /* U+055B ARMENIAN EMPHASIS MARK */, + Armenian_question = 0x100055e /* U+055E ARMENIAN QUESTION MARK */, + Armenian_paruyk = 0x100055e /* U+055E ARMENIAN QUESTION MARK */, + Armenian_AYB = 0x1000531 /* U+0531 ARMENIAN CAPITAL LETTER AYB */, + Armenian_ayb = 0x1000561 /* U+0561 ARMENIAN SMALL LETTER AYB */, + Armenian_BEN = 0x1000532 /* U+0532 ARMENIAN CAPITAL LETTER BEN */, + Armenian_ben = 0x1000562 /* U+0562 ARMENIAN SMALL LETTER BEN */, + Armenian_GIM = 0x1000533 /* U+0533 ARMENIAN CAPITAL LETTER GIM */, + Armenian_gim = 0x1000563 /* U+0563 ARMENIAN SMALL LETTER GIM */, + Armenian_DA = 0x1000534 /* U+0534 ARMENIAN CAPITAL LETTER DA */, + Armenian_da = 0x1000564 /* U+0564 ARMENIAN SMALL LETTER DA */, + Armenian_YECH = 0x1000535 /* U+0535 ARMENIAN CAPITAL LETTER ECH */, + Armenian_yech = 0x1000565 /* U+0565 ARMENIAN SMALL LETTER ECH */, + Armenian_ZA = 0x1000536 /* U+0536 ARMENIAN CAPITAL LETTER ZA */, + Armenian_za = 0x1000566 /* U+0566 ARMENIAN SMALL LETTER ZA */, + Armenian_E = 0x1000537 /* U+0537 ARMENIAN CAPITAL LETTER EH */, + Armenian_e = 0x1000567 /* U+0567 ARMENIAN SMALL LETTER EH */, + Armenian_AT = 0x1000538 /* U+0538 ARMENIAN CAPITAL LETTER ET */, + Armenian_at = 0x1000568 /* U+0568 ARMENIAN SMALL LETTER ET */, + Armenian_TO = 0x1000539 /* U+0539 ARMENIAN CAPITAL LETTER TO */, + Armenian_to = 0x1000569 /* U+0569 ARMENIAN SMALL LETTER TO */, + Armenian_ZHE = 0x100053a /* U+053A ARMENIAN CAPITAL LETTER ZHE */, + Armenian_zhe = 0x100056a /* U+056A ARMENIAN SMALL LETTER ZHE */, + Armenian_INI = 0x100053b /* U+053B ARMENIAN CAPITAL LETTER INI */, + Armenian_ini = 0x100056b /* U+056B ARMENIAN SMALL LETTER INI */, + Armenian_LYUN = 0x100053c /* U+053C ARMENIAN CAPITAL LETTER LIWN */, + Armenian_lyun = 0x100056c /* U+056C ARMENIAN SMALL LETTER LIWN */, + Armenian_KHE = 0x100053d /* U+053D ARMENIAN CAPITAL LETTER XEH */, + Armenian_khe = 0x100056d /* U+056D ARMENIAN SMALL LETTER XEH */, + Armenian_TSA = 0x100053e /* U+053E ARMENIAN CAPITAL LETTER CA */, + Armenian_tsa = 0x100056e /* U+056E ARMENIAN SMALL LETTER CA */, + Armenian_KEN = 0x100053f /* U+053F ARMENIAN CAPITAL LETTER KEN */, + Armenian_ken = 0x100056f /* U+056F ARMENIAN SMALL LETTER KEN */, + Armenian_HO = 0x1000540 /* U+0540 ARMENIAN CAPITAL LETTER HO */, + Armenian_ho = 0x1000570 /* U+0570 ARMENIAN SMALL LETTER HO */, + Armenian_DZA = 0x1000541 /* U+0541 ARMENIAN CAPITAL LETTER JA */, + Armenian_dza = 0x1000571 /* U+0571 ARMENIAN SMALL LETTER JA */, + Armenian_GHAT = 0x1000542 /* U+0542 ARMENIAN CAPITAL LETTER GHAD */, + Armenian_ghat = 0x1000572 /* U+0572 ARMENIAN SMALL LETTER GHAD */, + Armenian_TCHE = 0x1000543 /* U+0543 ARMENIAN CAPITAL LETTER CHEH */, + Armenian_tche = 0x1000573 /* U+0573 ARMENIAN SMALL LETTER CHEH */, + Armenian_MEN = 0x1000544 /* U+0544 ARMENIAN CAPITAL LETTER MEN */, + Armenian_men = 0x1000574 /* U+0574 ARMENIAN SMALL LETTER MEN */, + Armenian_HI = 0x1000545 /* U+0545 ARMENIAN CAPITAL LETTER YI */, + Armenian_hi = 0x1000575 /* U+0575 ARMENIAN SMALL LETTER YI */, + Armenian_NU = 0x1000546 /* U+0546 ARMENIAN CAPITAL LETTER NOW */, + Armenian_nu = 0x1000576 /* U+0576 ARMENIAN SMALL LETTER NOW */, + Armenian_SHA = 0x1000547 /* U+0547 ARMENIAN CAPITAL LETTER SHA */, + Armenian_sha = 0x1000577 /* U+0577 ARMENIAN SMALL LETTER SHA */, + Armenian_VO = 0x1000548 /* U+0548 ARMENIAN CAPITAL LETTER VO */, + Armenian_vo = 0x1000578 /* U+0578 ARMENIAN SMALL LETTER VO */, + Armenian_CHA = 0x1000549 /* U+0549 ARMENIAN CAPITAL LETTER CHA */, + Armenian_cha = 0x1000579 /* U+0579 ARMENIAN SMALL LETTER CHA */, + Armenian_PE = 0x100054a /* U+054A ARMENIAN CAPITAL LETTER PEH */, + Armenian_pe = 0x100057a /* U+057A ARMENIAN SMALL LETTER PEH */, + Armenian_JE = 0x100054b /* U+054B ARMENIAN CAPITAL LETTER JHEH */, + Armenian_je = 0x100057b /* U+057B ARMENIAN SMALL LETTER JHEH */, + Armenian_RA = 0x100054c /* U+054C ARMENIAN CAPITAL LETTER RA */, + Armenian_ra = 0x100057c /* U+057C ARMENIAN SMALL LETTER RA */, + Armenian_SE = 0x100054d /* U+054D ARMENIAN CAPITAL LETTER SEH */, + Armenian_se = 0x100057d /* U+057D ARMENIAN SMALL LETTER SEH */, + Armenian_VEV = 0x100054e /* U+054E ARMENIAN CAPITAL LETTER VEW */, + Armenian_vev = 0x100057e /* U+057E ARMENIAN SMALL LETTER VEW */, + Armenian_TYUN = 0x100054f /* U+054F ARMENIAN CAPITAL LETTER TIWN */, + Armenian_tyun = 0x100057f /* U+057F ARMENIAN SMALL LETTER TIWN */, + Armenian_RE = 0x1000550 /* U+0550 ARMENIAN CAPITAL LETTER REH */, + Armenian_re = 0x1000580 /* U+0580 ARMENIAN SMALL LETTER REH */, + Armenian_TSO = 0x1000551 /* U+0551 ARMENIAN CAPITAL LETTER CO */, + Armenian_tso = 0x1000581 /* U+0581 ARMENIAN SMALL LETTER CO */, + Armenian_VYUN = 0x1000552 /* U+0552 ARMENIAN CAPITAL LETTER YIWN */, + Armenian_vyun = 0x1000582 /* U+0582 ARMENIAN SMALL LETTER YIWN */, + Armenian_PYUR = 0x1000553 /* U+0553 ARMENIAN CAPITAL LETTER PIWR */, + Armenian_pyur = 0x1000583 /* U+0583 ARMENIAN SMALL LETTER PIWR */, + Armenian_KE = 0x1000554 /* U+0554 ARMENIAN CAPITAL LETTER KEH */, + Armenian_ke = 0x1000584 /* U+0584 ARMENIAN SMALL LETTER KEH */, + Armenian_O = 0x1000555 /* U+0555 ARMENIAN CAPITAL LETTER OH */, + Armenian_o = 0x1000585 /* U+0585 ARMENIAN SMALL LETTER OH */, + Armenian_FE = 0x1000556 /* U+0556 ARMENIAN CAPITAL LETTER FEH */, + Armenian_fe = 0x1000586 /* U+0586 ARMENIAN SMALL LETTER FEH */, + Armenian_apostrophe = 0x100055a /* U+055A ARMENIAN APOSTROPHE */, + Georgian_an = 0x10010d0 /* U+10D0 GEORGIAN LETTER AN */, + Georgian_ban = 0x10010d1 /* U+10D1 GEORGIAN LETTER BAN */, + Georgian_gan = 0x10010d2 /* U+10D2 GEORGIAN LETTER GAN */, + Georgian_don = 0x10010d3 /* U+10D3 GEORGIAN LETTER DON */, + Georgian_en = 0x10010d4 /* U+10D4 GEORGIAN LETTER EN */, + Georgian_vin = 0x10010d5 /* U+10D5 GEORGIAN LETTER VIN */, + Georgian_zen = 0x10010d6 /* U+10D6 GEORGIAN LETTER ZEN */, + Georgian_tan = 0x10010d7 /* U+10D7 GEORGIAN LETTER TAN */, + Georgian_in = 0x10010d8 /* U+10D8 GEORGIAN LETTER IN */, + Georgian_kan = 0x10010d9 /* U+10D9 GEORGIAN LETTER KAN */, + Georgian_las = 0x10010da /* U+10DA GEORGIAN LETTER LAS */, + Georgian_man = 0x10010db /* U+10DB GEORGIAN LETTER MAN */, + Georgian_nar = 0x10010dc /* U+10DC GEORGIAN LETTER NAR */, + Georgian_on = 0x10010dd /* U+10DD GEORGIAN LETTER ON */, + Georgian_par = 0x10010de /* U+10DE GEORGIAN LETTER PAR */, + Georgian_zhar = 0x10010df /* U+10DF GEORGIAN LETTER ZHAR */, + Georgian_rae = 0x10010e0 /* U+10E0 GEORGIAN LETTER RAE */, + Georgian_san = 0x10010e1 /* U+10E1 GEORGIAN LETTER SAN */, + Georgian_tar = 0x10010e2 /* U+10E2 GEORGIAN LETTER TAR */, + Georgian_un = 0x10010e3 /* U+10E3 GEORGIAN LETTER UN */, + Georgian_phar = 0x10010e4 /* U+10E4 GEORGIAN LETTER PHAR */, + Georgian_khar = 0x10010e5 /* U+10E5 GEORGIAN LETTER KHAR */, + Georgian_ghan = 0x10010e6 /* U+10E6 GEORGIAN LETTER GHAN */, + Georgian_qar = 0x10010e7 /* U+10E7 GEORGIAN LETTER QAR */, + Georgian_shin = 0x10010e8 /* U+10E8 GEORGIAN LETTER SHIN */, + Georgian_chin = 0x10010e9 /* U+10E9 GEORGIAN LETTER CHIN */, + Georgian_can = 0x10010ea /* U+10EA GEORGIAN LETTER CAN */, + Georgian_jil = 0x10010eb /* U+10EB GEORGIAN LETTER JIL */, + Georgian_cil = 0x10010ec /* U+10EC GEORGIAN LETTER CIL */, + Georgian_char = 0x10010ed /* U+10ED GEORGIAN LETTER CHAR */, + Georgian_xan = 0x10010ee /* U+10EE GEORGIAN LETTER XAN */, + Georgian_jhan = 0x10010ef /* U+10EF GEORGIAN LETTER JHAN */, + Georgian_hae = 0x10010f0 /* U+10F0 GEORGIAN LETTER HAE */, + Georgian_he = 0x10010f1 /* U+10F1 GEORGIAN LETTER HE */, + Georgian_hie = 0x10010f2 /* U+10F2 GEORGIAN LETTER HIE */, + Georgian_we = 0x10010f3 /* U+10F3 GEORGIAN LETTER WE */, + Georgian_har = 0x10010f4 /* U+10F4 GEORGIAN LETTER HAR */, + Georgian_hoe = 0x10010f5 /* U+10F5 GEORGIAN LETTER HOE */, + Georgian_fi = 0x10010f6 /* U+10F6 GEORGIAN LETTER FI */, + Xabovedot = 0x1001e8a /* U+1E8A LATIN CAPITAL LETTER X WITH DOT ABOVE */, + Ibreve = 0x100012c /* U+012C LATIN CAPITAL LETTER I WITH BREVE */, + Zstroke = 0x10001b5 /* U+01B5 LATIN CAPITAL LETTER Z WITH STROKE */, + Gcaron = 0x10001e6 /* U+01E6 LATIN CAPITAL LETTER G WITH CARON */, + Ocaron = 0x10001d1 /* U+01D2 LATIN CAPITAL LETTER O WITH CARON */, + Obarred = 0x100019f /* U+019F LATIN CAPITAL LETTER O WITH MIDDLE TILDE */, + xabovedot = 0x1001e8b /* U+1E8B LATIN SMALL LETTER X WITH DOT ABOVE */, + ibreve = 0x100012d /* U+012D LATIN SMALL LETTER I WITH BREVE */, + zstroke = 0x10001b6 /* U+01B6 LATIN SMALL LETTER Z WITH STROKE */, + gcaron = 0x10001e7 /* U+01E7 LATIN SMALL LETTER G WITH CARON */, + ocaron = 0x10001d2 /* U+01D2 LATIN SMALL LETTER O WITH CARON */, + obarred = 0x1000275 /* U+0275 LATIN SMALL LETTER BARRED O */, + SCHWA = 0x100018f /* U+018F LATIN CAPITAL LETTER SCHWA */, + schwa = 0x1000259 /* U+0259 LATIN SMALL LETTER SCHWA */, + EZH = 0x10001b7 /* U+01B7 LATIN CAPITAL LETTER EZH */, + ezh = 0x1000292 /* U+0292 LATIN SMALL LETTER EZH */, + Lbelowdot = 0x1001e36 /* U+1E36 LATIN CAPITAL LETTER L WITH DOT BELOW */, + lbelowdot = 0x1001e37 /* U+1E37 LATIN SMALL LETTER L WITH DOT BELOW */, + Abelowdot = 0x1001ea0 /* U+1EA0 LATIN CAPITAL LETTER A WITH DOT BELOW */, + abelowdot = 0x1001ea1 /* U+1EA1 LATIN SMALL LETTER A WITH DOT BELOW */, + Ahook = 0x1001ea2 /* U+1EA2 LATIN CAPITAL LETTER A WITH HOOK ABOVE */, + ahook = 0x1001ea3 /* U+1EA3 LATIN SMALL LETTER A WITH HOOK ABOVE */, + Acircumflexacute = 0x1001ea4 /* U+1EA4 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE */, + acircumflexacute = 0x1001ea5 /* U+1EA5 LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE */, + Acircumflexgrave = 0x1001ea6 /* U+1EA6 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE */, + acircumflexgrave = 0x1001ea7 /* U+1EA7 LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE */, + Acircumflexhook = 0x1001ea8 /* U+1EA8 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */, + acircumflexhook = 0x1001ea9 /* U+1EA9 LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */, + Acircumflextilde = 0x1001eaa /* U+1EAA LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE */, + acircumflextilde = 0x1001eab /* U+1EAB LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE */, + Acircumflexbelowdot = 0x1001eac /* U+1EAC LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW */, + acircumflexbelowdot = 0x1001ead /* U+1EAD LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW */, + Abreveacute = 0x1001eae /* U+1EAE LATIN CAPITAL LETTER A WITH BREVE AND ACUTE */, + abreveacute = 0x1001eaf /* U+1EAF LATIN SMALL LETTER A WITH BREVE AND ACUTE */, + Abrevegrave = 0x1001eb0 /* U+1EB0 LATIN CAPITAL LETTER A WITH BREVE AND GRAVE */, + abrevegrave = 0x1001eb1 /* U+1EB1 LATIN SMALL LETTER A WITH BREVE AND GRAVE */, + Abrevehook = 0x1001eb2 /* U+1EB2 LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE */, + abrevehook = 0x1001eb3 /* U+1EB3 LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE */, + Abrevetilde = 0x1001eb4 /* U+1EB4 LATIN CAPITAL LETTER A WITH BREVE AND TILDE */, + abrevetilde = 0x1001eb5 /* U+1EB5 LATIN SMALL LETTER A WITH BREVE AND TILDE */, + Abrevebelowdot = 0x1001eb6 /* U+1EB6 LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW */, + abrevebelowdot = 0x1001eb7 /* U+1EB7 LATIN SMALL LETTER A WITH BREVE AND DOT BELOW */, + Ebelowdot = 0x1001eb8 /* U+1EB8 LATIN CAPITAL LETTER E WITH DOT BELOW */, + ebelowdot = 0x1001eb9 /* U+1EB9 LATIN SMALL LETTER E WITH DOT BELOW */, + Ehook = 0x1001eba /* U+1EBA LATIN CAPITAL LETTER E WITH HOOK ABOVE */, + ehook = 0x1001ebb /* U+1EBB LATIN SMALL LETTER E WITH HOOK ABOVE */, + Etilde = 0x1001ebc /* U+1EBC LATIN CAPITAL LETTER E WITH TILDE */, + etilde = 0x1001ebd /* U+1EBD LATIN SMALL LETTER E WITH TILDE */, + Ecircumflexacute = 0x1001ebe /* U+1EBE LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE */, + ecircumflexacute = 0x1001ebf /* U+1EBF LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE */, + Ecircumflexgrave = 0x1001ec0 /* U+1EC0 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE */, + ecircumflexgrave = 0x1001ec1 /* U+1EC1 LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE */, + Ecircumflexhook = 0x1001ec2 /* U+1EC2 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */, + ecircumflexhook = 0x1001ec3 /* U+1EC3 LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */, + Ecircumflextilde = 0x1001ec4 /* U+1EC4 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE */, + ecircumflextilde = 0x1001ec5 /* U+1EC5 LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE */, + Ecircumflexbelowdot = 0x1001ec6 /* U+1EC6 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW */, + ecircumflexbelowdot = 0x1001ec7 /* U+1EC7 LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW */, + Ihook = 0x1001ec8 /* U+1EC8 LATIN CAPITAL LETTER I WITH HOOK ABOVE */, + ihook = 0x1001ec9 /* U+1EC9 LATIN SMALL LETTER I WITH HOOK ABOVE */, + Ibelowdot = 0x1001eca /* U+1ECA LATIN CAPITAL LETTER I WITH DOT BELOW */, + ibelowdot = 0x1001ecb /* U+1ECB LATIN SMALL LETTER I WITH DOT BELOW */, + Obelowdot = 0x1001ecc /* U+1ECC LATIN CAPITAL LETTER O WITH DOT BELOW */, + obelowdot = 0x1001ecd /* U+1ECD LATIN SMALL LETTER O WITH DOT BELOW */, + Ohook = 0x1001ece /* U+1ECE LATIN CAPITAL LETTER O WITH HOOK ABOVE */, + ohook = 0x1001ecf /* U+1ECF LATIN SMALL LETTER O WITH HOOK ABOVE */, + Ocircumflexacute = 0x1001ed0 /* U+1ED0 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE */, + ocircumflexacute = 0x1001ed1 /* U+1ED1 LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE */, + Ocircumflexgrave = 0x1001ed2 /* U+1ED2 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE */, + ocircumflexgrave = 0x1001ed3 /* U+1ED3 LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE */, + Ocircumflexhook = 0x1001ed4 /* U+1ED4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */, + ocircumflexhook = 0x1001ed5 /* U+1ED5 LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */, + Ocircumflextilde = 0x1001ed6 /* U+1ED6 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE */, + ocircumflextilde = 0x1001ed7 /* U+1ED7 LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE */, + Ocircumflexbelowdot = 0x1001ed8 /* U+1ED8 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW */, + ocircumflexbelowdot = 0x1001ed9 /* U+1ED9 LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW */, + Ohornacute = 0x1001eda /* U+1EDA LATIN CAPITAL LETTER O WITH HORN AND ACUTE */, + ohornacute = 0x1001edb /* U+1EDB LATIN SMALL LETTER O WITH HORN AND ACUTE */, + Ohorngrave = 0x1001edc /* U+1EDC LATIN CAPITAL LETTER O WITH HORN AND GRAVE */, + ohorngrave = 0x1001edd /* U+1EDD LATIN SMALL LETTER O WITH HORN AND GRAVE */, + Ohornhook = 0x1001ede /* U+1EDE LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE */, + ohornhook = 0x1001edf /* U+1EDF LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE */, + Ohorntilde = 0x1001ee0 /* U+1EE0 LATIN CAPITAL LETTER O WITH HORN AND TILDE */, + ohorntilde = 0x1001ee1 /* U+1EE1 LATIN SMALL LETTER O WITH HORN AND TILDE */, + Ohornbelowdot = 0x1001ee2 /* U+1EE2 LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW */, + ohornbelowdot = 0x1001ee3 /* U+1EE3 LATIN SMALL LETTER O WITH HORN AND DOT BELOW */, + Ubelowdot = 0x1001ee4 /* U+1EE4 LATIN CAPITAL LETTER U WITH DOT BELOW */, + ubelowdot = 0x1001ee5 /* U+1EE5 LATIN SMALL LETTER U WITH DOT BELOW */, + Uhook = 0x1001ee6 /* U+1EE6 LATIN CAPITAL LETTER U WITH HOOK ABOVE */, + uhook = 0x1001ee7 /* U+1EE7 LATIN SMALL LETTER U WITH HOOK ABOVE */, + Uhornacute = 0x1001ee8 /* U+1EE8 LATIN CAPITAL LETTER U WITH HORN AND ACUTE */, + uhornacute = 0x1001ee9 /* U+1EE9 LATIN SMALL LETTER U WITH HORN AND ACUTE */, + Uhorngrave = 0x1001eea /* U+1EEA LATIN CAPITAL LETTER U WITH HORN AND GRAVE */, + uhorngrave = 0x1001eeb /* U+1EEB LATIN SMALL LETTER U WITH HORN AND GRAVE */, + Uhornhook = 0x1001eec /* U+1EEC LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE */, + uhornhook = 0x1001eed /* U+1EED LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE */, + Uhorntilde = 0x1001eee /* U+1EEE LATIN CAPITAL LETTER U WITH HORN AND TILDE */, + uhorntilde = 0x1001eef /* U+1EEF LATIN SMALL LETTER U WITH HORN AND TILDE */, + Uhornbelowdot = 0x1001ef0 /* U+1EF0 LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW */, + uhornbelowdot = 0x1001ef1 /* U+1EF1 LATIN SMALL LETTER U WITH HORN AND DOT BELOW */, + Ybelowdot = 0x1001ef4 /* U+1EF4 LATIN CAPITAL LETTER Y WITH DOT BELOW */, + ybelowdot = 0x1001ef5 /* U+1EF5 LATIN SMALL LETTER Y WITH DOT BELOW */, + Yhook = 0x1001ef6 /* U+1EF6 LATIN CAPITAL LETTER Y WITH HOOK ABOVE */, + yhook = 0x1001ef7 /* U+1EF7 LATIN SMALL LETTER Y WITH HOOK ABOVE */, + Ytilde = 0x1001ef8 /* U+1EF8 LATIN CAPITAL LETTER Y WITH TILDE */, + ytilde = 0x1001ef9 /* U+1EF9 LATIN SMALL LETTER Y WITH TILDE */, + Ohorn = 0x10001a0 /* U+01A0 LATIN CAPITAL LETTER O WITH HORN */, + ohorn = 0x10001a1 /* U+01A1 LATIN SMALL LETTER O WITH HORN */, + Uhorn = 0x10001af /* U+01AF LATIN CAPITAL LETTER U WITH HORN */, + uhorn = 0x10001b0 /* U+01B0 LATIN SMALL LETTER U WITH HORN */, + EcuSign = 0x10020a0 /* U+20A0 EURO-CURRENCY SIGN */, + ColonSign = 0x10020a1 /* U+20A1 COLON SIGN */, + CruzeiroSign = 0x10020a2 /* U+20A2 CRUZEIRO SIGN */, + FFrancSign = 0x10020a3 /* U+20A3 FRENCH FRANC SIGN */, + LiraSign = 0x10020a4 /* U+20A4 LIRA SIGN */, + MillSign = 0x10020a5 /* U+20A5 MILL SIGN */, + NairaSign = 0x10020a6 /* U+20A6 NAIRA SIGN */, + PesetaSign = 0x10020a7 /* U+20A7 PESETA SIGN */, + RupeeSign = 0x10020a8 /* U+20A8 RUPEE SIGN */, + WonSign = 0x10020a9 /* U+20A9 WON SIGN */, + NewSheqelSign = 0x10020aa /* U+20AA NEW SHEQEL SIGN */, + DongSign = 0x10020ab /* U+20AB DONG SIGN */, + EuroSign = 0x20ac /* U+20AC EURO SIGN */, + zerosuperior = 0x1002070 /* U+2070 SUPERSCRIPT ZERO */, + foursuperior = 0x1002074 /* U+2074 SUPERSCRIPT FOUR */, + fivesuperior = 0x1002075 /* U+2075 SUPERSCRIPT FIVE */, + sixsuperior = 0x1002076 /* U+2076 SUPERSCRIPT SIX */, + sevensuperior = 0x1002077 /* U+2077 SUPERSCRIPT SEVEN */, + eightsuperior = 0x1002078 /* U+2078 SUPERSCRIPT EIGHT */, + ninesuperior = 0x1002079 /* U+2079 SUPERSCRIPT NINE */, + zerosubscript = 0x1002080 /* U+2080 SUBSCRIPT ZERO */, + onesubscript = 0x1002081 /* U+2081 SUBSCRIPT ONE */, + twosubscript = 0x1002082 /* U+2082 SUBSCRIPT TWO */, + threesubscript = 0x1002083 /* U+2083 SUBSCRIPT THREE */, + foursubscript = 0x1002084 /* U+2084 SUBSCRIPT FOUR */, + fivesubscript = 0x1002085 /* U+2085 SUBSCRIPT FIVE */, + sixsubscript = 0x1002086 /* U+2086 SUBSCRIPT SIX */, + sevensubscript = 0x1002087 /* U+2087 SUBSCRIPT SEVEN */, + eightsubscript = 0x1002088 /* U+2088 SUBSCRIPT EIGHT */, + ninesubscript = 0x1002089 /* U+2089 SUBSCRIPT NINE */, + partdifferential = 0x1002202 /* U+2202 PARTIAL DIFFERENTIAL */, + emptyset = 0x1002205 /* U+2205 NULL SET */, + elementof = 0x1002208 /* U+2208 ELEMENT OF */, + notelementof = 0x1002209 /* U+2209 NOT AN ELEMENT OF */, + containsas = 0x100220B /* U+220B CONTAINS AS MEMBER */, + squareroot = 0x100221A /* U+221A SQUARE ROOT */, + cuberoot = 0x100221B /* U+221B CUBE ROOT */, + fourthroot = 0x100221C /* U+221C FOURTH ROOT */, + dintegral = 0x100222C /* U+222C DOUBLE INTEGRAL */, + tintegral = 0x100222D /* U+222D TRIPLE INTEGRAL */, + because = 0x1002235 /* U+2235 BECAUSE */, + approxeq = 0x1002248 /* U+2245 ALMOST EQUAL TO */, + notapproxeq = 0x1002247 /* U+2247 NOT ALMOST EQUAL TO */, + notidentical = 0x1002262 /* U+2262 NOT IDENTICAL TO */, + stricteq = 0x1002263 /* U+2263 STRICTLY EQUIVALENT TO */, + braille_dot_1 = 0xfff1, + braille_dot_2 = 0xfff2, + braille_dot_3 = 0xfff3, + braille_dot_4 = 0xfff4, + braille_dot_5 = 0xfff5, + braille_dot_6 = 0xfff6, + braille_dot_7 = 0xfff7, + braille_dot_8 = 0xfff8, + braille_dot_9 = 0xfff9, + braille_dot_10 = 0xfffa, + braille_blank = 0x1002800 /* U+2800 BRAILLE PATTERN BLANK */, + braille_dots_1 = 0x1002801 /* U+2801 BRAILLE PATTERN DOTS-1 */, + braille_dots_2 = 0x1002802 /* U+2802 BRAILLE PATTERN DOTS-2 */, + braille_dots_12 = 0x1002803 /* U+2803 BRAILLE PATTERN DOTS-12 */, + braille_dots_3 = 0x1002804 /* U+2804 BRAILLE PATTERN DOTS-3 */, + braille_dots_13 = 0x1002805 /* U+2805 BRAILLE PATTERN DOTS-13 */, + braille_dots_23 = 0x1002806 /* U+2806 BRAILLE PATTERN DOTS-23 */, + braille_dots_123 = 0x1002807 /* U+2807 BRAILLE PATTERN DOTS-123 */, + braille_dots_4 = 0x1002808 /* U+2808 BRAILLE PATTERN DOTS-4 */, + braille_dots_14 = 0x1002809 /* U+2809 BRAILLE PATTERN DOTS-14 */, + braille_dots_24 = 0x100280a /* U+280a BRAILLE PATTERN DOTS-24 */, + braille_dots_124 = 0x100280b /* U+280b BRAILLE PATTERN DOTS-124 */, + braille_dots_34 = 0x100280c /* U+280c BRAILLE PATTERN DOTS-34 */, + braille_dots_134 = 0x100280d /* U+280d BRAILLE PATTERN DOTS-134 */, + braille_dots_234 = 0x100280e /* U+280e BRAILLE PATTERN DOTS-234 */, + braille_dots_1234 = 0x100280f /* U+280f BRAILLE PATTERN DOTS-1234 */, + braille_dots_5 = 0x1002810 /* U+2810 BRAILLE PATTERN DOTS-5 */, + braille_dots_15 = 0x1002811 /* U+2811 BRAILLE PATTERN DOTS-15 */, + braille_dots_25 = 0x1002812 /* U+2812 BRAILLE PATTERN DOTS-25 */, + braille_dots_125 = 0x1002813 /* U+2813 BRAILLE PATTERN DOTS-125 */, + braille_dots_35 = 0x1002814 /* U+2814 BRAILLE PATTERN DOTS-35 */, + braille_dots_135 = 0x1002815 /* U+2815 BRAILLE PATTERN DOTS-135 */, + braille_dots_235 = 0x1002816 /* U+2816 BRAILLE PATTERN DOTS-235 */, + braille_dots_1235 = 0x1002817 /* U+2817 BRAILLE PATTERN DOTS-1235 */, + braille_dots_45 = 0x1002818 /* U+2818 BRAILLE PATTERN DOTS-45 */, + braille_dots_145 = 0x1002819 /* U+2819 BRAILLE PATTERN DOTS-145 */, + braille_dots_245 = 0x100281a /* U+281a BRAILLE PATTERN DOTS-245 */, + braille_dots_1245 = 0x100281b /* U+281b BRAILLE PATTERN DOTS-1245 */, + braille_dots_345 = 0x100281c /* U+281c BRAILLE PATTERN DOTS-345 */, + braille_dots_1345 = 0x100281d /* U+281d BRAILLE PATTERN DOTS-1345 */, + braille_dots_2345 = 0x100281e /* U+281e BRAILLE PATTERN DOTS-2345 */, + braille_dots_12345 = 0x100281f /* U+281f BRAILLE PATTERN DOTS-12345 */, + braille_dots_6 = 0x1002820 /* U+2820 BRAILLE PATTERN DOTS-6 */, + braille_dots_16 = 0x1002821 /* U+2821 BRAILLE PATTERN DOTS-16 */, + braille_dots_26 = 0x1002822 /* U+2822 BRAILLE PATTERN DOTS-26 */, + braille_dots_126 = 0x1002823 /* U+2823 BRAILLE PATTERN DOTS-126 */, + braille_dots_36 = 0x1002824 /* U+2824 BRAILLE PATTERN DOTS-36 */, + braille_dots_136 = 0x1002825 /* U+2825 BRAILLE PATTERN DOTS-136 */, + braille_dots_236 = 0x1002826 /* U+2826 BRAILLE PATTERN DOTS-236 */, + braille_dots_1236 = 0x1002827 /* U+2827 BRAILLE PATTERN DOTS-1236 */, + braille_dots_46 = 0x1002828 /* U+2828 BRAILLE PATTERN DOTS-46 */, + braille_dots_146 = 0x1002829 /* U+2829 BRAILLE PATTERN DOTS-146 */, + braille_dots_246 = 0x100282a /* U+282a BRAILLE PATTERN DOTS-246 */, + braille_dots_1246 = 0x100282b /* U+282b BRAILLE PATTERN DOTS-1246 */, + braille_dots_346 = 0x100282c /* U+282c BRAILLE PATTERN DOTS-346 */, + braille_dots_1346 = 0x100282d /* U+282d BRAILLE PATTERN DOTS-1346 */, + braille_dots_2346 = 0x100282e /* U+282e BRAILLE PATTERN DOTS-2346 */, + braille_dots_12346 = 0x100282f /* U+282f BRAILLE PATTERN DOTS-12346 */, + braille_dots_56 = 0x1002830 /* U+2830 BRAILLE PATTERN DOTS-56 */, + braille_dots_156 = 0x1002831 /* U+2831 BRAILLE PATTERN DOTS-156 */, + braille_dots_256 = 0x1002832 /* U+2832 BRAILLE PATTERN DOTS-256 */, + braille_dots_1256 = 0x1002833 /* U+2833 BRAILLE PATTERN DOTS-1256 */, + braille_dots_356 = 0x1002834 /* U+2834 BRAILLE PATTERN DOTS-356 */, + braille_dots_1356 = 0x1002835 /* U+2835 BRAILLE PATTERN DOTS-1356 */, + braille_dots_2356 = 0x1002836 /* U+2836 BRAILLE PATTERN DOTS-2356 */, + braille_dots_12356 = 0x1002837 /* U+2837 BRAILLE PATTERN DOTS-12356 */, + braille_dots_456 = 0x1002838 /* U+2838 BRAILLE PATTERN DOTS-456 */, + braille_dots_1456 = 0x1002839 /* U+2839 BRAILLE PATTERN DOTS-1456 */, + braille_dots_2456 = 0x100283a /* U+283a BRAILLE PATTERN DOTS-2456 */, + braille_dots_12456 = 0x100283b /* U+283b BRAILLE PATTERN DOTS-12456 */, + braille_dots_3456 = 0x100283c /* U+283c BRAILLE PATTERN DOTS-3456 */, + braille_dots_13456 = 0x100283d /* U+283d BRAILLE PATTERN DOTS-13456 */, + braille_dots_23456 = 0x100283e /* U+283e BRAILLE PATTERN DOTS-23456 */, + braille_dots_123456 = 0x100283f /* U+283f BRAILLE PATTERN DOTS-123456 */, + braille_dots_7 = 0x1002840 /* U+2840 BRAILLE PATTERN DOTS-7 */, + braille_dots_17 = 0x1002841 /* U+2841 BRAILLE PATTERN DOTS-17 */, + braille_dots_27 = 0x1002842 /* U+2842 BRAILLE PATTERN DOTS-27 */, + braille_dots_127 = 0x1002843 /* U+2843 BRAILLE PATTERN DOTS-127 */, + braille_dots_37 = 0x1002844 /* U+2844 BRAILLE PATTERN DOTS-37 */, + braille_dots_137 = 0x1002845 /* U+2845 BRAILLE PATTERN DOTS-137 */, + braille_dots_237 = 0x1002846 /* U+2846 BRAILLE PATTERN DOTS-237 */, + braille_dots_1237 = 0x1002847 /* U+2847 BRAILLE PATTERN DOTS-1237 */, + braille_dots_47 = 0x1002848 /* U+2848 BRAILLE PATTERN DOTS-47 */, + braille_dots_147 = 0x1002849 /* U+2849 BRAILLE PATTERN DOTS-147 */, + braille_dots_247 = 0x100284a /* U+284a BRAILLE PATTERN DOTS-247 */, + braille_dots_1247 = 0x100284b /* U+284b BRAILLE PATTERN DOTS-1247 */, + braille_dots_347 = 0x100284c /* U+284c BRAILLE PATTERN DOTS-347 */, + braille_dots_1347 = 0x100284d /* U+284d BRAILLE PATTERN DOTS-1347 */, + braille_dots_2347 = 0x100284e /* U+284e BRAILLE PATTERN DOTS-2347 */, + braille_dots_12347 = 0x100284f /* U+284f BRAILLE PATTERN DOTS-12347 */, + braille_dots_57 = 0x1002850 /* U+2850 BRAILLE PATTERN DOTS-57 */, + braille_dots_157 = 0x1002851 /* U+2851 BRAILLE PATTERN DOTS-157 */, + braille_dots_257 = 0x1002852 /* U+2852 BRAILLE PATTERN DOTS-257 */, + braille_dots_1257 = 0x1002853 /* U+2853 BRAILLE PATTERN DOTS-1257 */, + braille_dots_357 = 0x1002854 /* U+2854 BRAILLE PATTERN DOTS-357 */, + braille_dots_1357 = 0x1002855 /* U+2855 BRAILLE PATTERN DOTS-1357 */, + braille_dots_2357 = 0x1002856 /* U+2856 BRAILLE PATTERN DOTS-2357 */, + braille_dots_12357 = 0x1002857 /* U+2857 BRAILLE PATTERN DOTS-12357 */, + braille_dots_457 = 0x1002858 /* U+2858 BRAILLE PATTERN DOTS-457 */, + braille_dots_1457 = 0x1002859 /* U+2859 BRAILLE PATTERN DOTS-1457 */, + braille_dots_2457 = 0x100285a /* U+285a BRAILLE PATTERN DOTS-2457 */, + braille_dots_12457 = 0x100285b /* U+285b BRAILLE PATTERN DOTS-12457 */, + braille_dots_3457 = 0x100285c /* U+285c BRAILLE PATTERN DOTS-3457 */, + braille_dots_13457 = 0x100285d /* U+285d BRAILLE PATTERN DOTS-13457 */, + braille_dots_23457 = 0x100285e /* U+285e BRAILLE PATTERN DOTS-23457 */, + braille_dots_123457 = 0x100285f /* U+285f BRAILLE PATTERN DOTS-123457 */, + braille_dots_67 = 0x1002860 /* U+2860 BRAILLE PATTERN DOTS-67 */, + braille_dots_167 = 0x1002861 /* U+2861 BRAILLE PATTERN DOTS-167 */, + braille_dots_267 = 0x1002862 /* U+2862 BRAILLE PATTERN DOTS-267 */, + braille_dots_1267 = 0x1002863 /* U+2863 BRAILLE PATTERN DOTS-1267 */, + braille_dots_367 = 0x1002864 /* U+2864 BRAILLE PATTERN DOTS-367 */, + braille_dots_1367 = 0x1002865 /* U+2865 BRAILLE PATTERN DOTS-1367 */, + braille_dots_2367 = 0x1002866 /* U+2866 BRAILLE PATTERN DOTS-2367 */, + braille_dots_12367 = 0x1002867 /* U+2867 BRAILLE PATTERN DOTS-12367 */, + braille_dots_467 = 0x1002868 /* U+2868 BRAILLE PATTERN DOTS-467 */, + braille_dots_1467 = 0x1002869 /* U+2869 BRAILLE PATTERN DOTS-1467 */, + braille_dots_2467 = 0x100286a /* U+286a BRAILLE PATTERN DOTS-2467 */, + braille_dots_12467 = 0x100286b /* U+286b BRAILLE PATTERN DOTS-12467 */, + braille_dots_3467 = 0x100286c /* U+286c BRAILLE PATTERN DOTS-3467 */, + braille_dots_13467 = 0x100286d /* U+286d BRAILLE PATTERN DOTS-13467 */, + braille_dots_23467 = 0x100286e /* U+286e BRAILLE PATTERN DOTS-23467 */, + braille_dots_123467 = 0x100286f /* U+286f BRAILLE PATTERN DOTS-123467 */, + braille_dots_567 = 0x1002870 /* U+2870 BRAILLE PATTERN DOTS-567 */, + braille_dots_1567 = 0x1002871 /* U+2871 BRAILLE PATTERN DOTS-1567 */, + braille_dots_2567 = 0x1002872 /* U+2872 BRAILLE PATTERN DOTS-2567 */, + braille_dots_12567 = 0x1002873 /* U+2873 BRAILLE PATTERN DOTS-12567 */, + braille_dots_3567 = 0x1002874 /* U+2874 BRAILLE PATTERN DOTS-3567 */, + braille_dots_13567 = 0x1002875 /* U+2875 BRAILLE PATTERN DOTS-13567 */, + braille_dots_23567 = 0x1002876 /* U+2876 BRAILLE PATTERN DOTS-23567 */, + braille_dots_123567 = 0x1002877 /* U+2877 BRAILLE PATTERN DOTS-123567 */, + braille_dots_4567 = 0x1002878 /* U+2878 BRAILLE PATTERN DOTS-4567 */, + braille_dots_14567 = 0x1002879 /* U+2879 BRAILLE PATTERN DOTS-14567 */, + braille_dots_24567 = 0x100287a /* U+287a BRAILLE PATTERN DOTS-24567 */, + braille_dots_124567 = 0x100287b /* U+287b BRAILLE PATTERN DOTS-124567 */, + braille_dots_34567 = 0x100287c /* U+287c BRAILLE PATTERN DOTS-34567 */, + braille_dots_134567 = 0x100287d /* U+287d BRAILLE PATTERN DOTS-134567 */, + braille_dots_234567 = 0x100287e /* U+287e BRAILLE PATTERN DOTS-234567 */, + braille_dots_1234567 = 0x100287f /* U+287f BRAILLE PATTERN DOTS-1234567 */, + braille_dots_8 = 0x1002880 /* U+2880 BRAILLE PATTERN DOTS-8 */, + braille_dots_18 = 0x1002881 /* U+2881 BRAILLE PATTERN DOTS-18 */, + braille_dots_28 = 0x1002882 /* U+2882 BRAILLE PATTERN DOTS-28 */, + braille_dots_128 = 0x1002883 /* U+2883 BRAILLE PATTERN DOTS-128 */, + braille_dots_38 = 0x1002884 /* U+2884 BRAILLE PATTERN DOTS-38 */, + braille_dots_138 = 0x1002885 /* U+2885 BRAILLE PATTERN DOTS-138 */, + braille_dots_238 = 0x1002886 /* U+2886 BRAILLE PATTERN DOTS-238 */, + braille_dots_1238 = 0x1002887 /* U+2887 BRAILLE PATTERN DOTS-1238 */, + braille_dots_48 = 0x1002888 /* U+2888 BRAILLE PATTERN DOTS-48 */, + braille_dots_148 = 0x1002889 /* U+2889 BRAILLE PATTERN DOTS-148 */, + braille_dots_248 = 0x100288a /* U+288a BRAILLE PATTERN DOTS-248 */, + braille_dots_1248 = 0x100288b /* U+288b BRAILLE PATTERN DOTS-1248 */, + braille_dots_348 = 0x100288c /* U+288c BRAILLE PATTERN DOTS-348 */, + braille_dots_1348 = 0x100288d /* U+288d BRAILLE PATTERN DOTS-1348 */, + braille_dots_2348 = 0x100288e /* U+288e BRAILLE PATTERN DOTS-2348 */, + braille_dots_12348 = 0x100288f /* U+288f BRAILLE PATTERN DOTS-12348 */, + braille_dots_58 = 0x1002890 /* U+2890 BRAILLE PATTERN DOTS-58 */, + braille_dots_158 = 0x1002891 /* U+2891 BRAILLE PATTERN DOTS-158 */, + braille_dots_258 = 0x1002892 /* U+2892 BRAILLE PATTERN DOTS-258 */, + braille_dots_1258 = 0x1002893 /* U+2893 BRAILLE PATTERN DOTS-1258 */, + braille_dots_358 = 0x1002894 /* U+2894 BRAILLE PATTERN DOTS-358 */, + braille_dots_1358 = 0x1002895 /* U+2895 BRAILLE PATTERN DOTS-1358 */, + braille_dots_2358 = 0x1002896 /* U+2896 BRAILLE PATTERN DOTS-2358 */, + braille_dots_12358 = 0x1002897 /* U+2897 BRAILLE PATTERN DOTS-12358 */, + braille_dots_458 = 0x1002898 /* U+2898 BRAILLE PATTERN DOTS-458 */, + braille_dots_1458 = 0x1002899 /* U+2899 BRAILLE PATTERN DOTS-1458 */, + braille_dots_2458 = 0x100289a /* U+289a BRAILLE PATTERN DOTS-2458 */, + braille_dots_12458 = 0x100289b /* U+289b BRAILLE PATTERN DOTS-12458 */, + braille_dots_3458 = 0x100289c /* U+289c BRAILLE PATTERN DOTS-3458 */, + braille_dots_13458 = 0x100289d /* U+289d BRAILLE PATTERN DOTS-13458 */, + braille_dots_23458 = 0x100289e /* U+289e BRAILLE PATTERN DOTS-23458 */, + braille_dots_123458 = 0x100289f /* U+289f BRAILLE PATTERN DOTS-123458 */, + braille_dots_68 = 0x10028a0 /* U+28a0 BRAILLE PATTERN DOTS-68 */, + braille_dots_168 = 0x10028a1 /* U+28a1 BRAILLE PATTERN DOTS-168 */, + braille_dots_268 = 0x10028a2 /* U+28a2 BRAILLE PATTERN DOTS-268 */, + braille_dots_1268 = 0x10028a3 /* U+28a3 BRAILLE PATTERN DOTS-1268 */, + braille_dots_368 = 0x10028a4 /* U+28a4 BRAILLE PATTERN DOTS-368 */, + braille_dots_1368 = 0x10028a5 /* U+28a5 BRAILLE PATTERN DOTS-1368 */, + braille_dots_2368 = 0x10028a6 /* U+28a6 BRAILLE PATTERN DOTS-2368 */, + braille_dots_12368 = 0x10028a7 /* U+28a7 BRAILLE PATTERN DOTS-12368 */, + braille_dots_468 = 0x10028a8 /* U+28a8 BRAILLE PATTERN DOTS-468 */, + braille_dots_1468 = 0x10028a9 /* U+28a9 BRAILLE PATTERN DOTS-1468 */, + braille_dots_2468 = 0x10028aa /* U+28aa BRAILLE PATTERN DOTS-2468 */, + braille_dots_12468 = 0x10028ab /* U+28ab BRAILLE PATTERN DOTS-12468 */, + braille_dots_3468 = 0x10028ac /* U+28ac BRAILLE PATTERN DOTS-3468 */, + braille_dots_13468 = 0x10028ad /* U+28ad BRAILLE PATTERN DOTS-13468 */, + braille_dots_23468 = 0x10028ae /* U+28ae BRAILLE PATTERN DOTS-23468 */, + braille_dots_123468 = 0x10028af /* U+28af BRAILLE PATTERN DOTS-123468 */, + braille_dots_568 = 0x10028b0 /* U+28b0 BRAILLE PATTERN DOTS-568 */, + braille_dots_1568 = 0x10028b1 /* U+28b1 BRAILLE PATTERN DOTS-1568 */, + braille_dots_2568 = 0x10028b2 /* U+28b2 BRAILLE PATTERN DOTS-2568 */, + braille_dots_12568 = 0x10028b3 /* U+28b3 BRAILLE PATTERN DOTS-12568 */, + braille_dots_3568 = 0x10028b4 /* U+28b4 BRAILLE PATTERN DOTS-3568 */, + braille_dots_13568 = 0x10028b5 /* U+28b5 BRAILLE PATTERN DOTS-13568 */, + braille_dots_23568 = 0x10028b6 /* U+28b6 BRAILLE PATTERN DOTS-23568 */, + braille_dots_123568 = 0x10028b7 /* U+28b7 BRAILLE PATTERN DOTS-123568 */, + braille_dots_4568 = 0x10028b8 /* U+28b8 BRAILLE PATTERN DOTS-4568 */, + braille_dots_14568 = 0x10028b9 /* U+28b9 BRAILLE PATTERN DOTS-14568 */, + braille_dots_24568 = 0x10028ba /* U+28ba BRAILLE PATTERN DOTS-24568 */, + braille_dots_124568 = 0x10028bb /* U+28bb BRAILLE PATTERN DOTS-124568 */, + braille_dots_34568 = 0x10028bc /* U+28bc BRAILLE PATTERN DOTS-34568 */, + braille_dots_134568 = 0x10028bd /* U+28bd BRAILLE PATTERN DOTS-134568 */, + braille_dots_234568 = 0x10028be /* U+28be BRAILLE PATTERN DOTS-234568 */, + braille_dots_1234568 = 0x10028bf /* U+28bf BRAILLE PATTERN DOTS-1234568 */, + braille_dots_78 = 0x10028c0 /* U+28c0 BRAILLE PATTERN DOTS-78 */, + braille_dots_178 = 0x10028c1 /* U+28c1 BRAILLE PATTERN DOTS-178 */, + braille_dots_278 = 0x10028c2 /* U+28c2 BRAILLE PATTERN DOTS-278 */, + braille_dots_1278 = 0x10028c3 /* U+28c3 BRAILLE PATTERN DOTS-1278 */, + braille_dots_378 = 0x10028c4 /* U+28c4 BRAILLE PATTERN DOTS-378 */, + braille_dots_1378 = 0x10028c5 /* U+28c5 BRAILLE PATTERN DOTS-1378 */, + braille_dots_2378 = 0x10028c6 /* U+28c6 BRAILLE PATTERN DOTS-2378 */, + braille_dots_12378 = 0x10028c7 /* U+28c7 BRAILLE PATTERN DOTS-12378 */, + braille_dots_478 = 0x10028c8 /* U+28c8 BRAILLE PATTERN DOTS-478 */, + braille_dots_1478 = 0x10028c9 /* U+28c9 BRAILLE PATTERN DOTS-1478 */, + braille_dots_2478 = 0x10028ca /* U+28ca BRAILLE PATTERN DOTS-2478 */, + braille_dots_12478 = 0x10028cb /* U+28cb BRAILLE PATTERN DOTS-12478 */, + braille_dots_3478 = 0x10028cc /* U+28cc BRAILLE PATTERN DOTS-3478 */, + braille_dots_13478 = 0x10028cd /* U+28cd BRAILLE PATTERN DOTS-13478 */, + braille_dots_23478 = 0x10028ce /* U+28ce BRAILLE PATTERN DOTS-23478 */, + braille_dots_123478 = 0x10028cf /* U+28cf BRAILLE PATTERN DOTS-123478 */, + braille_dots_578 = 0x10028d0 /* U+28d0 BRAILLE PATTERN DOTS-578 */, + braille_dots_1578 = 0x10028d1 /* U+28d1 BRAILLE PATTERN DOTS-1578 */, + braille_dots_2578 = 0x10028d2 /* U+28d2 BRAILLE PATTERN DOTS-2578 */, + braille_dots_12578 = 0x10028d3 /* U+28d3 BRAILLE PATTERN DOTS-12578 */, + braille_dots_3578 = 0x10028d4 /* U+28d4 BRAILLE PATTERN DOTS-3578 */, + braille_dots_13578 = 0x10028d5 /* U+28d5 BRAILLE PATTERN DOTS-13578 */, + braille_dots_23578 = 0x10028d6 /* U+28d6 BRAILLE PATTERN DOTS-23578 */, + braille_dots_123578 = 0x10028d7 /* U+28d7 BRAILLE PATTERN DOTS-123578 */, + braille_dots_4578 = 0x10028d8 /* U+28d8 BRAILLE PATTERN DOTS-4578 */, + braille_dots_14578 = 0x10028d9 /* U+28d9 BRAILLE PATTERN DOTS-14578 */, + braille_dots_24578 = 0x10028da /* U+28da BRAILLE PATTERN DOTS-24578 */, + braille_dots_124578 = 0x10028db /* U+28db BRAILLE PATTERN DOTS-124578 */, + braille_dots_34578 = 0x10028dc /* U+28dc BRAILLE PATTERN DOTS-34578 */, + braille_dots_134578 = 0x10028dd /* U+28dd BRAILLE PATTERN DOTS-134578 */, + braille_dots_234578 = 0x10028de /* U+28de BRAILLE PATTERN DOTS-234578 */, + braille_dots_1234578 = 0x10028df /* U+28df BRAILLE PATTERN DOTS-1234578 */, + braille_dots_678 = 0x10028e0 /* U+28e0 BRAILLE PATTERN DOTS-678 */, + braille_dots_1678 = 0x10028e1 /* U+28e1 BRAILLE PATTERN DOTS-1678 */, + braille_dots_2678 = 0x10028e2 /* U+28e2 BRAILLE PATTERN DOTS-2678 */, + braille_dots_12678 = 0x10028e3 /* U+28e3 BRAILLE PATTERN DOTS-12678 */, + braille_dots_3678 = 0x10028e4 /* U+28e4 BRAILLE PATTERN DOTS-3678 */, + braille_dots_13678 = 0x10028e5 /* U+28e5 BRAILLE PATTERN DOTS-13678 */, + braille_dots_23678 = 0x10028e6 /* U+28e6 BRAILLE PATTERN DOTS-23678 */, + braille_dots_123678 = 0x10028e7 /* U+28e7 BRAILLE PATTERN DOTS-123678 */, + braille_dots_4678 = 0x10028e8 /* U+28e8 BRAILLE PATTERN DOTS-4678 */, + braille_dots_14678 = 0x10028e9 /* U+28e9 BRAILLE PATTERN DOTS-14678 */, + braille_dots_24678 = 0x10028ea /* U+28ea BRAILLE PATTERN DOTS-24678 */, + braille_dots_124678 = 0x10028eb /* U+28eb BRAILLE PATTERN DOTS-124678 */, + braille_dots_34678 = 0x10028ec /* U+28ec BRAILLE PATTERN DOTS-34678 */, + braille_dots_134678 = 0x10028ed /* U+28ed BRAILLE PATTERN DOTS-134678 */, + braille_dots_234678 = 0x10028ee /* U+28ee BRAILLE PATTERN DOTS-234678 */, + braille_dots_1234678 = 0x10028ef /* U+28ef BRAILLE PATTERN DOTS-1234678 */, + braille_dots_5678 = 0x10028f0 /* U+28f0 BRAILLE PATTERN DOTS-5678 */, + braille_dots_15678 = 0x10028f1 /* U+28f1 BRAILLE PATTERN DOTS-15678 */, + braille_dots_25678 = 0x10028f2 /* U+28f2 BRAILLE PATTERN DOTS-25678 */, + braille_dots_125678 = 0x10028f3 /* U+28f3 BRAILLE PATTERN DOTS-125678 */, + braille_dots_35678 = 0x10028f4 /* U+28f4 BRAILLE PATTERN DOTS-35678 */, + braille_dots_135678 = 0x10028f5 /* U+28f5 BRAILLE PATTERN DOTS-135678 */, + braille_dots_235678 = 0x10028f6 /* U+28f6 BRAILLE PATTERN DOTS-235678 */, + braille_dots_1235678 = 0x10028f7 /* U+28f7 BRAILLE PATTERN DOTS-1235678 */, + braille_dots_45678 = 0x10028f8 /* U+28f8 BRAILLE PATTERN DOTS-45678 */, + braille_dots_145678 = 0x10028f9 /* U+28f9 BRAILLE PATTERN DOTS-145678 */, + braille_dots_245678 = 0x10028fa /* U+28fa BRAILLE PATTERN DOTS-245678 */, + braille_dots_1245678 = 0x10028fb /* U+28fb BRAILLE PATTERN DOTS-1245678 */, + braille_dots_345678 = 0x10028fc /* U+28fc BRAILLE PATTERN DOTS-345678 */, + braille_dots_1345678 = 0x10028fd /* U+28fd BRAILLE PATTERN DOTS-1345678 */, + braille_dots_2345678 = 0x10028fe /* U+28fe BRAILLE PATTERN DOTS-2345678 */, + braille_dots_12345678 = 0x10028ff /* U+28ff BRAILLE PATTERN DOTS-12345678 */, + Sinh_ng = 0x1000d82 /* U+0D82 SINHALA ANUSVARAYA */, + Sinh_h2 = 0x1000d83 /* U+0D83 SINHALA VISARGAYA */, + Sinh_a = 0x1000d85 /* U+0D85 SINHALA AYANNA */, + Sinh_aa = 0x1000d86 /* U+0D86 SINHALA AAYANNA */, + Sinh_ae = 0x1000d87 /* U+0D87 SINHALA AEYANNA */, + Sinh_aee = 0x1000d88 /* U+0D88 SINHALA AEEYANNA */, + Sinh_i = 0x1000d89 /* U+0D89 SINHALA IYANNA */, + Sinh_ii = 0x1000d8a /* U+0D8A SINHALA IIYANNA */, + Sinh_u = 0x1000d8b /* U+0D8B SINHALA UYANNA */, + Sinh_uu = 0x1000d8c /* U+0D8C SINHALA UUYANNA */, + Sinh_ri = 0x1000d8d /* U+0D8D SINHALA IRUYANNA */, + Sinh_rii = 0x1000d8e /* U+0D8E SINHALA IRUUYANNA */, + Sinh_lu = 0x1000d8f /* U+0D8F SINHALA ILUYANNA */, + Sinh_luu = 0x1000d90 /* U+0D90 SINHALA ILUUYANNA */, + Sinh_e = 0x1000d91 /* U+0D91 SINHALA EYANNA */, + Sinh_ee = 0x1000d92 /* U+0D92 SINHALA EEYANNA */, + Sinh_ai = 0x1000d93 /* U+0D93 SINHALA AIYANNA */, + Sinh_o = 0x1000d94 /* U+0D94 SINHALA OYANNA */, + Sinh_oo = 0x1000d95 /* U+0D95 SINHALA OOYANNA */, + Sinh_au = 0x1000d96 /* U+0D96 SINHALA AUYANNA */, + Sinh_ka = 0x1000d9a /* U+0D9A SINHALA KAYANNA */, + Sinh_kha = 0x1000d9b /* U+0D9B SINHALA MAHA. KAYANNA */, + Sinh_ga = 0x1000d9c /* U+0D9C SINHALA GAYANNA */, + Sinh_gha = 0x1000d9d /* U+0D9D SINHALA MAHA. GAYANNA */, + Sinh_ng2 = 0x1000d9e /* U+0D9E SINHALA KANTAJA NAASIKYAYA */, + Sinh_nga = 0x1000d9f /* U+0D9F SINHALA SANYAKA GAYANNA */, + Sinh_ca = 0x1000da0 /* U+0DA0 SINHALA CAYANNA */, + Sinh_cha = 0x1000da1 /* U+0DA1 SINHALA MAHA. CAYANNA */, + Sinh_ja = 0x1000da2 /* U+0DA2 SINHALA JAYANNA */, + Sinh_jha = 0x1000da3 /* U+0DA3 SINHALA MAHA. JAYANNA */, + Sinh_nya = 0x1000da4 /* U+0DA4 SINHALA TAALUJA NAASIKYAYA */, + Sinh_jnya = 0x1000da5 /* U+0DA5 SINHALA TAALUJA SANYOOGA NAASIKYAYA */, + Sinh_nja = 0x1000da6 /* U+0DA6 SINHALA SANYAKA JAYANNA */, + Sinh_tta = 0x1000da7 /* U+0DA7 SINHALA TTAYANNA */, + Sinh_ttha = 0x1000da8 /* U+0DA8 SINHALA MAHA. TTAYANNA */, + Sinh_dda = 0x1000da9 /* U+0DA9 SINHALA DDAYANNA */, + Sinh_ddha = 0x1000daa /* U+0DAA SINHALA MAHA. DDAYANNA */, + Sinh_nna = 0x1000dab /* U+0DAB SINHALA MUURDHAJA NAYANNA */, + Sinh_ndda = 0x1000dac /* U+0DAC SINHALA SANYAKA DDAYANNA */, + Sinh_tha = 0x1000dad /* U+0DAD SINHALA TAYANNA */, + Sinh_thha = 0x1000dae /* U+0DAE SINHALA MAHA. TAYANNA */, + Sinh_dha = 0x1000daf /* U+0DAF SINHALA DAYANNA */, + Sinh_dhha = 0x1000db0 /* U+0DB0 SINHALA MAHA. DAYANNA */, + Sinh_na = 0x1000db1 /* U+0DB1 SINHALA DANTAJA NAYANNA */, + Sinh_ndha = 0x1000db3 /* U+0DB3 SINHALA SANYAKA DAYANNA */, + Sinh_pa = 0x1000db4 /* U+0DB4 SINHALA PAYANNA */, + Sinh_pha = 0x1000db5 /* U+0DB5 SINHALA MAHA. PAYANNA */, + Sinh_ba = 0x1000db6 /* U+0DB6 SINHALA BAYANNA */, + Sinh_bha = 0x1000db7 /* U+0DB7 SINHALA MAHA. BAYANNA */, + Sinh_ma = 0x1000db8 /* U+0DB8 SINHALA MAYANNA */, + Sinh_mba = 0x1000db9 /* U+0DB9 SINHALA AMBA BAYANNA */, + Sinh_ya = 0x1000dba /* U+0DBA SINHALA YAYANNA */, + Sinh_ra = 0x1000dbb /* U+0DBB SINHALA RAYANNA */, + Sinh_la = 0x1000dbd /* U+0DBD SINHALA DANTAJA LAYANNA */, + Sinh_va = 0x1000dc0 /* U+0DC0 SINHALA VAYANNA */, + Sinh_sha = 0x1000dc1 /* U+0DC1 SINHALA TAALUJA SAYANNA */, + Sinh_ssha = 0x1000dc2 /* U+0DC2 SINHALA MUURDHAJA SAYANNA */, + Sinh_sa = 0x1000dc3 /* U+0DC3 SINHALA DANTAJA SAYANNA */, + Sinh_ha = 0x1000dc4 /* U+0DC4 SINHALA HAYANNA */, + Sinh_lla = 0x1000dc5 /* U+0DC5 SINHALA MUURDHAJA LAYANNA */, + Sinh_fa = 0x1000dc6 /* U+0DC6 SINHALA FAYANNA */, + Sinh_al = 0x1000dca /* U+0DCA SINHALA AL-LAKUNA */, + Sinh_aa2 = 0x1000dcf /* U+0DCF SINHALA AELA-PILLA */, + Sinh_ae2 = 0x1000dd0 /* U+0DD0 SINHALA AEDA-PILLA */, + Sinh_aee2 = 0x1000dd1 /* U+0DD1 SINHALA DIGA AEDA-PILLA */, + Sinh_i2 = 0x1000dd2 /* U+0DD2 SINHALA IS-PILLA */, + Sinh_ii2 = 0x1000dd3 /* U+0DD3 SINHALA DIGA IS-PILLA */, + Sinh_u2 = 0x1000dd4 /* U+0DD4 SINHALA PAA-PILLA */, + Sinh_uu2 = 0x1000dd6 /* U+0DD6 SINHALA DIGA PAA-PILLA */, + Sinh_ru2 = 0x1000dd8 /* U+0DD8 SINHALA GAETTA-PILLA */, + Sinh_e2 = 0x1000dd9 /* U+0DD9 SINHALA KOMBUVA */, + Sinh_ee2 = 0x1000dda /* U+0DDA SINHALA DIGA KOMBUVA */, + Sinh_ai2 = 0x1000ddb /* U+0DDB SINHALA KOMBU DEKA */, + Sinh_o2 = 0x1000ddc /* U+0DDC SINHALA KOMBUVA HAA AELA-PILLA*/, + Sinh_oo2 = 0x1000ddd /* U+0DDD SINHALA KOMBUVA HAA DIGA AELA-PILLA*/, + Sinh_au2 = 0x1000dde /* U+0DDE SINHALA KOMBUVA HAA GAYANUKITTA */, + Sinh_lu2 = 0x1000ddf /* U+0DDF SINHALA GAYANUKITTA */, + Sinh_ruu2 = 0x1000df2 /* U+0DF2 SINHALA DIGA GAETTA-PILLA */, + Sinh_luu2 = 0x1000df3 /* U+0DF3 SINHALA DIGA GAYANUKITTA */, + Sinh_kunddaliya = 0x1000df4 /* U+0DF4 SINHALA KUNDDALIYA */ + } +} diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs new file mode 100644 index 0000000000..ec694ba9a8 --- /dev/null +++ b/src/Avalonia.X11/Stubs.cs @@ -0,0 +1,17 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class PlatformSettingsStub : IPlatformSettings + { + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } +} diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs new file mode 100644 index 0000000000..db74a32b99 --- /dev/null +++ b/src/Avalonia.X11/X11Atoms.cs @@ -0,0 +1,207 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. (http://www.novell.com) +// +// + +using System; +using System.Linq; +using static Avalonia.X11.XLib; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +// ReSharper disable InconsistentNaming +// ReSharper disable StringLiteralTypo +#pragma warning disable 649 + +namespace Avalonia.X11 +{ + + internal class X11Atoms + { + +// Our atoms + public readonly IntPtr AnyPropertyType = (IntPtr)0; + public readonly IntPtr XA_PRIMARY = (IntPtr)1; + public readonly IntPtr XA_SECONDARY = (IntPtr)2; + public readonly IntPtr XA_ARC = (IntPtr)3; + public readonly IntPtr XA_ATOM = (IntPtr)4; + public readonly IntPtr XA_BITMAP = (IntPtr)5; + public readonly IntPtr XA_CARDINAL = (IntPtr)6; + public readonly IntPtr XA_COLORMAP = (IntPtr)7; + public readonly IntPtr XA_CURSOR = (IntPtr)8; + public readonly IntPtr XA_CUT_BUFFER0 = (IntPtr)9; + public readonly IntPtr XA_CUT_BUFFER1 = (IntPtr)10; + public readonly IntPtr XA_CUT_BUFFER2 = (IntPtr)11; + public readonly IntPtr XA_CUT_BUFFER3 = (IntPtr)12; + public readonly IntPtr XA_CUT_BUFFER4 = (IntPtr)13; + public readonly IntPtr XA_CUT_BUFFER5 = (IntPtr)14; + public readonly IntPtr XA_CUT_BUFFER6 = (IntPtr)15; + public readonly IntPtr XA_CUT_BUFFER7 = (IntPtr)16; + public readonly IntPtr XA_DRAWABLE = (IntPtr)17; + public readonly IntPtr XA_FONT = (IntPtr)18; + public readonly IntPtr XA_INTEGER = (IntPtr)19; + public readonly IntPtr XA_PIXMAP = (IntPtr)20; + public readonly IntPtr XA_POINT = (IntPtr)21; + public readonly IntPtr XA_RECTANGLE = (IntPtr)22; + public readonly IntPtr XA_RESOURCE_MANAGER = (IntPtr)23; + public readonly IntPtr XA_RGB_COLOR_MAP = (IntPtr)24; + public readonly IntPtr XA_RGB_BEST_MAP = (IntPtr)25; + public readonly IntPtr XA_RGB_BLUE_MAP = (IntPtr)26; + public readonly IntPtr XA_RGB_DEFAULT_MAP = (IntPtr)27; + public readonly IntPtr XA_RGB_GRAY_MAP = (IntPtr)28; + public readonly IntPtr XA_RGB_GREEN_MAP = (IntPtr)29; + public readonly IntPtr XA_RGB_RED_MAP = (IntPtr)30; + public readonly IntPtr XA_STRING = (IntPtr)31; + public readonly IntPtr XA_VISUALID = (IntPtr)32; + public readonly IntPtr XA_WINDOW = (IntPtr)33; + public readonly IntPtr XA_WM_COMMAND = (IntPtr)34; + public readonly IntPtr XA_WM_HINTS = (IntPtr)35; + public readonly IntPtr XA_WM_CLIENT_MACHINE = (IntPtr)36; + public readonly IntPtr XA_WM_ICON_NAME = (IntPtr)37; + public readonly IntPtr XA_WM_ICON_SIZE = (IntPtr)38; + public readonly IntPtr XA_WM_NAME = (IntPtr)39; + public readonly IntPtr XA_WM_NORMAL_HINTS = (IntPtr)40; + public readonly IntPtr XA_WM_SIZE_HINTS = (IntPtr)41; + public readonly IntPtr XA_WM_ZOOM_HINTS = (IntPtr)42; + public readonly IntPtr XA_MIN_SPACE = (IntPtr)43; + public readonly IntPtr XA_NORM_SPACE = (IntPtr)44; + public readonly IntPtr XA_MAX_SPACE = (IntPtr)45; + public readonly IntPtr XA_END_SPACE = (IntPtr)46; + public readonly IntPtr XA_SUPERSCRIPT_X = (IntPtr)47; + public readonly IntPtr XA_SUPERSCRIPT_Y = (IntPtr)48; + public readonly IntPtr XA_SUBSCRIPT_X = (IntPtr)49; + public readonly IntPtr XA_SUBSCRIPT_Y = (IntPtr)50; + public readonly IntPtr XA_UNDERLINE_POSITION = (IntPtr)51; + public readonly IntPtr XA_UNDERLINE_THICKNESS = (IntPtr)52; + public readonly IntPtr XA_STRIKEOUT_ASCENT = (IntPtr)53; + public readonly IntPtr XA_STRIKEOUT_DESCENT = (IntPtr)54; + public readonly IntPtr XA_ITALIC_ANGLE = (IntPtr)55; + public readonly IntPtr XA_X_HEIGHT = (IntPtr)56; + public readonly IntPtr XA_QUAD_WIDTH = (IntPtr)57; + public readonly IntPtr XA_WEIGHT = (IntPtr)58; + public readonly IntPtr XA_POINT_SIZE = (IntPtr)59; + public readonly IntPtr XA_RESOLUTION = (IntPtr)60; + public readonly IntPtr XA_COPYRIGHT = (IntPtr)61; + public readonly IntPtr XA_NOTICE = (IntPtr)62; + public readonly IntPtr XA_FONT_NAME = (IntPtr)63; + public readonly IntPtr XA_FAMILY_NAME = (IntPtr)64; + public readonly IntPtr XA_FULL_NAME = (IntPtr)65; + public readonly IntPtr XA_CAP_HEIGHT = (IntPtr)66; + public readonly IntPtr XA_WM_CLASS = (IntPtr)67; + public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; + + public readonly IntPtr WM_PROTOCOLS; + public readonly IntPtr WM_DELETE_WINDOW; + public readonly IntPtr WM_TAKE_FOCUS; + public readonly IntPtr _NET_SUPPORTED; + public readonly IntPtr _NET_CLIENT_LIST; + public readonly IntPtr _NET_NUMBER_OF_DESKTOPS; + public readonly IntPtr _NET_DESKTOP_GEOMETRY; + public readonly IntPtr _NET_DESKTOP_VIEWPORT; + public readonly IntPtr _NET_CURRENT_DESKTOP; + public readonly IntPtr _NET_DESKTOP_NAMES; + public readonly IntPtr _NET_ACTIVE_WINDOW; + public readonly IntPtr _NET_WORKAREA; + public readonly IntPtr _NET_SUPPORTING_WM_CHECK; + public readonly IntPtr _NET_VIRTUAL_ROOTS; + public readonly IntPtr _NET_DESKTOP_LAYOUT; + public readonly IntPtr _NET_SHOWING_DESKTOP; + public readonly IntPtr _NET_CLOSE_WINDOW; + public readonly IntPtr _NET_MOVERESIZE_WINDOW; + public readonly IntPtr _NET_WM_MOVERESIZE; + public readonly IntPtr _NET_RESTACK_WINDOW; + public readonly IntPtr _NET_REQUEST_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_NAME; + public readonly IntPtr _NET_WM_VISIBLE_NAME; + public readonly IntPtr _NET_WM_ICON_NAME; + public readonly IntPtr _NET_WM_VISIBLE_ICON_NAME; + public readonly IntPtr _NET_WM_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE; + public readonly IntPtr _NET_WM_STATE; + public readonly IntPtr _NET_WM_ALLOWED_ACTIONS; + public readonly IntPtr _NET_WM_STRUT; + public readonly IntPtr _NET_WM_STRUT_PARTIAL; + public readonly IntPtr _NET_WM_ICON_GEOMETRY; + public readonly IntPtr _NET_WM_ICON; + public readonly IntPtr _NET_WM_PID; + public readonly IntPtr _NET_WM_HANDLED_ICONS; + public readonly IntPtr _NET_WM_USER_TIME; + public readonly IntPtr _NET_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_PING; + public readonly IntPtr _NET_WM_SYNC_REQUEST; + public readonly IntPtr _NET_SYSTEM_TRAY_S; + public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION; + public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT; + public readonly IntPtr _XEMBED; + public readonly IntPtr _XEMBED_INFO; + public readonly IntPtr _MOTIF_WM_HINTS; + public readonly IntPtr _NET_WM_STATE_SKIP_TASKBAR; + public readonly IntPtr _NET_WM_STATE_ABOVE; + public readonly IntPtr _NET_WM_STATE_MODAL; + public readonly IntPtr _NET_WM_STATE_HIDDEN; + public readonly IntPtr _NET_WM_CONTEXT_HELP; + public readonly IntPtr _NET_WM_WINDOW_OPACITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DOCK; + public readonly IntPtr _NET_WM_WINDOW_TYPE_TOOLBAR; + public readonly IntPtr _NET_WM_WINDOW_TYPE_MENU; + public readonly IntPtr _NET_WM_WINDOW_TYPE_UTILITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_SPLASH; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DIALOG; + public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL; + public readonly IntPtr CLIPBOARD; + public readonly IntPtr CLIPBOARD_MANAGER; + public readonly IntPtr SAVE_TARGETS; + public readonly IntPtr MULTIPLE; + public readonly IntPtr PRIMARY; + public readonly IntPtr OEMTEXT; + public readonly IntPtr UNICODETEXT; + public readonly IntPtr TARGETS; + public readonly IntPtr UTF8_STRING; + public readonly IntPtr UTF16_STRING; + public readonly IntPtr ATOM_PAIR; + + + public X11Atoms(IntPtr display) + { + + // make sure this array stays in sync with the statements below + + var fields = typeof(X11Atoms).GetFields() + .Where(f => f.FieldType == typeof(IntPtr) && (IntPtr)f.GetValue(this) == IntPtr.Zero).ToArray(); + var atomNames = fields.Select(f => f.Name).ToArray(); + + IntPtr[] atoms = new IntPtr [atomNames.Length]; + ; + + XInternAtoms(display, atomNames, atomNames.Length, true, atoms); + + for (var c = 0; c < fields.Length; c++) + fields[c].SetValue(this, atoms[c]); + } + } +} diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs new file mode 100644 index 0000000000..cb9d3389e4 --- /dev/null +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -0,0 +1,234 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Input.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class X11Clipboard : IClipboard + { + private readonly X11Info _x11; + private string _storedString; + private IntPtr _handle; + private TaskCompletionSource _requestedFormatsTcs; + private TaskCompletionSource _requestedTextTcs; + private readonly IntPtr[] _textAtoms; + private readonly IntPtr _avaloniaSaveTargetsAtom; + + public X11Clipboard(AvaloniaX11Platform platform) + { + _x11 = platform.Info; + _handle = CreateEventWindow(platform, OnEvent); + _avaloniaSaveTargetsAtom = XInternAtom(_x11.Display, "AVALONIA_SAVE_TARGETS_PROPERTY_ATOM", false); + _textAtoms = new[] + { + _x11.Atoms.XA_STRING, + _x11.Atoms.OEMTEXT, + _x11.Atoms.UTF8_STRING, + _x11.Atoms.UTF16_STRING + }.Where(a => a != IntPtr.Zero).ToArray(); + } + + Encoding GetStringEncoding(IntPtr atom) + { + return (atom == _x11.Atoms.XA_STRING + || atom == _x11.Atoms.OEMTEXT) + ? Encoding.ASCII + : atom == _x11.Atoms.UTF8_STRING + ? Encoding.UTF8 + : atom == _x11.Atoms.UTF16_STRING + ? Encoding.Unicode + : null; + } + + private unsafe void OnEvent(XEvent ev) + { + if (ev.type == XEventName.SelectionRequest) + { + var sel = ev.SelectionRequestEvent; + var resp = new XEvent + { + SelectionEvent = + { + type = XEventName.SelectionNotify, + send_event = true, + display = _x11.Display, + selection = sel.selection, + target = sel.target, + requestor = sel.requestor, + time = sel.time, + property = IntPtr.Zero + } + }; + if (sel.selection == _x11.Atoms.CLIPBOARD) + { + resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property); + } + + XSendEvent(_x11.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp); + } + + IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property) + { + Encoding textEnc; + if (target == _x11.Atoms.TARGETS) + { + var atoms = _textAtoms; + atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) + .ToArray(); + XChangeProperty(_x11.Display, window, property, + target, 32, PropertyMode.Replace, atoms, atoms.Length); + return property; + } + else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) + { + return property; + } + else if ((textEnc = GetStringEncoding(target)) != null) + { + + var data = textEnc.GetBytes(_storedString ?? ""); + fixed (void* pdata = data) + XChangeProperty(_x11.Display, window, property, target, 8, + PropertyMode.Replace, + pdata, data.Length); + return property; + } + else if (target == _x11.Atoms.MULTIPLE && _x11.Atoms.MULTIPLE != IntPtr.Zero) + { + XGetWindowProperty(_x11.Display, window, property, IntPtr.Zero, new IntPtr(0x7fffffff), false, + _x11.Atoms.ATOM_PAIR, out _, out var actualFormat, out var nitems, out _, out var prop); + if (nitems == IntPtr.Zero) + return IntPtr.Zero; + if (actualFormat == 32) + { + var data = (IntPtr*)prop.ToPointer(); + for (var c = 0; c < nitems.ToInt32(); c += 2) + { + var subTarget = data[c]; + var subProp = data[c + 1]; + var converted = WriteTargetToProperty(subTarget, window, subProp); + data[c + 1] = converted; + } + + XChangeProperty(_x11.Display, window, property, _x11.Atoms.ATOM_PAIR, 32, PropertyMode.Replace, + prop.ToPointer(), nitems.ToInt32()); + } + + XFree(prop); + + return property; + } + else + return IntPtr.Zero; + } + + if (ev.type == XEventName.SelectionNotify && ev.SelectionEvent.selection == _x11.Atoms.CLIPBOARD) + { + var sel = ev.SelectionEvent; + if (sel.property == IntPtr.Zero) + { + _requestedFormatsTcs?.TrySetResult(null); + _requestedTextTcs?.TrySetResult(null); + } + XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType, + out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); + Encoding textEnc = null; + if (nitems == IntPtr.Zero) + { + _requestedFormatsTcs?.TrySetResult(null); + _requestedTextTcs?.TrySetResult(null); + } + else + { + if (sel.property == _x11.Atoms.TARGETS) + { + if (actualFormat != 32) + _requestedFormatsTcs?.TrySetResult(null); + else + { + var formats = new IntPtr[nitems.ToInt32()]; + Marshal.Copy(prop, formats, 0, formats.Length); + _requestedFormatsTcs?.TrySetResult(formats); + } + } + else if ((textEnc = GetStringEncoding(sel.property)) != null) + { + var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32()); + _requestedTextTcs?.TrySetResult(text); + } + } + + XFree(prop); + } + } + + Task SendFormatRequest() + { + if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted) + _requestedFormatsTcs = new TaskCompletionSource(); + XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, _handle, + IntPtr.Zero); + return _requestedFormatsTcs.Task; + } + + Task SendTextRequest(IntPtr format) + { + if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted) + _requestedTextTcs = new TaskCompletionSource(); + XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); + return _requestedTextTcs.Task; + } + + public async Task GetTextAsync() + { + if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero) + return null; + var res = await SendFormatRequest(); + var target = _x11.Atoms.UTF8_STRING; + if (res != null) + { + var preferredFormats = new[] {_x11.Atoms.UTF16_STRING, _x11.Atoms.UTF8_STRING, _x11.Atoms.XA_STRING}; + foreach (var pf in preferredFormats) + if (res.Contains(pf)) + { + target = pf; + break; + } + } + + return await SendTextRequest(target); + } + + void StoreAtomsInClipboardManager(IntPtr[] atoms) + { + if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) + { + var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER); + if (clipboardManager != IntPtr.Zero) + { + XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32, + PropertyMode.Replace, + atoms, atoms.Length); + XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS, + _avaloniaSaveTargetsAtom, _handle, IntPtr.Zero); + } + } + } + + public Task SetTextAsync(string text) + { + _storedString = text; + XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); + StoreAtomsInClipboardManager(_textAtoms); + return Task.CompletedTask; + } + + public Task ClearAsync() + { + return SetTextAsync(null); + } + } +} diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs new file mode 100644 index 0000000000..40b01117e3 --- /dev/null +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class X11CursorFactory : IStandardCursorFactory + { + private readonly IntPtr _display; + private Dictionary _cursors; + + private static readonly Dictionary s_mapping = + new Dictionary + { + {StandardCursorType.Arrow, CursorFontShape.XC_top_left_arrow}, + {StandardCursorType.Cross, CursorFontShape.XC_cross}, + {StandardCursorType.Hand, CursorFontShape.XC_hand1}, + {StandardCursorType.Help, CursorFontShape.XC_question_arrow}, + {StandardCursorType.Ibeam, CursorFontShape.XC_xterm}, + {StandardCursorType.No, CursorFontShape.XC_X_cursor}, + {StandardCursorType.Wait, CursorFontShape.XC_watch}, + {StandardCursorType.AppStarting, CursorFontShape.XC_watch}, + {StandardCursorType.BottomSize, CursorFontShape.XC_bottom_side}, + {StandardCursorType.DragCopy, CursorFontShape.XC_center_ptr}, + {StandardCursorType.DragLink, CursorFontShape.XC_fleur}, + {StandardCursorType.DragMove, CursorFontShape.XC_diamond_cross}, + {StandardCursorType.LeftSide, CursorFontShape.XC_left_side}, + {StandardCursorType.RightSide, CursorFontShape.XC_right_side}, + {StandardCursorType.SizeAll, CursorFontShape.XC_sizing}, + {StandardCursorType.TopSide, CursorFontShape.XC_top_side}, + {StandardCursorType.UpArrow, CursorFontShape.XC_sb_up_arrow}, + {StandardCursorType.BottomLeftCorner, CursorFontShape.XC_bottom_left_corner}, + {StandardCursorType.BottomRightCorner, CursorFontShape.XC_bottom_right_corner}, + {StandardCursorType.SizeNorthSouth, CursorFontShape.XC_sb_v_double_arrow}, + {StandardCursorType.SizeWestEast, CursorFontShape.XC_sb_h_double_arrow}, + {StandardCursorType.TopLeftCorner, CursorFontShape.XC_top_left_corner}, + {StandardCursorType.TopRightCorner, CursorFontShape.XC_top_right_corner}, + }; + + public X11CursorFactory(IntPtr display) + { + _display = display; + _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() + .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); + } + + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + var handle = s_mapping.TryGetValue(cursorType, out var shape) + ? _cursors[shape] + : _cursors[CursorFontShape.XC_top_left_arrow]; + return new PlatformHandle(handle, "XCURSOR"); + } + } +} diff --git a/src/Avalonia.X11/X11Enums.cs b/src/Avalonia.X11/X11Enums.cs new file mode 100644 index 0000000000..d97e1c42bb --- /dev/null +++ b/src/Avalonia.X11/X11Enums.cs @@ -0,0 +1,110 @@ +using System; + +namespace Avalonia.X11 +{ + + public enum Status + { + Success = 0, /* everything's okay */ + BadRequest = 1, /* bad request code */ + BadValue = 2, /* int parameter out of range */ + BadWindow = 3, /* parameter not a Window */ + BadPixmap = 4, /* parameter not a Pixmap */ + BadAtom = 5, /* parameter not an Atom */ + BadCursor = 6, /* parameter not a Cursor */ + BadFont = 7, /* parameter not a Font */ + BadMatch = 8, /* parameter mismatch */ + BadDrawable = 9, /* parameter not a Pixmap or Window */ + BadAccess = 10, /* depending on context: + - key/button already grabbed + - attempt to free an illegal + cmap entry + - attempt to store into a read-only + color map entry. + - attempt to modify the access control + list from other than the local host. + */ + BadAlloc = 11, /* insufficient resources */ + BadColor = 12, /* no such colormap */ + BadGC = 13, /* parameter not a GC */ + BadIDChoice = 14, /* choice not in range or already used */ + BadName = 15, /* font or color name doesn't exist */ + BadLength = 16, /* Request length incorrect */ + BadImplementation = 17, /* server is defective */ + + FirstExtensionError = 128, + LastExtensionError = 255, + + } + + [Flags] + public enum XEventMask : int + { + NoEventMask = 0, + KeyPressMask = (1 << 0), + KeyReleaseMask = (1 << 1), + ButtonPressMask = (1 << 2), + ButtonReleaseMask = (1 << 3), + EnterWindowMask = (1 << 4), + LeaveWindowMask = (1 << 5), + PointerMotionMask = (1 << 6), + PointerMotionHintMask = (1 << 7), + Button1MotionMask = (1 << 8), + Button2MotionMask = (1 << 9), + Button3MotionMask = (1 << 10), + Button4MotionMask = (1 << 11), + Button5MotionMask = (1 << 12), + ButtonMotionMask = (1 << 13), + KeymapStateMask = (1 << 14), + ExposureMask = (1 << 15), + VisibilityChangeMask = (1 << 16), + StructureNotifyMask = (1 << 17), + ResizeRedirectMask = (1 << 18), + SubstructureNotifyMask = (1 << 19), + SubstructureRedirectMask = (1 << 20), + FocusChangeMask = (1 << 21), + PropertyChangeMask = (1 << 22), + ColormapChangeMask = (1 << 23), + OwnerGrabButtonMask = (1 << 24) + } + + [Flags] + public enum XModifierMask + { + ShiftMask = (1 << 0), + LockMask = (1 << 1), + ControlMask = (1 << 2), + Mod1Mask = (1 << 3), + Mod2Mask = (1 << 4), + Mod3Mask = (1 << 5), + Mod4Mask = (1 << 6), + Mod5Mask = (1 << 7), + Button1Mask = (1 << 8), + Button2Mask = (1 << 9), + Button3Mask = (1 << 10), + Button4Mask = (1 << 11), + Button5Mask = (1 << 12), + AnyModifier = (1 << 15) + + } + + [Flags] + public enum XCreateWindowFlags + { + CWBackPixmap = (1 << 0), + CWBackPixel = (1 << 1), + CWBorderPixmap = (1 << 2), + CWBorderPixel = (1 << 3), + CWBitGravity = (1 << 4), + CWWinGravity = (1 << 5), + CWBackingStore = (1 << 6), + CWBackingPlanes = (1 << 7), + CWBackingPixel = (1 << 8), + CWOverrideRedirect = (1 << 9), + CWSaveUnder = (1 << 10), + CWEventMask = (1 << 11), + CWDontPropagate = (1 << 12), + CWColormap = (1 << 13), + CWCursor = (1 << 14), + } +} diff --git a/src/Avalonia.X11/X11Exception.cs b/src/Avalonia.X11/X11Exception.cs new file mode 100644 index 0000000000..2ac5a31d9b --- /dev/null +++ b/src/Avalonia.X11/X11Exception.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.X11 +{ + public class X11Exception : Exception + { + public X11Exception(string message) : base(message) + { + + } + } +} diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs new file mode 100644 index 0000000000..00288f300d --- /dev/null +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using Avalonia.Platform; +using SkiaSharp; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class X11Framebuffer : ILockedFramebuffer + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private readonly int _depth; + private IUnmanagedBlob _blob; + + public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor) + { + _display = display; + _xid = xid; + _depth = depth; + Size = new PixelSize(width, height); + RowBytes = width * 4; + Dpi = new Vector(96, 96) * factor; + Format = PixelFormat.Bgra8888; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * height); + Address = _blob.Address; + } + + public void Dispose() + { + var image = new XImage(); + int bitsPerPixel = 32; + image.width = Size.Width; + image.height = Size.Height; + image.format = 2; //ZPixmap; + image.data = Address; + image.byte_order = 0;// LSBFirst; + image.bitmap_unit = bitsPerPixel; + image.bitmap_bit_order = 0;// LSBFirst; + image.bitmap_pad = bitsPerPixel; + image.depth = _depth; + image.bytes_per_line = RowBytes; + image.bits_per_pixel = bitsPerPixel; + XLockDisplay(_display); + XInitImage(ref image); + var gc = XCreateGC(_display, _xid, 0, IntPtr.Zero); + XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Size.Width, (uint) Size.Height); + XFreeGC(_display, gc); + XSync(_display, true); + XUnlockDisplay(_display); + _blob.Dispose(); + } + + public IntPtr Address { get; } + public PixelSize Size { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs new file mode 100644 index 0000000000..70220cebcd --- /dev/null +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -0,0 +1,29 @@ +using System; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + public class X11FramebufferSurface : IFramebufferPlatformSurface + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private readonly Func _scaling; + + public X11FramebufferSurface(IntPtr display, IntPtr xid, Func scaling) + { + _display = display; + _xid = xid; + _scaling = scaling; + } + + public ILockedFramebuffer Lock() + { + XLockDisplay(_display); + XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height, + out var bw, out var d); + XUnlockDisplay(_display); + return new X11Framebuffer(_display, _xid, 24,width, height, _scaling()); + } + } +} diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs new file mode 100644 index 0000000000..f0e75536d0 --- /dev/null +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class X11IconLoader : IPlatformIconLoader + { + private readonly X11Info _x11; + + public X11IconLoader(X11Info x11) + { + _x11 = x11; + } + + IWindowIconImpl LoadIcon(Bitmap bitmap) + { + var rv = new X11IconData(bitmap); + bitmap.Dispose(); + return rv; + } + + public IWindowIconImpl LoadIcon(string fileName) => LoadIcon(new Bitmap(fileName)); + + public IWindowIconImpl LoadIcon(Stream stream) => LoadIcon(new Bitmap(stream)); + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + ms.Position = 0; + return LoadIcon(ms); + } + } + + unsafe class X11IconData : IWindowIconImpl, IFramebufferPlatformSurface + { + private int _width; + private int _height; + private uint[] _bdata; + public UIntPtr[] Data { get; } + + public X11IconData(Bitmap bitmap) + { + _width = Math.Min(bitmap.PixelSize.Width, 128); + _height = Math.Min(bitmap.PixelSize.Height, 128); + _bdata = new uint[_width * _height]; + fixed (void* ptr = _bdata) + { + var iptr = (int*)ptr; + iptr[0] = _width; + iptr[1] = _height; + } + using(var rt = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[]{this})) + using (var ctx = rt.CreateDrawingContext(null)) + ctx.DrawImage(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), + new Rect(0, 0, _width, _height)); + Data = new UIntPtr[_width * _height + 2]; + Data[0] = new UIntPtr((uint)_width); + Data[1] = new UIntPtr((uint)_height); + for (var y = 0; y < _height; y++) + { + var r = y * _width; + for (var x = 0; x < _width; x++) + Data[r + x] = new UIntPtr(_bdata[r + x]); + } + + _bdata = null; + } + + public void Save(Stream outputStream) + { + using (var wr = + new WriteableBitmap(new PixelSize(_width, _height), new Vector(96, 96), PixelFormat.Bgra8888)) + { + using (var fb = wr.Lock()) + { + var fbp = (uint*)fb.Address; + for (var y = 0; y < _height; y++) + { + var r = y * _width; + var fbr = y * fb.RowBytes / 4; + for (var x = 0; x < _width; x++) + fbp[fbr + x] = Data[r + x].ToUInt32(); + } + } + wr.Save(outputStream); + } + } + + public ILockedFramebuffer Lock() + { + var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned); + return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4, + new Vector(96, 96), PixelFormat.Bgra8888, + () => h.Free()); + } + } +} diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs new file mode 100644 index 0000000000..6e4b31fb5c --- /dev/null +++ b/src/Avalonia.X11/X11Info.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using JetBrains.Annotations; +using static Avalonia.X11.XLib; +// ReSharper disable UnusedAutoPropertyAccessor.Local +namespace Avalonia.X11 +{ + class X11Info + { + public IntPtr Display { get; } + public IntPtr DeferredDisplay { get; } + public int DefaultScreen { get; } + public IntPtr BlackPixel { get; } + public IntPtr RootWindow { get; } + public IntPtr DefaultRootWindow { get; } + public IntPtr DefaultCursor { get; } + public X11Atoms Atoms { get; } + public IntPtr Xim { get; } + + public int RandrEventBase { get; } + public int RandrErrorBase { get; } + + public Version RandrVersion { get; } + + public int XInputOpcode { get; } + public int XInputEventBase { get; } + public int XInputErrorBase { get; } + + public Version XInputVersion { get; } + + public IntPtr LastActivityTimestamp { get; set; } + + public unsafe X11Info(IntPtr display, IntPtr deferredDisplay) + { + Display = display; + DeferredDisplay = deferredDisplay; + DefaultScreen = XDefaultScreen(display); + BlackPixel = XBlackPixel(display, DefaultScreen); + RootWindow = XRootWindow(display, DefaultScreen); + DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_top_left_arrow); + DefaultRootWindow = XDefaultRootWindow(display); + Atoms = new X11Atoms(display); + //TODO: Open an actual XIM once we get support for preedit in our textbox + XSetLocaleModifiers("@im=none"); + Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + try + { + if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0) + { + RandrEventBase = randrEventBase; + RandrErrorBase = randrErrorBase; + if (XRRQueryVersion(display, out var major, out var minor) != 0) + RandrVersion = new Version(major, minor); + } + } + catch + { + //Ignore, randr is not supported + } + + try + { + if (XQueryExtension(display, "XInputExtension", + out var xiopcode, out var xievent, out var xierror)) + { + int major = 2, minor = 2; + if (XIQueryVersion(display, ref major, ref minor) == Status.Success) + { + XInputVersion = new Version(major, minor); + XInputOpcode = xiopcode; + XInputEventBase = xievent; + XInputErrorBase = xierror; + } + } + } + catch + { + //Ignore, XI is not supported + } + } + } +} diff --git a/src/Avalonia.X11/X11KeyTransform.cs b/src/Avalonia.X11/X11KeyTransform.cs new file mode 100644 index 0000000000..26495111d1 --- /dev/null +++ b/src/Avalonia.X11/X11KeyTransform.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using Avalonia.Input; + +namespace Avalonia.X11 +{ + static class X11KeyTransform + { + private static readonly Dictionary KeyDic = new Dictionary + { + {X11Key.Cancel, Key.Cancel}, + {X11Key.BackSpace, Key.Back}, + {X11Key.Tab, Key.Tab}, + {X11Key.Linefeed, Key.LineFeed}, + {X11Key.Clear, Key.Clear}, + {X11Key.Return, Key.Return}, + {X11Key.KP_Enter, Key.Return}, + {X11Key.Pause, Key.Pause}, + {X11Key.Caps_Lock, Key.CapsLock}, + //{ X11Key.?, Key.HangulMode } + //{ X11Key.?, Key.JunjaMode } + //{ X11Key.?, Key.FinalMode } + //{ X11Key.?, Key.KanjiMode } + {X11Key.Escape, Key.Escape}, + //{ X11Key.?, Key.ImeConvert } + //{ X11Key.?, Key.ImeNonConvert } + //{ X11Key.?, Key.ImeAccept } + //{ X11Key.?, Key.ImeModeChange } + {X11Key.space, Key.Space}, + {X11Key.Prior, Key.Prior}, + {X11Key.KP_Prior, Key.Prior}, + {X11Key.Page_Down, Key.PageDown}, + {X11Key.KP_Page_Down, Key.PageDown}, + {X11Key.End, Key.End}, + {X11Key.KP_End, Key.End}, + {X11Key.Home, Key.Home}, + {X11Key.KP_Home, Key.Home}, + {X11Key.Left, Key.Left}, + {X11Key.KP_Left, Key.Left}, + {X11Key.Up, Key.Up}, + {X11Key.KP_Up, Key.Up}, + {X11Key.Right, Key.Right}, + {X11Key.KP_Right, Key.Right}, + {X11Key.Down, Key.Down}, + {X11Key.KP_Down, Key.Down}, + {X11Key.Select, Key.Select}, + {X11Key.Print, Key.Print}, + {X11Key.Execute, Key.Execute}, + //{ X11Key.?, Key.Snapshot } + {X11Key.Insert, Key.Insert}, + {X11Key.KP_Insert, Key.Insert}, + {X11Key.Delete, Key.Delete}, + {X11Key.KP_Delete, Key.Delete}, + {X11Key.Help, Key.Help}, + {X11Key.XK_0, Key.D0}, + {X11Key.XK_1, Key.D1}, + {X11Key.XK_2, Key.D2}, + {X11Key.XK_3, Key.D3}, + {X11Key.XK_4, Key.D4}, + {X11Key.XK_5, Key.D5}, + {X11Key.XK_6, Key.D6}, + {X11Key.XK_7, Key.D7}, + {X11Key.XK_8, Key.D8}, + {X11Key.XK_9, Key.D9}, + {X11Key.A, Key.A}, + {X11Key.B, Key.B}, + {X11Key.C, Key.C}, + {X11Key.D, Key.D}, + {X11Key.E, Key.E}, + {X11Key.F, Key.F}, + {X11Key.G, Key.G}, + {X11Key.H, Key.H}, + {X11Key.I, Key.I}, + {X11Key.J, Key.J}, + {X11Key.K, Key.K}, + {X11Key.L, Key.L}, + {X11Key.M, Key.M}, + {X11Key.N, Key.N}, + {X11Key.O, Key.O}, + {X11Key.P, Key.P}, + {X11Key.Q, Key.Q}, + {X11Key.R, Key.R}, + {X11Key.S, Key.S}, + {X11Key.T, Key.T}, + {X11Key.U, Key.U}, + {X11Key.V, Key.V}, + {X11Key.W, Key.W}, + {X11Key.X, Key.X}, + {X11Key.Y, Key.Y}, + {X11Key.Z, Key.Z}, + {X11Key.a, Key.A}, + {X11Key.b, Key.B}, + {X11Key.c, Key.C}, + {X11Key.d, Key.D}, + {X11Key.e, Key.E}, + {X11Key.f, Key.F}, + {X11Key.g, Key.G}, + {X11Key.h, Key.H}, + {X11Key.i, Key.I}, + {X11Key.j, Key.J}, + {X11Key.k, Key.K}, + {X11Key.l, Key.L}, + {X11Key.m, Key.M}, + {X11Key.n, Key.N}, + {X11Key.o, Key.O}, + {X11Key.p, Key.P}, + {X11Key.q, Key.Q}, + {X11Key.r, Key.R}, + {X11Key.s, Key.S}, + {X11Key.t, Key.T}, + {X11Key.u, Key.U}, + {X11Key.v, Key.V}, + {X11Key.w, Key.W}, + {X11Key.x, Key.X}, + {X11Key.y, Key.Y}, + {X11Key.z, Key.Z}, + //{ X11Key.?, Key.LWin } + //{ X11Key.?, Key.RWin } + {X11Key.Menu, Key.Apps}, + //{ X11Key.?, Key.Sleep } + {X11Key.KP_0, Key.NumPad0}, + {X11Key.KP_1, Key.NumPad1}, + {X11Key.KP_2, Key.NumPad2}, + {X11Key.KP_3, Key.NumPad3}, + {X11Key.KP_4, Key.NumPad4}, + {X11Key.KP_5, Key.NumPad5}, + {X11Key.KP_6, Key.NumPad6}, + {X11Key.KP_7, Key.NumPad7}, + {X11Key.KP_8, Key.NumPad8}, + {X11Key.KP_9, Key.NumPad9}, + {X11Key.multiply, Key.Multiply}, + {X11Key.KP_Multiply, Key.Multiply}, + {X11Key.KP_Add, Key.Add}, + //{ X11Key.?, Key.Separator } + {X11Key.KP_Subtract, Key.Subtract}, + {X11Key.KP_Decimal, Key.Decimal}, + {X11Key.KP_Divide, Key.Divide}, + {X11Key.F1, Key.F1}, + {X11Key.F2, Key.F2}, + {X11Key.F3, Key.F3}, + {X11Key.F4, Key.F4}, + {X11Key.F5, Key.F5}, + {X11Key.F6, Key.F6}, + {X11Key.F7, Key.F7}, + {X11Key.F8, Key.F8}, + {X11Key.F9, Key.F9}, + {X11Key.F10, Key.F10}, + {X11Key.F11, Key.F11}, + {X11Key.F12, Key.F12}, + {X11Key.L3, Key.F13}, + {X11Key.F14, Key.F14}, + {X11Key.L5, Key.F15}, + {X11Key.F16, Key.F16}, + {X11Key.F17, Key.F17}, + {X11Key.L8, Key.F18}, + {X11Key.L9, Key.F19}, + {X11Key.L10, Key.F20}, + {X11Key.R1, Key.F21}, + {X11Key.R2, Key.F22}, + {X11Key.F23, Key.F23}, + {X11Key.R4, Key.F24}, + {X11Key.Num_Lock, Key.NumLock}, + {X11Key.Scroll_Lock, Key.Scroll}, + {X11Key.Shift_L, Key.LeftShift}, + {X11Key.Shift_R, Key.RightShift}, + {X11Key.Control_L, Key.LeftCtrl}, + {X11Key.Control_R, Key.RightCtrl}, + {X11Key.Alt_L, Key.LeftAlt}, + {X11Key.Alt_R, Key.RightAlt}, + //{ X11Key.?, Key.BrowserBack } + //{ X11Key.?, Key.BrowserForward } + //{ X11Key.?, Key.BrowserRefresh } + //{ X11Key.?, Key.BrowserStop } + //{ X11Key.?, Key.BrowserSearch } + //{ X11Key.?, Key.BrowserFavorites } + //{ X11Key.?, Key.BrowserHome } + //{ X11Key.?, Key.VolumeMute } + //{ X11Key.?, Key.VolumeDown } + //{ X11Key.?, Key.VolumeUp } + //{ X11Key.?, Key.MediaNextTrack } + //{ X11Key.?, Key.MediaPreviousTrack } + //{ X11Key.?, Key.MediaStop } + //{ X11Key.?, Key.MediaPlayPause } + //{ X11Key.?, Key.LaunchMail } + //{ X11Key.?, Key.SelectMedia } + //{ X11Key.?, Key.LaunchApplication1 } + //{ X11Key.?, Key.LaunchApplication2 } + {X11Key.semicolon, Key.OemSemicolon}, + {X11Key.plus, Key.OemPlus}, + {X11Key.equal, Key.OemPlus}, + {X11Key.comma, Key.OemComma}, + {X11Key.minus, Key.OemMinus}, + {X11Key.period, Key.OemPeriod}, + {X11Key.slash, Key.Oem2}, + {X11Key.grave, Key.OemTilde}, + //{ X11Key.?, Key.AbntC1 } + //{ X11Key.?, Key.AbntC2 } + {X11Key.bracketleft, Key.OemOpenBrackets}, + {X11Key.backslash, Key.OemPipe}, + {X11Key.bracketright, Key.OemCloseBrackets}, + {X11Key.apostrophe, Key.OemQuotes}, + //{ X11Key.?, Key.Oem8 } + //{ X11Key.?, Key.Oem102 } + //{ X11Key.?, Key.ImeProcessed } + //{ X11Key.?, Key.System } + //{ X11Key.?, Key.OemAttn } + //{ X11Key.?, Key.OemFinish } + //{ X11Key.?, Key.DbeHiragana } + //{ X11Key.?, Key.OemAuto } + //{ X11Key.?, Key.DbeDbcsChar } + //{ X11Key.?, Key.OemBackTab } + //{ X11Key.?, Key.Attn } + //{ X11Key.?, Key.DbeEnterWordRegisterMode } + //{ X11Key.?, Key.DbeEnterImeConfigureMode } + //{ X11Key.?, Key.EraseEof } + //{ X11Key.?, Key.Play } + //{ X11Key.?, Key.Zoom } + //{ X11Key.?, Key.NoName } + //{ X11Key.?, Key.DbeEnterDialogConversionMode } + //{ X11Key.?, Key.OemClear } + //{ X11Key.?, Key.DeadCharProcessed } + }; + + public static Key ConvertKey(IntPtr key) + { + var ikey = key.ToInt32(); + Key result; + return KeyDic.TryGetValue((X11Key)ikey, out result) ? result : Key.None; + } +} + +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs new file mode 100644 index 0000000000..3b50dff5c8 --- /dev/null +++ b/src/Avalonia.X11/X11Platform.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Gtk3; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.OpenGL; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.X11; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class AvaloniaX11Platform : IWindowingPlatform + { + private Lazy _keyboardDevice = new Lazy(() => new KeyboardDevice()); + private Lazy _mouseDevice = new Lazy(() => new MouseDevice()); + public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; + public MouseDevice MouseDevice => _mouseDevice.Value; + public Dictionary> Windows = new Dictionary>(); + public XI2Manager XI2; + public X11Info Info { get; private set; } + public IX11Screens X11Screens { get; private set; } + public IScreenImpl Screens { get; private set; } + public void Initialize() + { + XInitThreads(); + Display = XOpenDisplay(IntPtr.Zero); + DeferredDisplay = XOpenDisplay(IntPtr.Zero); + if (Display == IntPtr.Zero) + throw new Exception("XOpenDisplay failed"); + XError.Init(); + Info = new X11Info(Display, DeferredDisplay); + + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(this) + .Bind().ToConstant(new X11PlatformThreading(this)) + .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) + .Bind().ToFunc(() => KeyboardDevice) + .Bind().ToConstant(new X11CursorFactory(Display)) + .Bind().ToConstant(new X11Clipboard(this)) + .Bind().ToConstant(new PlatformSettingsStub()) + .Bind().ToConstant(new X11IconLoader(Info)) + .Bind().ToConstant(new Gtk3ForeignX11SystemDialog()); + + X11Screens = Avalonia.X11.X11Screens.Init(this); + Screens = new X11Screens(X11Screens); + if (Info.XInputVersion != null) + { + var xi2 = new XI2Manager(); + if (xi2.Init(this)) + XI2 = xi2; + } + EglGlPlatformFeature.TryInitialize(); + + } + + public IntPtr DeferredDisplay { get; set; } + public IntPtr Display { get; set; } + public IWindowImpl CreateWindow() + { + return new X11Window(this, false); + } + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotSupportedException(); + } + + public IPopupImpl CreatePopup() + { + return new X11Window(this, true); + } + } +} + +namespace Avalonia +{ + public static class AvaloniaX11PlatformExtensions + { + public static T UseX11(this T builder) where T : AppBuilderBase, new() + { + builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); + return builder; + } + + public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize(); + } + +} diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs new file mode 100644 index 0000000000..f66afc4a83 --- /dev/null +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Threading; +using static Avalonia.X11.XLib; + +namespace Avalonia.X11 +{ + unsafe class X11PlatformThreading : IPlatformThreadingInterface + { + private readonly AvaloniaX11Platform _platform; + private readonly IntPtr _display; + private readonly Dictionary> _eventHandlers; + private Thread _mainThread; + + [StructLayout(LayoutKind.Explicit)] + struct epoll_data + { + [FieldOffset(0)] + public IntPtr ptr; + [FieldOffset(0)] + public int fd; + [FieldOffset(0)] + public uint u32; + [FieldOffset(0)] + public ulong u64; + } + + private const int EPOLLIN = 1; + private const int EPOLL_CTL_ADD = 1; + private const int O_NONBLOCK = 2048; + + [StructLayout(LayoutKind.Sequential)] + struct epoll_event + { + public uint events; + public epoll_data data; + } + + [DllImport("libc")] + extern static int epoll_create1(int size); + + [DllImport("libc")] + extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event); + + [DllImport("libc")] + extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout); + + [DllImport("libc")] + extern static int pipe2(int* fds, int flags); + [DllImport("libc")] + extern static IntPtr write(int fd, void* buf, IntPtr count); + + [DllImport("libc")] + extern static IntPtr read(int fd, void* buf, IntPtr count); + + enum EventCodes + { + X11 = 1, + Signal =2 + } + + private int _sigread, _sigwrite; + private object _lock = new object(); + private bool _signaled; + private DispatcherPriority _signaledPriority; + private int _epoll; + private Stopwatch _clock = Stopwatch.StartNew(); + + class X11Timer : IDisposable + { + private readonly X11PlatformThreading _parent; + + public X11Timer(X11PlatformThreading parent, DispatcherPriority prio, TimeSpan interval, Action tick) + { + _parent = parent; + Priority = prio; + Tick = tick; + Interval = interval; + Reschedule(); + } + + public DispatcherPriority Priority { get; } + public TimeSpan NextTick { get; private set; } + public TimeSpan Interval { get; } + public Action Tick { get; } + public bool Disposed { get; private set; } + + public void Reschedule() + { + NextTick = _parent._clock.Elapsed + Interval; + } + + public void Dispose() + { + Disposed = true; + lock (_parent._lock) + _parent._timers.Remove(this); + } + } + + List _timers = new List(); + + public X11PlatformThreading(AvaloniaX11Platform platform) + { + _platform = platform; + _display = platform.Display; + _eventHandlers = platform.Windows; + _mainThread = Thread.CurrentThread; + var fd = XLib.XConnectionNumber(_display); + var ev = new epoll_event() + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.X11} + }; + _epoll = epoll_create1(0); + if (_epoll == -1) + throw new X11Exception("epoll_create1 failed"); + + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1) + throw new X11Exception("Unable to attach X11 connection handle to epoll"); + + var fds = stackalloc int[2]; + pipe2(fds, O_NONBLOCK); + _sigread = fds[0]; + _sigwrite = fds[1]; + + ev = new epoll_event + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.Signal} + }; + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1) + throw new X11Exception("Unable to attach signal pipe to epoll"); + } + + int TimerComparer(X11Timer t1, X11Timer t2) + { + return t2.Priority - t1.Priority; + } + + void CheckSignaled() + { + int buf = 0; + while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) + { + } + + DispatcherPriority prio; + lock (_lock) + { + if (!_signaled) + return; + _signaled = false; + prio = _signaledPriority; + _signaledPriority = DispatcherPriority.MinValue; + } + + Signaled?.Invoke(prio); + } + + void HandleX11(CancellationToken cancellationToken) + { + while (true) + { + var pending = XPending(_display); + if (pending == 0) + break; + while (pending > 0) + { + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + if (xev.type == XEventName.GenericEvent) + XGetEventData(_display, &xev.GenericEventCookie); + pending--; + try + { + if (xev.type == XEventName.GenericEvent) + { + if (_platform.XI2 != null && _platform.Info.XInputOpcode == + xev.GenericEventCookie.extension) + { + _platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data); + } + } + else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) + handler(xev); + } + finally + { + if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null) + XFreeEventData(_display, &xev.GenericEventCookie); + } + } + } + Dispatcher.UIThread.RunJobs(); + } + + public void RunLoop(CancellationToken cancellationToken) + { + var readyTimers = new List(); + while (!cancellationToken.IsCancellationRequested) + { + var now = _clock.Elapsed; + TimeSpan? nextTick = null; + readyTimers.Clear(); + lock(_timers) + foreach (var t in _timers) + { + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + if (t.NextTick < now) + readyTimers.Add(t); + } + + readyTimers.Sort(TimerComparer); + + foreach (var t in readyTimers) + { + if (cancellationToken.IsCancellationRequested) + return; + t.Tick(); + if(!t.Disposed) + { + t.Reschedule(); + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + } + } + + if (cancellationToken.IsCancellationRequested) + return; + //Flush whatever requests were made to XServer + XFlush(_display); + epoll_event ev; + if (XPending(_display) == 0) + epoll_wait(_epoll, &ev, 1, + nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds)); + if (cancellationToken.IsCancellationRequested) + return; + CheckSignaled(); + HandleX11(cancellationToken); + } + } + + + + public void Signal(DispatcherPriority priority) + { + lock (_lock) + { + if (priority > _signaledPriority) + _signaledPriority = priority; + + if(_signaled) + return; + _signaled = true; + int buf = 0; + write(_sigwrite, &buf, new IntPtr(1)); + } + } + + public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread; + public event Action Signaled; + + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) + { + if (_mainThread != Thread.CurrentThread) + throw new InvalidOperationException("StartTimer can be only called from UI thread"); + if (interval <= TimeSpan.Zero) + throw new ArgumentException("Interval must be positive", nameof(interval)); + + // We assume that we are on the main thread and outside of epoll_wait, so there is no need for wakeup signal + + var timer = new X11Timer(this, priority, interval, tick); + lock(_timers) + _timers.Add(timer); + return timer; + } + } +} diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs new file mode 100644 index 0000000000..f2a0520c10 --- /dev/null +++ b/src/Avalonia.X11/X11Screens.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +using JetBrains.Annotations; + +namespace Avalonia.X11 +{ + class X11Screens : IScreenImpl + { + private IX11Screens _impl; + + public X11Screens(IX11Screens impl) + { + _impl = impl; + } + + static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) + { + var rect = default(PixelRect); + foreach (var s in screens) + { + rect = rect.Union(s.Bounds); + //Fallback value + s.WorkingArea = s.Bounds; + } + + var res = XGetWindowProperty(info.Display, + info.RootWindow, + info.Atoms._NET_WORKAREA, + IntPtr.Zero, + new IntPtr(128), + false, + info.Atoms.AnyPropertyType, + out var type, + out var format, + out var count, + out var bytesAfter, + out var prop); + + if (res != (int)Status.Success || type == IntPtr.Zero || + format == 0 || bytesAfter.ToInt64() != 0 || count.ToInt64() % 4 != 0) + return screens; + + var pwa = (IntPtr*)prop; + var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); + + + foreach (var s in screens) + s.WorkingArea = s.Bounds.Intersect(wa); + + XFree(prop); + return screens; + } + + class Randr15ScreensImpl : IX11Screens + { + private readonly X11ScreensUserSettings _settings; + private X11Screen[] _cache; + private X11Info _x11; + private IntPtr _window; + + public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings) + { + _settings = settings; + _x11 = platform.Info; + _window = CreateEventWindow(platform, OnEvent); + XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify); + } + + private void OnEvent(XEvent ev) + { + // Invalidate cache on RRScreenChangeNotify + if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) + _cache = null; + } + + public unsafe X11Screen[] Screens + { + get + { + if (_cache != null) + return _cache; + var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count); + + var screens = new X11Screen[count]; + for (var c = 0; c < count; c++) + { + var mon = monitors[c]; + var namePtr = XGetAtomName(_x11.Display, mon.Name); + var name = Marshal.PtrToStringAnsi(namePtr); + XFree(namePtr); + + var density = 1d; + if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) + { + if (mon.MWidth == 0) + density = 1; + else + density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth); + } + + density *= _settings.GlobalScaleFactor; + + var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height); + screens[c] = new X11Screen(bounds, + mon.Primary != 0, + name, + (mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight), + density); + } + + XFree(new IntPtr(monitors)); + _cache = UpdateWorkArea(_x11, screens); + return screens; + } + } + } + + class FallbackScreensImpl : IX11Screens + { + public FallbackScreensImpl(X11Info info, X11ScreensUserSettings settings) + { + if (XGetGeometry(info.Display, info.RootWindow, out var geo)) + { + + Screens = UpdateWorkArea(info, + new[] + { + new X11Screen(new PixelRect(0, 0, geo.width, geo.height), true, "Default", null, + settings.GlobalScaleFactor) + }); + } + + Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; + } + + public X11Screen[] Screens { get; } + } + + public static IX11Screens Init(AvaloniaX11Platform platform) + { + var info = platform.Info; + var settings = X11ScreensUserSettings.Detect(); + var impl = (info.RandrVersion != null && info.RandrVersion >= new Version(1, 5)) + ? new Randr15ScreensImpl(platform, settings) + : (IX11Screens)new FallbackScreensImpl(info, settings); + + return impl; + + } + + + public int ScreenCount => _impl.Screens.Length; + + public Screen[] AllScreens => + _impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray(); + } + + interface IX11Screens + { + X11Screen[] Screens { get; } + } + + class X11ScreensUserSettings + { + public double GlobalScaleFactor { get; set; } = 1; + public Dictionary NamedScaleFactors { get; set; } + + static double? TryParse(string s) + { + if (s == null) + return null; + if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var rv)) + return rv; + return null; + } + + + public static X11ScreensUserSettings DetectEnvironment() + { + var globalFactor = Environment.GetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR"); + var screenFactors = Environment.GetEnvironmentVariable("AVALONIA_SCREEN_SCALE_FACTORS"); + if (globalFactor == null && screenFactors == null) + return null; + + var rv = new X11ScreensUserSettings + { + GlobalScaleFactor = TryParse(globalFactor) ?? 1 + }; + + try + { + if (!string.IsNullOrWhiteSpace(screenFactors)) + { + rv.NamedScaleFactors = screenFactors.Split(';').Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Split('=')).ToDictionary(x => x[0], + x => double.Parse(x[1], CultureInfo.InvariantCulture)); + } + } + catch + { + //Ignore + } + + return rv; + } + + + public static X11ScreensUserSettings Detect() + { + return DetectEnvironment() ?? new X11ScreensUserSettings(); + } + } + + class X11Screen + { + public bool Primary { get; } + public string Name { get; set; } + public PixelRect Bounds { get; set; } + public Size? PhysicalSize { get; set; } + public double PixelDensity { get; set; } + public PixelRect WorkingArea { get; set; } + + public X11Screen(PixelRect bounds, bool primary, + string name, Size? physicalSize, double? pixelDensity) + { + Primary = primary; + Name = name; + Bounds = bounds; + if (physicalSize == null && pixelDensity == null) + { + PixelDensity = 1; + } + else if (pixelDensity == null) + { + PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width); + } + else + { + PixelDensity = pixelDensity.Value; + PhysicalSize = physicalSize; + } + } + + public static double GuessPixelDensity(double pixelWidth, double mmWidth) + => Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); + } +} diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs new file mode 100644 index 0000000000..0ae5c16aef --- /dev/null +++ b/src/Avalonia.X11/X11Structs.cs @@ -0,0 +1,1886 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software",, to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok pbartok@novell.com +// + + +// NOT COMPLETE + +using System; +using System.ComponentModel; +using System.Collections; +using System.Drawing; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +#pragma warning disable 649 + +namespace Avalonia.X11 { + // + // In the structures below, fields of type long are mapped to IntPtr. + // This will work on all platforms where sizeof(long)==sizeof(void*), which + // is almost all platforms except WIN64. + // + + [StructLayout(LayoutKind.Sequential)] + internal struct XAnyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal int keycode; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XButtonEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal int button; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMotionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal byte is_hint; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCrossingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal NotifyMode mode; + internal NotifyDetail detail; + internal bool same_screen; + internal bool focus; + internal XModifierMask state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XFocusChangeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int mode; + internal NotifyDetail detail; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeymapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal byte key_vector0; + internal byte key_vector1; + internal byte key_vector2; + internal byte key_vector3; + internal byte key_vector4; + internal byte key_vector5; + internal byte key_vector6; + internal byte key_vector7; + internal byte key_vector8; + internal byte key_vector9; + internal byte key_vector10; + internal byte key_vector11; + internal byte key_vector12; + internal byte key_vector13; + internal byte key_vector14; + internal byte key_vector15; + internal byte key_vector16; + internal byte key_vector17; + internal byte key_vector18; + internal byte key_vector19; + internal byte key_vector20; + internal byte key_vector21; + internal byte key_vector22; + internal byte key_vector23; + internal byte key_vector24; + internal byte key_vector25; + internal byte key_vector26; + internal byte key_vector27; + internal byte key_vector28; + internal byte key_vector29; + internal byte key_vector30; + internal byte key_vector31; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGraphicsExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XNoExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XVisibilityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCreateWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XDestroyWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XUnmapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool from_configure; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XReparentEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal IntPtr parent; + internal int x; + internal int y; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGravityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XResizeRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int width; + internal int height; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal int detail; + internal IntPtr value_mask; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XPropertyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr atom; + internal IntPtr time; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionClearEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr selection; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr owner; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XColormapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr colormap; + internal bool c_new; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XClientMessageEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr message_type; + internal int format; + internal IntPtr ptr1; + internal IntPtr ptr2; + internal IntPtr ptr3; + internal IntPtr ptr4; + internal IntPtr ptr5; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMappingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int request; + internal int first_keycode; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XErrorEvent { + internal XEventName type; + internal IntPtr display; + internal IntPtr resourceid; + internal IntPtr serial; + internal byte error_code; + internal XRequest request_code; + internal byte minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XEventPad { + internal IntPtr pad0; + internal IntPtr pad1; + internal IntPtr pad2; + internal IntPtr pad3; + internal IntPtr pad4; + internal IntPtr pad5; + internal IntPtr pad6; + internal IntPtr pad7; + internal IntPtr pad8; + internal IntPtr pad9; + internal IntPtr pad10; + internal IntPtr pad11; + internal IntPtr pad12; + internal IntPtr pad13; + internal IntPtr pad14; + internal IntPtr pad15; + internal IntPtr pad16; + internal IntPtr pad17; + internal IntPtr pad18; + internal IntPtr pad19; + internal IntPtr pad20; + internal IntPtr pad21; + internal IntPtr pad22; + internal IntPtr pad23; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct XGenericEventCookie + { + internal int type; /* of event. Always GenericEvent */ + internal IntPtr serial; /* # of last request processed */ + internal bool send_event; /* true if from SendEvent request */ + internal IntPtr display; /* Display the event was read from */ + internal int extension; /* major opcode of extension that caused the event */ + internal int evtype; /* actual event type. */ + internal uint cookie; + internal void* data; + + public T GetEvent() where T : unmanaged + { + if (data == null) + throw new InvalidOperationException(); + return *(T*)data; + } + } + + [StructLayout(LayoutKind.Explicit)] + internal struct XEvent { + [ FieldOffset(0) ] internal XEventName type; + [ FieldOffset(0) ] internal XAnyEvent AnyEvent; + [ FieldOffset(0) ] internal XKeyEvent KeyEvent; + [ FieldOffset(0) ] internal XButtonEvent ButtonEvent; + [ FieldOffset(0) ] internal XMotionEvent MotionEvent; + [ FieldOffset(0) ] internal XCrossingEvent CrossingEvent; + [ FieldOffset(0) ] internal XFocusChangeEvent FocusChangeEvent; + [ FieldOffset(0) ] internal XExposeEvent ExposeEvent; + [ FieldOffset(0) ] internal XGraphicsExposeEvent GraphicsExposeEvent; + [ FieldOffset(0) ] internal XNoExposeEvent NoExposeEvent; + [ FieldOffset(0) ] internal XVisibilityEvent VisibilityEvent; + [ FieldOffset(0) ] internal XCreateWindowEvent CreateWindowEvent; + [ FieldOffset(0) ] internal XDestroyWindowEvent DestroyWindowEvent; + [ FieldOffset(0) ] internal XUnmapEvent UnmapEvent; + [ FieldOffset(0) ] internal XMapEvent MapEvent; + [ FieldOffset(0) ] internal XMapRequestEvent MapRequestEvent; + [ FieldOffset(0) ] internal XReparentEvent ReparentEvent; + [ FieldOffset(0) ] internal XConfigureEvent ConfigureEvent; + [ FieldOffset(0) ] internal XGravityEvent GravityEvent; + [ FieldOffset(0) ] internal XResizeRequestEvent ResizeRequestEvent; + [ FieldOffset(0) ] internal XConfigureRequestEvent ConfigureRequestEvent; + [ FieldOffset(0) ] internal XCirculateEvent CirculateEvent; + [ FieldOffset(0) ] internal XCirculateRequestEvent CirculateRequestEvent; + [ FieldOffset(0) ] internal XPropertyEvent PropertyEvent; + [ FieldOffset(0) ] internal XSelectionClearEvent SelectionClearEvent; + [ FieldOffset(0) ] internal XSelectionRequestEvent SelectionRequestEvent; + [ FieldOffset(0) ] internal XSelectionEvent SelectionEvent; + [ FieldOffset(0) ] internal XColormapEvent ColormapEvent; + [ FieldOffset(0) ] internal XClientMessageEvent ClientMessageEvent; + [ FieldOffset(0) ] internal XMappingEvent MappingEvent; + [ FieldOffset(0) ] internal XErrorEvent ErrorEvent; + [ FieldOffset(0) ] internal XKeymapEvent KeymapEvent; + [ FieldOffset(0) ] internal XGenericEventCookie GenericEventCookie; + + //[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=24)] + //[ FieldOffset(0) ] internal int[] pad; + [ FieldOffset(0) ] internal XEventPad Pad; + public override string ToString() { + switch (type) + { + case XEventName.ButtonPress: + case XEventName.ButtonRelease: + return ToString (ButtonEvent); + case XEventName.CirculateNotify: + case XEventName.CirculateRequest: + return ToString (CirculateEvent); + case XEventName.ClientMessage: + return ToString (ClientMessageEvent); + case XEventName.ColormapNotify: + return ToString (ColormapEvent); + case XEventName.ConfigureNotify: + return ToString (ConfigureEvent); + case XEventName.ConfigureRequest: + return ToString (ConfigureRequestEvent); + case XEventName.CreateNotify: + return ToString (CreateWindowEvent); + case XEventName.DestroyNotify: + return ToString (DestroyWindowEvent); + case XEventName.Expose: + return ToString (ExposeEvent); + case XEventName.FocusIn: + case XEventName.FocusOut: + return ToString (FocusChangeEvent); + case XEventName.GraphicsExpose: + return ToString (GraphicsExposeEvent); + case XEventName.GravityNotify: + return ToString (GravityEvent); + case XEventName.KeymapNotify: + return ToString (KeymapEvent); + case XEventName.MapNotify: + return ToString (MapEvent); + case XEventName.MappingNotify: + return ToString (MappingEvent); + case XEventName.MapRequest: + return ToString (MapRequestEvent); + case XEventName.MotionNotify: + return ToString (MotionEvent); + case XEventName.NoExpose: + return ToString (NoExposeEvent); + case XEventName.PropertyNotify: + return ToString (PropertyEvent); + case XEventName.ReparentNotify: + return ToString (ReparentEvent); + case XEventName.ResizeRequest: + return ToString (ResizeRequestEvent); + case XEventName.SelectionClear: + return ToString (SelectionClearEvent); + case XEventName.SelectionNotify: + return ToString (SelectionEvent); + case XEventName.SelectionRequest: + return ToString (SelectionRequestEvent); + case XEventName.UnmapNotify: + return ToString (UnmapEvent); + case XEventName.VisibilityNotify: + return ToString (VisibilityEvent); + case XEventName.EnterNotify: + case XEventName.LeaveNotify: + return ToString (CrossingEvent); + default: + return type.ToString (); + } + } + + public static string ToString (object ev) + { + string result = string.Empty; + Type type = ev.GetType (); + FieldInfo [] fields = type.GetFields (System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance); + for (int i = 0; i < fields.Length; i++) { + if (result != string.Empty) { + result += ", "; + } + object value = fields [i].GetValue (ev); + result += fields [i].Name + "=" + (value == null ? "" : value.ToString ()); + } + return type.Name + " (" + result + ")"; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSetWindowAttributes { + internal IntPtr background_pixmap; + internal IntPtr background_pixel; + internal IntPtr border_pixmap; + internal IntPtr border_pixel; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_redirect; + internal IntPtr colormap; + internal IntPtr cursor; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowAttributes { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal int depth; + internal IntPtr visual; + internal IntPtr root; + internal int c_class; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr colormap; + internal bool map_installed; + internal MapState map_state; + internal IntPtr all_event_masks; + internal IntPtr your_event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_direct; + internal IntPtr screen; + + public override string ToString () + { + return XEvent.ToString (this); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XTextProperty { + internal string value; + internal IntPtr encoding; + internal int format; + internal IntPtr nitems; + } + + internal enum XWindowClass { + InputOutput = 1, + InputOnly = 2 + } + + internal enum XEventName { + KeyPress = 2, + KeyRelease = 3, + ButtonPress = 4, + ButtonRelease = 5, + MotionNotify = 6, + EnterNotify = 7, + LeaveNotify = 8, + FocusIn = 9, + FocusOut = 10, + KeymapNotify = 11, + Expose = 12, + GraphicsExpose = 13, + NoExpose = 14, + VisibilityNotify = 15, + CreateNotify = 16, + DestroyNotify = 17, + UnmapNotify = 18, + MapNotify = 19, + MapRequest = 20, + ReparentNotify = 21, + ConfigureNotify = 22, + ConfigureRequest = 23, + GravityNotify = 24, + ResizeRequest = 25, + CirculateNotify = 26, + CirculateRequest = 27, + PropertyNotify = 28, + SelectionClear = 29, + SelectionRequest = 30, + SelectionNotify = 31, + ColormapNotify = 32, + ClientMessage = 33, + MappingNotify = 34, + GenericEvent = 35, + LASTEvent + } + + [Flags] + internal enum SetWindowValuemask { + Nothing = 0, + BackPixmap = 1, + BackPixel = 2, + BorderPixmap = 4, + BorderPixel = 8, + BitGravity = 16, + WinGravity = 32, + BackingStore = 64, + BackingPlanes = 128, + BackingPixel = 256, + OverrideRedirect = 512, + SaveUnder = 1024, + EventMask = 2048, + DontPropagate = 4096, + ColorMap = 8192, + Cursor = 16384 + } + + internal enum SendEventValues { + PointerWindow = 0, + InputFocus = 1 + } + + internal enum CreateWindowArgs { + CopyFromParent = 0, + ParentRelative = 1, + InputOutput = 1, + InputOnly = 2 + } + + internal enum Gravity { + ForgetGravity = 0, + NorthWestGravity= 1, + NorthGravity = 2, + NorthEastGravity= 3, + WestGravity = 4, + CenterGravity = 5, + EastGravity = 6, + SouthWestGravity= 7, + SouthGravity = 8, + SouthEastGravity= 9, + StaticGravity = 10 + } + + internal enum XKeySym : uint { + XK_BackSpace = 0xFF08, + XK_Tab = 0xFF09, + XK_Clear = 0xFF0B, + XK_Return = 0xFF0D, + XK_Home = 0xFF50, + XK_Left = 0xFF51, + XK_Up = 0xFF52, + XK_Right = 0xFF53, + XK_Down = 0xFF54, + XK_Page_Up = 0xFF55, + XK_Page_Down = 0xFF56, + XK_End = 0xFF57, + XK_Begin = 0xFF58, + XK_Menu = 0xFF67, + XK_Shift_L = 0xFFE1, + XK_Shift_R = 0xFFE2, + XK_Control_L = 0xFFE3, + XK_Control_R = 0xFFE4, + XK_Caps_Lock = 0xFFE5, + XK_Shift_Lock = 0xFFE6, + XK_Meta_L = 0xFFE7, + XK_Meta_R = 0xFFE8, + XK_Alt_L = 0xFFE9, + XK_Alt_R = 0xFFEA, + XK_Super_L = 0xFFEB, + XK_Super_R = 0xFFEC, + XK_Hyper_L = 0xFFED, + XK_Hyper_R = 0xFFEE, + } + + [Flags] + internal enum EventMask { + NoEventMask = 0, + KeyPressMask = 1<<0, + KeyReleaseMask = 1<<1, + ButtonPressMask = 1<<2, + ButtonReleaseMask = 1<<3, + EnterWindowMask = 1<<4, + LeaveWindowMask = 1<<5, + PointerMotionMask = 1<<6, + PointerMotionHintMask = 1<<7, + Button1MotionMask = 1<<8, + Button2MotionMask = 1<<9, + Button3MotionMask = 1<<10, + Button4MotionMask = 1<<11, + Button5MotionMask = 1<<12, + ButtonMotionMask = 1<<13, + KeymapStateMask = 1<<14, + ExposureMask = 1<<15, + VisibilityChangeMask = 1<<16, + StructureNotifyMask = 1<<17, + ResizeRedirectMask = 1<<18, + SubstructureNotifyMask = 1<<19, + SubstructureRedirectMask= 1<<20, + FocusChangeMask = 1<<21, + PropertyChangeMask = 1<<22, + ColormapChangeMask = 1<<23, + OwnerGrabButtonMask = 1<<24 + } + + [Flags] + internal enum RandrEventMask + { + RRScreenChangeNotify = 1 << 0, + +/* V1.2 additions */ + RRCrtcChangeNotifyMask = 1 << 1, + RROutputChangeNotifyMask = 1 << 2, + RROutputPropertyNotifyMask = 1 << 3, + +/* V1.4 additions */ + RRProviderChangeNotifyMask = 1 << 4, + RRProviderPropertyNotifyMask = 1 << 5, + RRResourceChangeNotifyMask = 1 << 6, + +/* V1.6 additions */ + RRLeaseNotifyMask = 1 << 7 + } + + internal enum RandrEvent + { + RRScreenChangeNotify = 0, + + /* V1.2 additions */ + RRNotify = 1 + } + + internal enum RandrRotate + { + /* used in the rotation field; rotation and reflection in 0.1 proto. */ + RR_Rotate_0 = 1, + RR_Rotate_90 = 2, + RR_Rotate_180 = 4, + RR_Rotate_270 = 8 + } + + internal enum GrabMode { + GrabModeSync = 0, + GrabModeAsync = 1 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XStandardColormap { + internal IntPtr colormap; + internal IntPtr red_max; + internal IntPtr red_mult; + internal IntPtr green_max; + internal IntPtr green_mult; + internal IntPtr blue_max; + internal IntPtr blue_mult; + internal IntPtr base_pixel; + internal IntPtr visualid; + internal IntPtr killid; + } + + [StructLayout(LayoutKind.Sequential, Pack=2)] + internal struct XColor { + internal IntPtr pixel; + internal ushort red; + internal ushort green; + internal ushort blue; + internal byte flags; + internal byte pad; + } + + internal enum Atom { + AnyPropertyType = 0, + XA_PRIMARY = 1, + XA_SECONDARY = 2, + XA_ARC = 3, + XA_ATOM = 4, + XA_BITMAP = 5, + XA_CARDINAL = 6, + XA_COLORMAP = 7, + XA_CURSOR = 8, + XA_CUT_BUFFER0 = 9, + XA_CUT_BUFFER1 = 10, + XA_CUT_BUFFER2 = 11, + XA_CUT_BUFFER3 = 12, + XA_CUT_BUFFER4 = 13, + XA_CUT_BUFFER5 = 14, + XA_CUT_BUFFER6 = 15, + XA_CUT_BUFFER7 = 16, + XA_DRAWABLE = 17, + XA_FONT = 18, + XA_INTEGER = 19, + XA_PIXMAP = 20, + XA_POINT = 21, + XA_RECTANGLE = 22, + XA_RESOURCE_MANAGER = 23, + XA_RGB_COLOR_MAP = 24, + XA_RGB_BEST_MAP = 25, + XA_RGB_BLUE_MAP = 26, + XA_RGB_DEFAULT_MAP = 27, + XA_RGB_GRAY_MAP = 28, + XA_RGB_GREEN_MAP = 29, + XA_RGB_RED_MAP = 30, + XA_STRING = 31, + XA_VISUALID = 32, + XA_WINDOW = 33, + XA_WM_COMMAND = 34, + XA_WM_HINTS = 35, + XA_WM_CLIENT_MACHINE = 36, + XA_WM_ICON_NAME = 37, + XA_WM_ICON_SIZE = 38, + XA_WM_NAME = 39, + XA_WM_NORMAL_HINTS = 40, + XA_WM_SIZE_HINTS = 41, + XA_WM_ZOOM_HINTS = 42, + XA_MIN_SPACE = 43, + XA_NORM_SPACE = 44, + XA_MAX_SPACE = 45, + XA_END_SPACE = 46, + XA_SUPERSCRIPT_X = 47, + XA_SUPERSCRIPT_Y = 48, + XA_SUBSCRIPT_X = 49, + XA_SUBSCRIPT_Y = 50, + XA_UNDERLINE_POSITION = 51, + XA_UNDERLINE_THICKNESS = 52, + XA_STRIKEOUT_ASCENT = 53, + XA_STRIKEOUT_DESCENT = 54, + XA_ITALIC_ANGLE = 55, + XA_X_HEIGHT = 56, + XA_QUAD_WIDTH = 57, + XA_WEIGHT = 58, + XA_POINT_SIZE = 59, + XA_RESOLUTION = 60, + XA_COPYRIGHT = 61, + XA_NOTICE = 62, + XA_FONT_NAME = 63, + XA_FAMILY_NAME = 64, + XA_FULL_NAME = 65, + XA_CAP_HEIGHT = 66, + XA_WM_CLASS = 67, + XA_WM_TRANSIENT_FOR = 68, + + XA_LAST_PREDEFINED = 68 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XScreen { + internal IntPtr ext_data; + internal IntPtr display; + internal IntPtr root; + internal int width; + internal int height; + internal int mwidth; + internal int mheight; + internal int ndepths; + internal IntPtr depths; + internal int root_depth; + internal IntPtr root_visual; + internal IntPtr default_gc; + internal IntPtr cmap; + internal IntPtr white_pixel; + internal IntPtr black_pixel; + internal int max_maps; + internal int min_maps; + internal int backing_store; + internal bool save_unders; + internal IntPtr root_input_mask; + } + + [Flags] + internal enum ChangeWindowFlags { + CWX = 1<<0, + CWY = 1<<1, + CWWidth = 1<<2, + CWHeight = 1<<3, + CWBorderWidth = 1<<4, + CWSibling = 1<<5, + CWStackMode = 1<<6 + } + + internal enum StackMode { + Above = 0, + Below = 1, + TopIf = 2, + BottomIf = 3, + Opposite = 4 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowChanges { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr sibling; + internal StackMode stack_mode; + } + + [Flags] + internal enum ColorFlags { + DoRed = 1<<0, + DoGreen = 1<<1, + DoBlue = 1<<2 + } + + internal enum NotifyMode { + NotifyNormal = 0, + NotifyGrab = 1, + NotifyUngrab = 2 + } + + internal enum NotifyDetail { + NotifyAncestor = 0, + NotifyVirtual = 1, + NotifyInferior = 2, + NotifyNonlinear = 3, + NotifyNonlinearVirtual = 4, + NotifyPointer = 5, + NotifyPointerRoot = 6, + NotifyDetailNone = 7 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MotifWmHints { + internal IntPtr flags; + internal IntPtr functions; + internal IntPtr decorations; + internal IntPtr input_mode; + internal IntPtr status; + + public override string ToString () + { + return string.Format("MotifWmHints _transientChildren = new HashSet(); + private X11Window _transientParent; + public object SyncRoot { get; } = new object(); + + class InputEventContainer + { + public RawInputEventArgs Event; + } + private readonly Queue _inputQueue = new Queue(); + private InputEventContainer _lastEvent; + + public X11Window(AvaloniaX11Platform platform, bool popup) + { + _platform = platform; + _popup = popup; + _x11 = platform.Info; + _mouse = platform.MouseDevice; + _keyboard = platform.KeyboardDevice; + _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing, + XNames.XNClientWindow, _handle, IntPtr.Zero); + + XSetWindowAttributes attr = new XSetWindowAttributes(); + var valueMask = default(SetWindowValuemask); + + attr.backing_store = 1; + attr.bit_gravity = Gravity.NorthWestGravity; + attr.win_gravity = Gravity.NorthWestGravity; + valueMask |= SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel + | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore + | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; + + if (popup) + { + attr.override_redirect = true; + valueMask |= SetWindowValuemask.OverrideRedirect; + } + + _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, + 24, + (int)CreateWindowArgs.InputOutput, IntPtr.Zero, + new UIntPtr((uint)valueMask), ref attr); + _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24, + (int)CreateWindowArgs.InputOutput, + IntPtr.Zero, + new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | + SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + + Handle = new PlatformHandle(_handle, "XID"); + _realSize = new PixelSize(300, 200); + platform.Windows[_handle] = OnEvent; + XEventMask ignoredMask = XEventMask.SubstructureRedirectMask + | XEventMask.ResizeRedirectMask + | XEventMask.PointerMotionHintMask; + if (platform.XI2 != null) + ignoredMask |= platform.XI2.AddWindow(_handle, this); + var mask = new IntPtr(0xffffff ^ (int)ignoredMask); + XSelectInput(_x11.Display, _handle, mask); + var protocols = new[] + { + _x11.Atoms.WM_DELETE_WINDOW + }; + XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, + 32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1); + + var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService(); + var surfaces = new List + { + new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling) + }; + if (feature != null) + surfaces.Insert(0, + new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, + new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); + Surfaces = surfaces.ToArray(); + UpdateMotifHints(); + XFlush(_x11.Display); + } + + class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo + { + private readonly X11Window _window; + private readonly IntPtr _display; + private readonly IntPtr _parent; + + public SurfaceInfo(X11Window window, IntPtr display, IntPtr parent, IntPtr xid) + { + _window = window; + _display = display; + _parent = parent; + Handle = xid; + } + public IntPtr Handle { get; } + + public PixelSize Size + { + get + { + XLockDisplay(_display); + XGetGeometry(_display, _parent, out var geo); + XResizeWindow(_display, Handle, geo.width, geo.height); + XFlush(_display); + XSync(_display, true); + XUnlockDisplay(_display); + return new PixelSize(geo.width, geo.height); + } + } + + public double Scaling => _window.Scaling; + } + + void UpdateMotifHints() + { + var functions = MotifFunctions.Move | MotifFunctions.Close | MotifFunctions.Resize | + MotifFunctions.Minimize | MotifFunctions.Maximize; + var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border | + MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH; + + if (_popup || !_systemDecorations) + { + decorations = 0; + } + + if (!_canResize) + { + functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); + decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); + } + + var hints = new MotifWmHints + { + flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)), + decorations = new IntPtr((int)decorations), + functions = new IntPtr((int)functions) + }; + + XChangeProperty(_x11.Display, _handle, + _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32, + PropertyMode.Replace, ref hints, 5); + } + + void UpdateSizeHints(PixelSize? preResize) + { + var min = _minMaxSize.minSize; + var max = _minMaxSize.maxSize; + + if (!_canResize) + max = min = _realSize; + + if (preResize.HasValue) + { + var desired = preResize.Value; + max = new PixelSize(Math.Max(desired.Width, max.Width), Math.Max(desired.Height, max.Height)); + min = new PixelSize(Math.Min(desired.Width, min.Width), Math.Min(desired.Height, min.Height)); + } + + var hints = new XSizeHints + { + min_width = min.Width, + min_height = min.Height + }; + hints.height_inc = hints.width_inc = 1; + var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; + // People might be passing double.MaxValue + if (max.Width < 100000 && max.Height < 100000) + { + hints.max_width = max.Width; + hints.max_height = max.Height; + flags |= XSizeHintsFlags.PMaxSize; + } + + hints.flags = (IntPtr)flags; + + XSetWMNormalHints(_x11.Display, _handle, ref hints); + } + + public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling); + + public double Scaling + { + get + { + lock (SyncRoot) + return _scaling; + + } + private set => _scaling = value; + } + + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + //TODO + public Action ScalingChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public Func Closing { get; set; } + public Action WindowStateChanged { get; set; } + public Action Closed { get; set; } + public Action PositionChanged { get; set; } + + public IRenderer CreateRenderer(IRenderRoot root) => + new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + + void OnEvent(XEvent ev) + { + lock (SyncRoot) + OnEventSync(ev); + } + void OnEventSync(XEvent ev) + { + if(XFilterEvent(ref ev, _handle)) + return; + if (ev.type == XEventName.MapNotify) + { + _mapped = true; + XMapWindow(_x11.Display, _renderHandle); + } + else if (ev.type == XEventName.UnmapNotify) + _mapped = false; + else if (ev.type == XEventName.Expose) + { + if (!_triggeredExpose) + { + _triggeredExpose = true; + Dispatcher.UIThread.Post(() => + { + _triggeredExpose = false; + DoPaint(); + }, DispatcherPriority.Render); + } + } + else if (ev.type == XEventName.FocusIn) + { + if (ActivateTransientChildIfNeeded()) + return; + Activated?.Invoke(); + } + else if (ev.type == XEventName.FocusOut) + Deactivated?.Invoke(); + else if (ev.type == XEventName.MotionNotify) + MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state); + else if (ev.type == XEventName.LeaveNotify) + MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state); + else if (ev.type == XEventName.PropertyNotify) + { + OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0); + } + else if (ev.type == XEventName.ButtonPress) + { + if (ActivateTransientChildIfNeeded()) + return; + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown + : RawMouseEventType.RightButtonDown, ref ev, ev.ButtonEvent.state); + else + { + var delta = ev.ButtonEvent.button == 4 + ? new Vector(0, 1) + : ev.ButtonEvent.button == 5 + ? new Vector(0, -1) + : ev.ButtonEvent.button == 6 + ? new Vector(1, 0) + : new Vector(-1, 0); + ScheduleInput(new RawMouseWheelEventArgs(_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), + _inputRoot, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), delta, + TranslateModifiers(ev.ButtonEvent.state)), ref ev); + } + + } + else if (ev.type == XEventName.ButtonRelease) + { + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp + : RawMouseEventType.RightButtonUp, ref ev, ev.ButtonEvent.state); + } + else if (ev.type == XEventName.ConfigureNotify) + { + if (ev.ConfigureEvent.window != _handle) + return; + var needEnqueue = (_configure == null); + _configure = ev.ConfigureEvent; + if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event) + _configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y); + else + { + XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow, + 0, 0, + out var tx, out var ty, out _); + _configurePoint = new PixelPoint(tx, ty); + } + if (needEnqueue) + Dispatcher.UIThread.Post(() => + { + if (_configure == null) + return; + var cev = _configure.Value; + var npos = _configurePoint.Value; + _configure = null; + _configurePoint = null; + + var nsize = new PixelSize(cev.width, cev.height); + var changedSize = _realSize != nsize; + var changedPos = _position == null || npos != _position; + _realSize = nsize; + _position = npos; + bool updatedSizeViaScaling = false; + if (changedPos) + { + PositionChanged?.Invoke(npos); + updatedSizeViaScaling = UpdateScaling(); + } + + if (changedSize && !updatedSizeViaScaling) + Resized?.Invoke(ClientSize); + + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + }, DispatcherPriority.Layout); + XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); + } + else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle) + { + Cleanup(); + } + else if (ev.type == XEventName.ClientMessage) + { + if (ev.ClientMessageEvent.message_type == _x11.Atoms.WM_PROTOCOLS) + { + if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW) + { + if (Closing?.Invoke() != true) + Dispose(); + } + + } + } + else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease) + { + if (ActivateTransientChildIfNeeded()) + return; + var buffer = stackalloc byte[40]; + + var latinKeysym = XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, 0); + ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), + ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, + X11KeyTransform.ConvertKey(latinKeysym), TranslateModifiers(ev.KeyEvent.state)), ref ev); + + if (ev.type == XEventName.KeyPress) + { + var len = Xutf8LookupString(_xic, ref ev, buffer, 40, out _, out _); + if (len != 0) + { + var text = Encoding.UTF8.GetString(buffer, len); + if (text.Length == 1) + { + if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL + return; + } + ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text), + ref ev); + } + } + } + } + + private bool UpdateScaling() + { + lock (SyncRoot) + { + var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(Position)); + var newScaling = monitor?.PixelDensity ?? Scaling; + if (Scaling != newScaling) + { + Console.WriteLine( + $"Updating scaling from {Scaling} to {newScaling} as a response to position change to {Position}"); + var oldScaledSize = ClientSize; + Scaling = newScaling; + ScalingChanged?.Invoke(Scaling); + SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); + Resize(oldScaledSize, true); + return true; + } + + return false; + } + } + + private WindowState _lastWindowState; + public WindowState WindowState + { + get => _lastWindowState; + set + { + if(_lastWindowState == value) + return; + _lastWindowState = value; + if (value == WindowState.Minimized) + { + XIconifyWindow(_x11.Display, _handle, _x11.DefaultScreen); + } + else if (value == WindowState.Maximized) + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + else + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + } + } + + private void OnPropertyChange(IntPtr atom, bool hasValue) + { + if (atom == _x11.Atoms._NET_WM_STATE) + { + WindowState state = WindowState.Normal; + if(hasValue) + { + + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256), + false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _, + out var prop); + int maximized = 0; + var pitems = (IntPtr*)prop.ToPointer(); + for (var c = 0; c < nitems.ToInt32(); c++) + { + if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN) + { + state = WindowState.Minimized; + break; + } + + if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ || + pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT) + { + maximized++; + if (maximized == 2) + { + state = WindowState.Maximized; + break; + } + } + } + XFree(prop); + } + if (_lastWindowState != state) + { + _lastWindowState = state; + WindowStateChanged?.Invoke(state); + } + } + + } + + + InputModifiers TranslateModifiers(XModifierMask state) + { + var rv = default(InputModifiers); + if (state.HasFlag(XModifierMask.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.MiddleMouseButton; + if (state.HasFlag(XModifierMask.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(XModifierMask.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(XModifierMask.Mod1Mask)) + rv |= InputModifiers.Alt; + if (state.HasFlag(XModifierMask.Mod4Mask)) + rv |= InputModifiers.Windows; + return rv; + } + + private bool _systemDecorations = true; + private bool _canResize = true; + private (Size minSize, Size maxSize) _scaledMinMaxSize; + private (PixelSize minSize, PixelSize maxSize) _minMaxSize; + private double _scaling = 1; + + void ScheduleInput(RawInputEventArgs args, ref XEvent xev) + { + _x11.LastActivityTimestamp = xev.ButtonEvent.time; + ScheduleInput(args); + } + + public void ScheduleInput(RawInputEventArgs args) + { + if (args is RawMouseEventArgs mouse) + mouse.Position = mouse.Position / Scaling; + if (args is RawDragEvent drag) + drag.Location = drag.Location / Scaling; + + _lastEvent = new InputEventContainer() {Event = args}; + _inputQueue.Enqueue(_lastEvent); + if (_inputQueue.Count == 1) + { + Dispatcher.UIThread.Post(() => + { + while (_inputQueue.Count > 0) + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + var ev = _inputQueue.Dequeue(); + Input?.Invoke(ev.Event); + } + }, DispatcherPriority.Input); + } + } + + void MouseEvent(RawMouseEventType type, ref XEvent ev, XModifierMask mods) + { + var mev = new RawMouseEventArgs( + _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot, + type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods)); + if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma) + if (ma.Type == RawMouseEventType.Move) + { + _lastEvent.Event = mev; + return; + } + ScheduleInput(mev, ref ev); + } + + void DoPaint() + { + _invalidated = false; + Paint?.Invoke(new Rect()); + } + + public void Invalidate(Rect rect) + { + if(_invalidated) + return; + _invalidated = true; + Dispatcher.UIThread.InvokeAsync(() => + { + if (_mapped) + DoPaint(); + }); + } + + public IInputRoot InputRoot => _inputRoot; + + public void SetInputRoot(IInputRoot inputRoot) + { + _inputRoot = inputRoot; + } + + public void Dispose() + { + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + Cleanup(); + } + } + + void Cleanup() + { + SetTransientParent(null, false); + if (_xic != IntPtr.Zero) + { + XDestroyIC(_xic); + _xic = IntPtr.Zero; + } + + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + _platform.Windows.Remove(_handle); + _platform.XI2?.OnWindowDestroyed(_handle); + _handle = IntPtr.Zero; + Closed?.Invoke(); + } + + if (_renderHandle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _renderHandle); + _renderHandle = IntPtr.Zero; + } + } + + bool ActivateTransientChildIfNeeded() + { + if (_transientChildren.Count == 0) + return false; + var child = _transientChildren.First(); + if (!child.ActivateTransientChildIfNeeded()) + child.Activate(); + return true; + } + + void SetTransientParent(X11Window window, bool informServer = true) + { + _transientParent?._transientChildren.Remove(this); + _transientParent = window; + _transientParent?._transientChildren.Add(this); + if (informServer) + XSetTransientForHint(_x11.Display, _handle, _transientParent?._handle ?? IntPtr.Zero); + } + + public void Show() + { + SetTransientParent(null); + ShowCore(); + } + + void ShowCore() + { + XMapWindow(_x11.Display, _handle); + XFlush(_x11.Display); + } + + public void Hide() => XUnmapWindow(_x11.Display, _handle); + + + public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); + + public PixelPoint PointToScreen(Point point) => new PixelPoint( + (int)(point.X * Scaling + Position.X), + (int)(point.Y * Scaling + Position.Y)); + + public void SetSystemDecorations(bool enabled) + { + _systemDecorations = enabled; + UpdateMotifHints(); + } + + + public void Resize(Size clientSize) => Resize(clientSize, false); + + PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling)); + + void Resize(Size clientSize, bool force) + { + if (!force && clientSize == ClientSize) + return; + + var needImmediatePopupResize = clientSize != ClientSize; + + var pixelSize = ToPixelSize(clientSize); + UpdateSizeHints(pixelSize); + XConfigureResizeWindow(_x11.Display, _handle, pixelSize); + XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize); + XFlush(_x11.Display); + + if (force || (_popup && needImmediatePopupResize)) + { + _realSize = pixelSize; + Resized?.Invoke(ClientSize); + } + } + + public void CanResize(bool value) + { + _canResize = value; + UpdateMotifHints(); + UpdateSizeHints(null); + } + + public void SetCursor(IPlatformHandle cursor) + { + if (cursor == null) + XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor); + else + { + if (cursor.HandleDescriptor != "XCURSOR") + throw new ArgumentException("Expected XCURSOR handle type"); + XDefineCursor(_x11.Display, _handle, cursor.Handle); + } + } + + public IPlatformHandle Handle { get; } + + public PixelPoint Position + { + get => _position ?? default; + set + { + var changes = new XWindowChanges + { + x = (int)value.X, + y = (int)value.Y + }; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY, + ref changes); + XFlush(_x11.Display); + + } + } + + public IMouseDevice MouseDevice => _mouse; + + public void Activate() + { + if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) + { + SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp, + IntPtr.Zero); + } + else + { + XRaiseWindow(_x11.Display, _handle); + XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero); + } + } + + + public IScreenImpl Screen => _platform.Screens; + + public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) + .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); + + + void SendNetWMMessage(IntPtr message_type, IntPtr l0, + IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null) + { + var xev = new XEvent + { + ClientMessageEvent = + { + type = XEventName.ClientMessage, + send_event = true, + window = _handle, + message_type = message_type, + format = 32, + ptr1 = l0, + ptr2 = l1 ?? IntPtr.Zero, + ptr3 = l2 ?? IntPtr.Zero, + ptr4 = l3 ?? IntPtr.Zero + } + }; + xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero; + XSendEvent(_x11.Display, _x11.RootWindow, false, + new IntPtr((int)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev); + + } + + void BeginMoveResize(NetWmMoveResize side) + { + var pos = GetCursorPos(_x11); + XUngrabPointer(_x11.Display, new IntPtr(0)); + SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y, + (IntPtr) side, + (IntPtr) 1, (IntPtr)1); // left button + } + + public void BeginMoveDrag() + { + BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE); + } + + public void BeginResizeDrag(WindowEdge edge) + { + var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL; + if (edge == WindowEdge.East) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_RIGHT; + if (edge == WindowEdge.North) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOP; + if (edge == WindowEdge.South) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOM; + if (edge == WindowEdge.West) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_LEFT; + if (edge == WindowEdge.NorthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + if (edge == WindowEdge.NorthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPLEFT; + if (edge == WindowEdge.SouthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + if (edge == WindowEdge.SouthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + BeginMoveResize(side); + } + + public void SetTitle(string title) + { + var data = Encoding.UTF8.GetBytes(title); + fixed (void* pdata = data) + { + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8, + PropertyMode.Replace, pdata, data.Length); + XStoreName(_x11.Display, _handle, title); + } + } + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + _scaledMinMaxSize = (minSize, maxSize); + var min = new PixelSize( + (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling), + (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling)); + + const int maxDim = 100000; + var max = new PixelSize( + (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, minSize.Width * Scaling)), + (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, minSize.Height * Scaling))); + + _minMaxSize = (min, max); + UpdateSizeHints(null); + } + + public void SetTopmost(bool value) + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero); + } + + public void ShowDialog(IWindowImpl parent) + { + SetTransientParent((X11Window)parent); + ShowCore(); + } + + public void SetIcon(IWindowIconImpl icon) + { + var data = ((X11IconData)icon).Data; + fixed (void* pdata = data) + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON, + new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace, + pdata, data.Length); + } + + public void ShowTaskbarIcon(bool value) + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero); + } + } +} diff --git a/src/Avalonia.X11/XError.cs b/src/Avalonia.X11/XError.cs new file mode 100644 index 0000000000..2cc8f63c96 --- /dev/null +++ b/src/Avalonia.X11/XError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Avalonia.X11 +{ + static class XError + { + private static readonly XErrorHandler s_errorHandlerDelegate = Handler; + public static XErrorEvent LastError; + static int Handler(IntPtr display, ref XErrorEvent error) + { + LastError = error; + return 0; + } + + public static void ThrowLastError(string desc) + { + var err = LastError; + LastError = new XErrorEvent(); + if (err.error_code == 0) + throw new X11Exception(desc); + throw new X11Exception(desc + ": " + err.error_code); + + } + + public static void Init() + { + XLib.XSetErrorHandler(s_errorHandlerDelegate); + } + } +} diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs new file mode 100644 index 0000000000..ee73ccc907 --- /dev/null +++ b/src/Avalonia.X11/XI2Manager.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Avalonia.Input; +using Avalonia.Input.Raw; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + unsafe class XI2Manager + { + private X11Info _x11; + private Dictionary _clients = new Dictionary(); + class DeviceInfo + { + public int Id { get; } + public XIValuatorClassInfo[] Valuators { get; private set; } + public XIScrollClassInfo[] Scrollers { get; private set; } + public DeviceInfo(XIDeviceInfo info) + { + Id = info.Deviceid; + Update(info.Classes, info.NumClasses); + } + + public virtual void Update(XIAnyClassInfo** classes, int num) + { + var valuators = new List(); + var scrollers = new List(); + for (var c = 0; c < num; c++) + { + if (classes[c]->Type == XiDeviceClass.XIValuatorClass) + valuators.Add(*((XIValuatorClassInfo**)classes)[c]); + if (classes[c]->Type == XiDeviceClass.XIScrollClass) + scrollers.Add(*((XIScrollClassInfo**)classes)[c]); + } + + Valuators = valuators.ToArray(); + Scrollers = scrollers.ToArray(); + } + + public void UpdateValuators(Dictionary valuators) + { + foreach (var v in valuators) + { + if (Valuators.Length > v.Key) + Valuators[v.Key].Value = v.Value; + } + } + } + + class PointerDeviceInfo : DeviceInfo + { + public PointerDeviceInfo(XIDeviceInfo info) : base(info) + { + } + + public bool HasScroll(ParsedDeviceEvent ev) + { + foreach (var val in ev.Valuators) + if (Scrollers.Any(s => s.Number == val.Key)) + return true; + + return false; + } + + public bool HasMotion(ParsedDeviceEvent ev) + { + foreach (var val in ev.Valuators) + if (Scrollers.All(s => s.Number != val.Key)) + return true; + + return false; + } + + } + + private PointerDeviceInfo _pointerDevice; + private AvaloniaX11Platform _platform; + + public bool Init(AvaloniaX11Platform platform) + { + _platform = platform; + _x11 = platform.Info; + var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display, + (int)XiPredefinedDeviceId.XIAllMasterDevices, out int num); + for (var c = 0; c < num; c++) + { + if (devices[c].Use == XiDeviceType.XIMasterPointer) + { + _pointerDevice = new PointerDeviceInfo(devices[c]); + break; + } + } + if(_pointerDevice == null) + return false; + /* + int mask = 0; + + XISetMask(ref mask, XiEventType.XI_DeviceChanged); + var emask = new XIEventMask + { + Mask = &mask, + Deviceid = _pointerDevice.Id, + MaskLen = XiEventMaskLen + }; + + if (XISelectEvents(_x11.Display, _x11.RootWindow, &emask, 1) != Status.Success) + return false; + return true; + */ + return XiSelectEvents(_x11.Display, _x11.RootWindow, new Dictionary> + { + [_pointerDevice.Id] = new List + { + XiEventType.XI_DeviceChanged + } + }) == Status.Success; + } + + public XEventMask AddWindow(IntPtr xid, IXI2Client window) + { + _clients[xid] = window; + + XiSelectEvents(_x11.Display, xid, new Dictionary> + { + [_pointerDevice.Id] = new List() + { + XiEventType.XI_Motion, + XiEventType.XI_ButtonPress, + XiEventType.XI_ButtonRelease, + } + }); + + // We are taking over mouse input handling from here + return XEventMask.PointerMotionMask + | XEventMask.ButtonMotionMask + | XEventMask.Button1MotionMask + | XEventMask.Button2MotionMask + | XEventMask.Button3MotionMask + | XEventMask.Button4MotionMask + | XEventMask.Button5MotionMask + | XEventMask.ButtonPressMask + | XEventMask.ButtonReleaseMask; + } + + public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid); + + public void OnEvent(XIEvent* xev) + { + if (xev->evtype == XiEventType.XI_DeviceChanged) + { + var changed = (XIDeviceChangedEvent*)xev; + _pointerDevice.Update(changed->Classes, changed->NumClasses); + } + + //TODO: this should only be used for non-touch devices + if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) + { + var dev = (XIDeviceEvent*)xev; + if (_clients.TryGetValue(dev->EventWindow, out var client)) + OnDeviceEvent(client, new ParsedDeviceEvent(dev)); + } + } + + void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev) + { + if (ev.Type == XiEventType.XI_Motion) + { + Vector scrollDelta = default; + foreach (var v in ev.Valuators) + { + foreach (var scroller in _pointerDevice.Scrollers) + { + if (scroller.Number == v.Key) + { + var old = _pointerDevice.Valuators[scroller.Number].Value; + // Value was zero after reset, ignore the event and use it as a reference next time + if (old == 0) + continue; + var diff = (old - v.Value) / scroller.Increment; + if (scroller.ScrollType == XiScrollType.Horizontal) + scrollDelta = scrollDelta.WithX(scrollDelta.X + diff); + else + scrollDelta = scrollDelta.WithY(scrollDelta.Y + diff); + + } + } + + + } + + if (scrollDelta != default) + client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp, + client.InputRoot, ev.Position, scrollDelta, ev.Modifiers)); + if (_pointerDevice.HasMotion(ev)) + client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + RawMouseEventType.Move, ev.Position, ev.Modifiers)); + } + + if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease) + { + var down = ev.Type == XiEventType.XI_ButtonPress; + var type = + ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp) + : ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp) + : ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp) + : (RawMouseEventType?)null; + if (type.HasValue) + client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + type.Value, ev.Position, ev.Modifiers)); + } + + _pointerDevice.UpdateValuators(ev.Valuators); + } + } + + unsafe class ParsedDeviceEvent + { + public XiEventType Type { get; } + public InputModifiers Modifiers { get; } + public ulong Timestamp { get; } + public Point Position { get; } + public int Button { get; set; } + public Dictionary Valuators { get; } + public ParsedDeviceEvent(XIDeviceEvent* ev) + { + Type = ev->evtype; + Timestamp = (ulong)ev->time.ToInt64(); + var state = (XModifierMask)ev->mods.Effective; + if (state.HasFlag(XModifierMask.ShiftMask)) + Modifiers |= InputModifiers.Shift; + if (state.HasFlag(XModifierMask.ControlMask)) + Modifiers |= InputModifiers.Control; + if (state.HasFlag(XModifierMask.Mod1Mask)) + Modifiers |= InputModifiers.Alt; + if (state.HasFlag(XModifierMask.Mod4Mask)) + Modifiers |= InputModifiers.Windows; + + if (ev->buttons.MaskLen > 0) + { + var buttons = ev->buttons.Mask; + if (XIMaskIsSet(buttons, 1)) + Modifiers |= InputModifiers.LeftMouseButton; + + if (XIMaskIsSet(buttons, 2)) + Modifiers |= InputModifiers.MiddleMouseButton; + + if (XIMaskIsSet(buttons, 3)) + Modifiers |= InputModifiers.RightMouseButton; + } + + Valuators = new Dictionary(); + Position = new Point(ev->event_x, ev->event_y); + var values = ev->valuators.Values; + for (var c = 0; c < ev->valuators.MaskLen * 8; c++) + if (XIMaskIsSet(ev->valuators.Mask, c)) + Valuators[c] = *values++; + if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease) + Button = ev->detail; + } + } + + interface IXI2Client + { + IInputRoot InputRoot { get; } + void ScheduleInput(RawInputEventArgs args); + } +} diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs new file mode 100644 index 0000000000..772bd1989c --- /dev/null +++ b/src/Avalonia.X11/XIStructs.cs @@ -0,0 +1,283 @@ +using System; +using System.Runtime.InteropServices; +using Bool = System.Boolean; +using Atom = System.IntPtr; +// ReSharper disable IdentifierTypo +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable MemberCanBePrivate.Global +#pragma warning disable 649 + +namespace Avalonia.X11 +{ + [StructLayout(LayoutKind.Sequential)] + struct XIAddMasterInfo + { + public int Type; + public IntPtr Name; + public Bool SendCore; + public Bool Enable; + } + + [StructLayout(LayoutKind.Sequential)] + struct XIRemoveMasterInfo + { + public int Type; + public int Deviceid; + public int ReturnMode; /* AttachToMaster, Floating */ + public int ReturnPointer; + public int ReturnKeyboard; + }; + + [StructLayout(LayoutKind.Sequential)] + struct XIAttachSlaveInfo + { + public int Type; + public int Deviceid; + public int NewMaster; + }; + + [StructLayout(LayoutKind.Sequential)] + struct XIDetachSlaveInfo + { + public int Type; + public int Deviceid; + }; + + [StructLayout(LayoutKind.Explicit)] + struct XIAnyHierarchyChangeInfo + { + [FieldOffset(0)] + public int type; /* must be first element */ + [FieldOffset(4)] + public XIAddMasterInfo add; + [FieldOffset(4)] + public XIRemoveMasterInfo remove; + [FieldOffset(4)] + public XIAttachSlaveInfo attach; + [FieldOffset(4)] + public XIDetachSlaveInfo detach; + }; + + [StructLayout(LayoutKind.Sequential)] + struct XIModifierState + { + public int Base; + public int Latched; + public int Locked; + public int Effective; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIButtonState + { + public int MaskLen; + public byte* Mask; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIValuatorState + { + public int MaskLen; + public byte* Mask; + public double* Values; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIEventMask + { + public int Deviceid; + public int MaskLen; + public int* Mask; + }; + + [StructLayout(LayoutKind.Sequential)] + struct XIAnyClassInfo + { + public XiDeviceClass Type; + public int Sourceid; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIButtonClassInfo + { + public int Type; + public int Sourceid; + public int NumButtons; + public IntPtr* Labels; + public XIButtonState State; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIKeyClassInfo + { + public int Type; + public int Sourceid; + public int NumKeycodes; + public int* Keycodes; + }; + + [StructLayout(LayoutKind.Sequential)] + struct XIValuatorClassInfo + { + public int Type; + public int Sourceid; + public int Number; + public IntPtr Label; + public double Min; + public double Max; + public double Value; + public int Resolution; + public int Mode; + }; + +/* new in XI 2.1 */ + [StructLayout(LayoutKind.Sequential)] + struct XIScrollClassInfo + { + public int Type; + public int Sourceid; + public int Number; + public XiScrollType ScrollType; + public double Increment; + public int Flags; + }; + + enum XiScrollType + { + Vertical = 1, + Horizontal = 2 + } + + [StructLayout(LayoutKind.Sequential)] + struct XITouchClassInfo + { + public int Type; + public int Sourceid; + public int Mode; + public int NumTouches; + }; + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIDeviceInfo + { + public int Deviceid; + public IntPtr Name; + public XiDeviceType Use; + public int Attachment; + public Bool Enabled; + public int NumClasses; + public XIAnyClassInfo** Classes; + } + + enum XiDeviceType + { + XIMasterPointer = 1, + XIMasterKeyboard = 2, + XISlavePointer = 3, + XISlaveKeyboard = 4, + XIFloatingSlave = 5 + } + + enum XiPredefinedDeviceId : int + { + XIAllDevices = 0, + XIAllMasterDevices = 1 + } + + enum XiDeviceClass + { + XIKeyClass = 0, + XIButtonClass = 1, + XIValuatorClass = 2, + XIScrollClass = 3, + XITouchClass = 8, + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIDeviceChangedEvent + { + public int Type; /* GenericEvent */ + public ulong Serial; /* # of last request processed by server */ + public Bool SendEvent; /* true if this came from a SendEvent request */ + public IntPtr Display; /* Display the event was read from */ + public int Extension; /* XI extension offset */ + public int Evtype; /* XI_DeviceChanged */ + public IntPtr Time; + public int Deviceid; /* id of the device that changed */ + public int Sourceid; /* Source for the new classes. */ + public int Reason; /* Reason for the change */ + public int NumClasses; + public XIAnyClassInfo** Classes; /* same as in XIDeviceInfo */ + } + + [StructLayout(LayoutKind.Sequential)] + struct XIDeviceEvent + { + public XEventName type; /* GenericEvent */ + public ulong serial; /* # of last request processed by server */ + public Bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public int extension; /* XI extension offset */ + public XiEventType evtype; + public IntPtr time; + public int deviceid; + public int sourceid; + public int detail; + public IntPtr RootWindow; + public IntPtr EventWindow; + public IntPtr ChildWindow; + public double root_x; + public double root_y; + public double event_x; + public double event_y; + public int flags; + public XIButtonState buttons; + public XIValuatorState valuators; + public XIModifierState mods; + public XIModifierState group; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIEvent + { + public int type; /* GenericEvent */ + public ulong serial; /* # of last request processed by server */ + public Bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public int extension; /* XI extension offset */ + public XiEventType evtype; + public IntPtr time; + } + + enum XiEventType + { + XI_DeviceChanged = 1, + XI_KeyPress = 2, + XI_KeyRelease = 3, + XI_ButtonPress = 4, + XI_ButtonRelease = 5, + XI_Motion = 6, + XI_Enter = 7, + XI_Leave = 8, + XI_FocusIn = 9, + XI_FocusOut = 10, + XI_HierarchyChanged = 11, + XI_PropertyEvent = 12, + XI_RawKeyPress = 13, + XI_RawKeyRelease = 14, + XI_RawButtonPress = 15, + XI_RawButtonRelease = 16, + XI_RawMotion = 17, + XI_TouchBegin = 18 /* XI 2.2 */, + XI_TouchUpdate = 19, + XI_TouchEnd = 20, + XI_TouchOwnership = 21, + XI_RawTouchBegin = 22, + XI_RawTouchUpdate = 23, + XI_RawTouchEnd = 24, + XI_BarrierHit = 25 /* XI 2.3 */, + XI_BarrierLeave = 26, + XI_LASTEVENT = XI_BarrierLeave, + } + +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs new file mode 100644 index 0000000000..8a146f922d --- /dev/null +++ b/src/Avalonia.X11/XLib.cs @@ -0,0 +1,632 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +// ReSharper disable NotAccessedField.Global +// ReSharper disable UnusedMethodReturnValue.Global + +namespace Avalonia.X11 +{ + internal unsafe static class XLib + { + const string libX11 = "libX11.so.6"; + const string libX11Randr = "libXrandr.so.2"; + const string libX11Ext = "libXext.so.6"; + const string libXInput = "libXi.so.6"; + + [DllImport(libX11)] + public static extern IntPtr XOpenDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XCloseDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XSynchronize(IntPtr display, bool onoff); + + [DllImport(libX11)] + public static extern IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, + int border_width, int depth, int xclass, IntPtr visual, UIntPtr valuemask, + ref XSetWindowAttributes attributes); + + [DllImport(libX11)] + public static extern IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, int width, + int height, int border_width, IntPtr border, IntPtr background); + + [DllImport(libX11)] + public static extern int XMapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XMapSubindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapSubwindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern IntPtr XRootWindow(IntPtr display, int screen_number); + [DllImport(libX11)] + public static extern IntPtr XDefaultRootWindow(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XNextEvent(IntPtr display, out XEvent xevent); + + [DllImport(libX11)] + public static extern int XConnectionNumber(IntPtr diplay); + + [DllImport(libX11)] + public static extern int XPending(IntPtr diplay); + + [DllImport(libX11)] + public static extern IntPtr XSelectInput(IntPtr display, IntPtr window, IntPtr mask); + + [DllImport(libX11)] + public static extern int XDestroyWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XReparentWindow(IntPtr display, IntPtr window, IntPtr parent, int x, int y); + + [DllImport(libX11)] + public static extern int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, int width, int height); + + [DllImport(libX11)] + public static extern int XResizeWindow(IntPtr display, IntPtr window, int width, int height); + + [DllImport(libX11)] + public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes); + + [DllImport(libX11)] + public static extern int XFlush(IntPtr display); + + [DllImport(libX11)] + public static extern int XSetWMName(IntPtr display, IntPtr window, ref XTextProperty text_prop); + + [DllImport(libX11)] + public static extern int XStoreName(IntPtr display, IntPtr window, string window_name); + + [DllImport(libX11)] + public static extern int XFetchName(IntPtr display, IntPtr window, ref IntPtr window_name); + + [DllImport(libX11)] + public static extern int XSendEvent(IntPtr display, IntPtr window, bool propagate, IntPtr event_mask, + ref XEvent send_event); + + [DllImport(libX11)] + public static extern int XQueryTree(IntPtr display, IntPtr window, out IntPtr root_return, + out IntPtr parent_return, out IntPtr children_return, out int nchildren_return); + + [DllImport(libX11)] + public static extern int XFree(IntPtr data); + + [DllImport(libX11)] + public static extern int XRaiseWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XLowerWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XConfigureWindow(IntPtr display, IntPtr window, ChangeWindowFlags value_mask, + ref XWindowChanges values); + + public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, PixelSize size) + => XConfigureResizeWindow(display, window, size.Width, size.Height); + + public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, int width, int height) + { + var changes = new XWindowChanges + { + width = width, + height = height + }; + + return XConfigureWindow(display, window, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, + ref changes); + } + + [DllImport(libX11)] + public static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists); + + [DllImport(libX11)] + public static extern int XInternAtoms(IntPtr display, string[] atom_names, int atom_count, bool only_if_exists, + IntPtr[] atoms); + + [DllImport(libX11)] + public static extern IntPtr XGetAtomName(IntPtr display, IntPtr atom); + + public static string GetAtomName(IntPtr display, IntPtr atom) + { + var ptr = XGetAtomName(display, atom); + if (ptr == IntPtr.Zero) + return null; + var s = Marshal.PtrToStringAnsi(ptr); + XFree(ptr); + return s; + } + + [DllImport(libX11)] + public static extern int XSetWMProtocols(IntPtr display, IntPtr window, IntPtr[] protocols, int count); + + [DllImport(libX11)] + public static extern int XGrabPointer(IntPtr display, IntPtr window, bool owner_events, EventMask event_mask, + GrabMode pointer_mode, GrabMode keyboard_mode, IntPtr confine_to, IntPtr cursor, IntPtr timestamp); + + [DllImport(libX11)] + public static extern int XUngrabPointer(IntPtr display, IntPtr timestamp); + + [DllImport(libX11)] + public static extern bool XQueryPointer(IntPtr display, IntPtr window, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int win_x, out int win_y, out int keys_buttons); + + [DllImport(libX11)] + public static extern bool XTranslateCoordinates(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, + int src_y, out int intdest_x_return, out int dest_y_return, out IntPtr child_return); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, out IntPtr root, out int x, out int y, + out int width, out int height, out int border_width, out int depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + IntPtr width, IntPtr height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, IntPtr x, IntPtr y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern uint XWarpPointer(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, int src_y, + uint src_width, uint src_height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XClearWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XClearArea(IntPtr display, IntPtr window, int x, int y, int width, int height, + bool exposures); + + // Colormaps + [DllImport(libX11)] + public static extern IntPtr XDefaultScreenOfDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XScreenNumberOfScreen(IntPtr display, IntPtr Screen); + + [DllImport(libX11)] + public static extern IntPtr XDefaultVisual(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern uint XDefaultDepth(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XDefaultScreen(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XDefaultColormap(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XLookupColor(IntPtr display, IntPtr Colormap, string Coloranem, + ref XColor exact_def_color, ref XColor screen_def_color); + + [DllImport(libX11)] + public static extern int XAllocColor(IntPtr display, IntPtr Colormap, ref XColor colorcell_def); + + [DllImport(libX11)] + public static extern int XSetTransientForHint(IntPtr display, IntPtr window, IntPtr parent); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref MotifWmHints data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref uint value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref IntPtr value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, uint[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, int[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr[] data, int nelements); + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, void* data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr atoms, int nelements); + + [DllImport(libX11, CharSet = CharSet.Ansi)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, string text, int text_length); + + [DllImport(libX11)] + public static extern int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property); + + // Drawing + [DllImport(libX11)] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr window, IntPtr valuemask, ref XGCValues values); + + [DllImport(libX11)] + public static extern int XFreeGC(IntPtr display, IntPtr gc); + + [DllImport(libX11)] + public static extern int XSetFunction(IntPtr display, IntPtr gc, GXFunction function); + + [DllImport(libX11)] + internal static extern int XSetLineAttributes(IntPtr display, IntPtr gc, int line_width, GCLineStyle line_style, + GCCapStyle cap_style, GCJoinStyle join_style); + + [DllImport(libX11)] + public static extern int XDrawLine(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int x2, int y2); + + [DllImport(libX11)] + public static extern int XDrawRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XFillRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XSetWindowBackground(IntPtr display, IntPtr window, IntPtr background); + + [DllImport(libX11)] + public static extern int XCopyArea(IntPtr display, IntPtr src, IntPtr dest, IntPtr gc, int src_x, int src_y, + int width, int height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset, + IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format, + out IntPtr nitems, out IntPtr bytes_after, out IntPtr prop); + + [DllImport(libX11)] + public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time); + + [DllImport(libX11)] + public static extern int XIconifyWindow(IntPtr display, IntPtr window, int screen_number); + + [DllImport(libX11)] + public static extern int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor); + + [DllImport(libX11)] + public static extern int XUndefineCursor(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XFreeCursor(IntPtr display, IntPtr cursor); + + [DllImport(libX11)] + public static extern IntPtr XCreateFontCursor(IntPtr display, CursorFontShape shape); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, + ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width, + int height, IntPtr fg, IntPtr bg, int depth); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmap(IntPtr display, IntPtr d, int width, int height, int depth); + + [DllImport(libX11)] + public static extern IntPtr XFreePixmap(IntPtr display, IntPtr pixmap); + + [DllImport(libX11)] + public static extern int XQueryBestCursor(IntPtr display, IntPtr drawable, int width, int height, + out int best_width, out int best_height); + + [DllImport(libX11)] + public static extern IntPtr XWhitePixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern IntPtr XBlackPixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern void XGrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XUngrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XGetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints, + out IntPtr supplied_return); + + [DllImport(libX11)] + public static extern void XSetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetZoomHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetWMHints(IntPtr display, IntPtr window, ref XWMHints wmhints); + + [DllImport(libX11)] + public static extern int XGetIconSizes(IntPtr display, IntPtr window, out IntPtr size_list, out int count); + + [DllImport(libX11)] + public static extern IntPtr XSetErrorHandler(XErrorHandler error_handler); + + [DllImport(libX11)] + public static extern IntPtr XGetErrorText(IntPtr display, byte code, StringBuilder buffer, int length); + + [DllImport(libX11)] + public static extern int XInitThreads(); + + [DllImport(libX11)] + public static extern int XConvertSelection(IntPtr display, IntPtr selection, IntPtr target, IntPtr property, + IntPtr requestor, IntPtr time); + + [DllImport(libX11)] + public static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection); + + [DllImport(libX11)] + public static extern int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, IntPtr time); + + [DllImport(libX11)] + public static extern int XSetPlaneMask(IntPtr display, IntPtr gc, IntPtr mask); + + [DllImport(libX11)] + public static extern int XSetForeground(IntPtr display, IntPtr gc, UIntPtr foreground); + + [DllImport(libX11)] + public static extern int XSetBackground(IntPtr display, IntPtr gc, UIntPtr background); + + [DllImport(libX11)] + public static extern int XBell(IntPtr display, int percent); + + [DllImport(libX11)] + public static extern int XChangeActivePointerGrab(IntPtr display, EventMask event_mask, IntPtr cursor, + IntPtr time); + + [DllImport(libX11)] + public static extern bool XFilterEvent(ref XEvent xevent, IntPtr window); + + [DllImport(libX11)] + public static extern void XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, IntPtr supported); + + [DllImport(libX11)] + public static extern void XPeekEvent(IntPtr display, out XEvent xevent); + + [DllImport(libX11)] + public static extern void XMatchVisualInfo(IntPtr display, int screen, int depth, int klass, out XVisualInfo info); + + [DllImport(libX11)] + public static extern IntPtr XLockDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XUnlockDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); + + [DllImport(libX11)] + public static extern int XInitImage(ref XImage image); + + [DllImport(libX11)] + public static extern int XDestroyImage(ref XImage image); + + [DllImport(libX11)] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + [DllImport(libX11)] + public static extern int XSync(IntPtr display, bool discard); + + [DllImport(libX11)] + public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create); + + public enum XLookupStatus + { + XBufferOverflow = -1, + XLookupNone = 1, + XLookupChars = 2, + XLookupKeySym = 3, + XLookupBoth = 4 + } + + [DllImport (libX11)] + public static extern unsafe int XLookupString(ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); + + [DllImport (libX11)] + public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); + + [DllImport (libX11)] + public static extern unsafe IntPtr XKeycodeToKeysym(IntPtr display, int keycode, int index); + + [DllImport (libX11)] + public static extern unsafe IntPtr XSetLocaleModifiers(string modifiers); + + [DllImport (libX11)] + public static extern IntPtr XOpenIM (IntPtr display, IntPtr rdb, IntPtr res_name, IntPtr res_class); + + [DllImport (libX11)] + public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, IntPtr terminator); + + [DllImport (libX11)] + public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator); + + [DllImport (libX11)] + public static extern void XCloseIM (IntPtr xim); + + [DllImport (libX11)] + public static extern void XDestroyIC (IntPtr xic); + + [DllImport(libX11)] + public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string name, + out int majorOpcode, out int firstEvent, out int firstError); + + [DllImport(libX11)] + public static extern bool XGetEventData(IntPtr display, void* cookie); + + [DllImport(libX11)] + public static extern void XFreeEventData(IntPtr display, void* cookie); + + [DllImport(libX11Randr)] + public static extern int XRRQueryExtension (IntPtr dpy, + out int event_base_return, + out int error_base_return); + + [DllImport(libX11Randr)] + public static extern int XRRQueryVersion(IntPtr dpy, + out int major_version_return, + out int minor_version_return); + + [DllImport(libX11Randr)] + public static extern XRRMonitorInfo* + XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors); + [DllImport(libX11Randr)] + public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask); + + [DllImport(libXInput)] + public static extern Status XIQueryVersion(IntPtr dpy, ref int major, ref int minor); + + [DllImport(libXInput)] + public static extern IntPtr XIQueryDevice(IntPtr dpy, int deviceid, out int ndevices_return); + + [DllImport(libXInput)] + public static extern void XIFreeDeviceInfo(XIDeviceInfo* info); + + public static void XISetMask(ref int mask, XiEventType ev) + { + mask |= (1 << (int)ev); + } + + public static int XiEventMaskLen { get; } = 4; + + public static bool XIMaskIsSet(void* ptr, int shift) => + (((byte*)(ptr))[(shift) >> 3] & (1 << (shift & 7))) != 0; + + [DllImport(libXInput)] + public static extern Status XISelectEvents( + IntPtr dpy, + IntPtr win, + XIEventMask* masks, + int num_masks + ); + + public static Status XiSelectEvents(IntPtr display, IntPtr window, Dictionary> devices) + { + var masks = stackalloc int[devices.Count]; + var emasks = stackalloc XIEventMask[devices.Count]; + int c = 0; + foreach (var d in devices) + { + foreach (var ev in d.Value) + XISetMask(ref masks[c], ev); + emasks[c] = new XIEventMask + { + Mask = &masks[c], + Deviceid = d.Key, + MaskLen = XiEventMaskLen + }; + c++; + } + + + return XISelectEvents(display, window, emasks, devices.Count); + + } + + public struct XGeometry + { + public IntPtr root; + public int x; + public int y; + public int width; + public int height; + public int bw; + public int d; + } + + public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) + { + geo = new XGeometry(); + return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height, + out geo.bw, out geo.d); + } + + public static void QueryPointer (IntPtr display, IntPtr w, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int child_x, out int child_y, + out int mask) + { + + IntPtr c; + + XGrabServer (display); + + XQueryPointer(display, w, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + + if (root != w) + c = root; + + IntPtr child_last = IntPtr.Zero; + while (c != IntPtr.Zero) { + child_last = c; + XQueryPointer(display, c, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + } + XUngrabServer (display); + XFlush (display); + + child = child_last; + } + + public static (int x, int y) GetCursorPos(X11Info x11, IntPtr? handle = null) + { + IntPtr root; + IntPtr child; + int root_x; + int root_y; + int win_x; + int win_y; + int keys_buttons; + + + + QueryPointer(x11.Display, handle ?? x11.RootWindow, out root, out child, out root_x, out root_y, out win_x, out win_y, + out keys_buttons); + + + if (handle != null) + { + return (win_x, win_y); + } + else + { + return (root_x, root_y); + } + } + + public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, Action handler) + { + var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow, + 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); + plat.Windows[win] = handler; + return win; + } + + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs b/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs new file mode 100644 index 0000000000..b80914572b --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; +using Avalonia.Platform.Interop; + +namespace Avalonia.Gtk3 +{ + public class Gtk3ForeignX11SystemDialog : ISystemDialogImpl + { + private Task _initialized; + private SystemDialogBase _inner = new SystemDialogBase(); + + + public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + await EnsureInitialized(); + var xid = parent.Handle.Handle; + return await await RunOnGtkThread( + () => _inner.ShowFileDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); + } + + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + await EnsureInitialized(); + var xid = parent.Handle.Handle; + return await await RunOnGtkThread( + () => _inner.ShowFolderDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); + } + + void UpdateParent(GtkFileChooser chooser, IntPtr xid) + { + Native.GtkWidgetRealize(chooser); + var window = Native.GtkWidgetGetWindow(chooser); + var parent = Native.GdkWindowForeignNewForDisplay(GdkDisplay, xid); + if (window != IntPtr.Zero && parent != IntPtr.Zero) + Native.GdkWindowSetTransientFor(window, parent); + } + + async Task EnsureInitialized() + { + if (_initialized == null) + { + var tcs = new TaskCompletionSource(); + _initialized = tcs.Task; + new Thread(() => GtkThread(tcs)) + { + IsBackground = true + }.Start(); + } + + if (!(await _initialized)) + throw new Exception("Unable to initialize GTK on separate thread"); + + } + + Task RunOnGtkThread(Func action) + { + var tcs = new TaskCompletionSource(); + GlibTimeout.Add(0, 0, () => + { + + try + { + tcs.SetResult(action()); + } + catch (Exception e) + { + tcs.TrySetException(e); + } + + return false; + }); + return tcs.Task; + } + + + void GtkThread(TaskCompletionSource tcs) + { + try + { + X11.XInitThreads(); + }catch{} + Resolver.Resolve(); + if (Native.GdkWindowForeignNewForDisplay == null) + throw new Exception("gdk_x11_window_foreign_new_for_display is not found in your libgdk-3.so"); + using (var backends = new Utf8Buffer("x11")) + Native.GdkSetAllowedBackends?.Invoke(backends); + if (!Native.GtkInitCheck(0, IntPtr.Zero)) + { + tcs.SetResult(false); + return; + } + + using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}")) + App = Native.GtkApplicationNew(utf, 0); + if (App == IntPtr.Zero) + { + tcs.SetResult(false); + return; + } + GdkDisplay = Native.GdkGetDefaultDisplay(); + tcs.SetResult(true); + while (true) + Native.GtkMainIteration(); + } + + private IntPtr GdkDisplay { get; set; } + private IntPtr App { get; set; } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 8444e509fd..965973d3cb 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -50,7 +50,7 @@ namespace Avalonia.Gtk3 { Resolver.Custom = options.CustomResolver; UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); - var useGpu = EnvOption("USE_GPU", false, options.UseGpuAcceleration); + var useGpu = EnvOption("USE_GPU", true, options.UseGpuAcceleration); if (!s_gtkInitialized) { try diff --git a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs index 7c28dd47b9..a2b4604130 100644 --- a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs +++ b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs @@ -6,7 +6,7 @@ namespace Avalonia.Gtk3 { private readonly int _screenId; - public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary) + public GtkScreen(PixelRect bounds, PixelRect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary) { this._screenId = screenId; } @@ -21,4 +21,4 @@ namespace Avalonia.Gtk3 return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj); } } -} \ No newline at end of file +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index bf973119a9..765c19a796 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -50,6 +50,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate IntPtr gtk_init(int argc, IntPtr argv); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate bool gtk_init_check(int argc, IntPtr argv); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)] public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends); @@ -288,6 +291,12 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_window_end_paint(IntPtr window); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)] + public delegate IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_set_transient_for(IntPtr window, IntPtr parent); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_event_request_motions(IntPtr ev); @@ -414,6 +423,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_window_set_transient_for GtkWindowSetTransientFor; public static D.gdk_set_allowed_backends GdkSetAllowedBackends; public static D.gtk_init GtkInit; + public static D.gtk_init_check GtkInitCheck; public static D.gtk_window_present GtkWindowPresent; public static D.gtk_widget_hide GtkWidgetHide; public static D.gtk_widget_show GtkWidgetShow; @@ -483,8 +493,9 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_process_updates GdkWindowProcessUpdates; public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect; public static D.gdk_window_end_paint GdkWindowEndPaint; + public static D.gdk_x11_window_foreign_new_for_display GdkWindowForeignNewForDisplay; + public static D.gdk_window_set_transient_for GdkWindowSetTransientFor; - public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile; public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault; public static D.gtk_icon_theme_load_icon GtkIconThemeLoadIcon; diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs index 97b5029219..9a19ed00e0 100644 --- a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs @@ -27,8 +27,8 @@ namespace Avalonia.Gtk3 GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle(); Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry); Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea); - Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height); - Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height); + PixelRect workAreaRect = new PixelRect(workArea.X, workArea.Y, workArea.Width, workArea.Height); + PixelRect geometryRect = new PixelRect(geometry.X, geometry.Y, geometry.Width, geometry.Height); GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i); screens[i] = s; } diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs index 5e6615f92d..1e85eaa156 100644 --- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs +++ b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs @@ -11,16 +11,17 @@ using Avalonia.Platform.Interop; namespace Avalonia.Gtk3 { - class SystemDialog : ISystemDialogImpl + class SystemDialogBase { - unsafe static Task ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action, - bool multiselect, string initialFileName) + public unsafe static Task ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action, + bool multiselect, string initialFileName, Action modify) { GtkFileChooser dlg; parent = parent ?? GtkWindow.Null; using (var name = new Utf8Buffer(title)) dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero); + modify?.Invoke(dlg); if (multiselect) Native.GtkFileChooserSetSelectMultiple(dlg, true); @@ -42,7 +43,7 @@ namespace Avalonia.Gtk3 dispose(); return false; }), - Signal.Connect(dlg, "response", (_, resp, __)=> + Signal.Connect(dlg, "response", (_, resp, __) => { string[] result = null; if (resp == GtkResponseType.Accept) @@ -56,9 +57,11 @@ namespace Avalonia.Gtk3 rlst.Add(Utf8Buffer.StringFromPtr(cgs->Data)); cgs = cgs->Next; } + Native.GSlistFree(gs); result = rlst.ToArray(); } + Native.GtkWidgetHide(dlg); dispose(); tcs.TrySetResult(result); @@ -70,27 +73,38 @@ namespace Avalonia.Gtk3 Native.GtkDialogAddButton(dlg, open, GtkResponseType.Accept); using (var open = new Utf8Buffer("Cancel")) Native.GtkDialogAddButton(dlg, open, GtkResponseType.Cancel); - if(initialFileName!=null) + if (initialFileName != null) using (var fn = new Utf8Buffer(initialFileName)) Native.GtkFileChooserSetFilename(dlg, fn); Native.GtkWindowPresent(dlg); return tcs.Task; } - public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + public Task ShowFileDialogAsync(FileDialog dialog, GtkWindow parent, + Action modify = null) { - return ShowDialog(dialog.Title, ((WindowBaseImpl)parent)?.GtkWidget, + return ShowDialog(dialog.Title, parent, dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, (dialog as OpenFileDialog)?.AllowMultiple ?? false, Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, - string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName)); + string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), modify); } - public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, GtkWindow parent, + Action modify = null) { - var res = await ShowDialog(dialog.Title, ((WindowBaseImpl) parent)?.GtkWidget, - GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); + var res = await ShowDialog(dialog.Title, parent, + GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, modify); return res?.FirstOrDefault(); } } + + class SystemDialog : SystemDialogBase, ISystemDialogImpl + { + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + => ShowFolderDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget); + + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + => ShowFileDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget); + } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 6d4544bfbd..c4697462d2 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Gtk3 private readonly EglGlPlatformSurface _egl; protected readonly List Disposables = new List(); private Size _lastSize; - private Point _lastPosition; + private PixelPoint _lastPosition; private double _lastScaling; private uint _lastKbdEvent; private uint _lastSmoothScrollEvent; @@ -383,7 +383,7 @@ namespace Avalonia.Gtk3 public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } //TODO - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public void Activate() => Native.GtkWidgetActivate(GtkWidget); @@ -402,7 +402,7 @@ namespace Avalonia.Gtk3 Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { int x, y; Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); @@ -410,11 +410,11 @@ namespace Avalonia.Gtk3 return new Point(point.X - x, point.Y - y); } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { int x, y; Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); - return new Point(point.X + x, point.Y + y); + return new PixelPoint((int)(point.X + x), (int)(point.Y + y)); } public void SetCursor(IPlatformHandle cursor) @@ -490,13 +490,13 @@ namespace Avalonia.Gtk3 get; } = new ScreenImpl(); - public Point Position + public PixelPoint Position { get { int x, y; Native.GtkWindowGetPosition(GtkWidget, out x, out y); - return new Point(x, y); + return new PixelPoint(x, y); } set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 968873abc0..6a63014e1a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -51,9 +51,9 @@ namespace Avalonia.LinuxFramebuffer InputRoot = inputRoot; } - public Point PointToClient(Point point) => point; + public Point PointToClient(PixelPoint p) => p.ToPoint(1); - public Point PointToScreen(Point point) => point; + public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); public void SetCursor(IPlatformHandle cursor) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index ec73859a0a..38d207a31d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -4,13 +4,14 @@ PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER False false - false CS1591 + + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index 93db025343..c30822aacb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -11,6 +11,8 @@ using Avalonia.Controls.Templates; namespace Avalonia.Markup.Xaml { + using Avalonia.Media; + /// /// Maintains a repository of s for XAML parsing on top of those /// maintained by . @@ -37,10 +39,17 @@ namespace Avalonia.Markup.Xaml { typeof(Selector), typeof(SelectorTypeConverter) }, { typeof(TimeSpan), typeof(TimeSpanTypeConverter) }, { typeof(WindowIcon), typeof(IconTypeConverter) }, - { typeof(CultureInfo), typeof(CultureInfoConverter)}, - { typeof(Uri), typeof(AvaloniaUriTypeConverter)} + { typeof(CultureInfo), typeof(CultureInfoConverter) }, + { typeof(Uri), typeof(AvaloniaUriTypeConverter) }, + { typeof(FontFamily), typeof(FontFamilyTypeConverter) } }; + internal static Type GetBuiltinTypeConverter(Type type) + { + _converters.TryGetValue(type, out var result); + return result; + } + /// /// Tries to lookup a for a type. /// @@ -48,6 +57,14 @@ namespace Avalonia.Markup.Xaml /// The type converter. public static Type GetTypeConverter(Type type) { + if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var inner = GetTypeConverter(type.GetGenericArguments()[0]); + if (inner == null) + return null; + return typeof(NullableTypeConverter<>).MakeGenericType(inner); + } + if (_converters.TryGetValue(type, out var result)) { return result; @@ -84,4 +101,4 @@ namespace Avalonia.Markup.Xaml /// The converter type. Maybe be a non-constructed generic type. public static void Register(Type type, Type converterType) => _converters[type] = converterType; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs new file mode 100644 index 0000000000..9b0e7a2b6b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs @@ -0,0 +1,28 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.ComponentModel; +using System.Globalization; + +using Avalonia.Media; + +using Portable.Xaml.ComponentModel; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class FontFamilyTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + var s = (string)value; + + return FontFamily.Parse(s, context.GetBaseUri()); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs new file mode 100644 index 0000000000..5e7a31da56 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Globalization; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class NullableTypeConverter : TypeConverter where T : TypeConverter, new() + { + private TypeConverter _inner; + + public NullableTypeConverter() + { + _inner = new T(); + } + + public NullableTypeConverter(TypeConverter inner) + { + _inner = inner; + } + + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value == null) + return null; + return _inner.ConvertTo(context, culture, value, destinationType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) + return null; + if (value as string == "") + return null; + return _inner.ConvertFrom(context, culture, value); + } + + public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) + { + return _inner.CreateInstance(context, propertyValues); + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return _inner.GetStandardValuesSupported(context); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return _inner.GetStandardValuesExclusive(context); + } + + public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) + { + return _inner.GetCreateInstanceSupported(context); + } + + public override bool GetPropertiesSupported(ITypeDescriptorContext context) + { + return _inner.GetPropertiesSupported(context); + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + return _inner.GetStandardValues(context); + } + + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) + { + return _inner.GetProperties(context, value, attributes); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return _inner.CanConvertTo(context, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return _inner.CanConvertFrom(context, sourceType); + } + + public override bool IsValid(ITypeDescriptorContext context, object value) + { + return _inner.IsValid(context, value); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs index 83f4919d4b..7558a5df0b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs @@ -42,7 +42,10 @@ namespace Avalonia.Markup.Xaml.PortableXaml } else if (attributeType == typeof(TypeConverterAttribute)) { - result = ti.GetCustomAttribute(attributeType, inherit); + var builtin = AvaloniaTypeConverters.GetBuiltinTypeConverter(_type); + if (builtin != null) + result = new TypeConverterAttribute(builtin); + result = result ?? ti.GetCustomAttribute(attributeType, inherit); if (result == null) { @@ -111,4 +114,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml return null; } } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fc2656f236..9273011cb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling IResourceNode IResourceNode.ResourceParent => _parent; /// - public void Attach(IStyleable control, IStyleHost container) + public bool Attach(IStyleable control, IStyleHost container) { if (Source != null) { - Loaded.Attach(control, container); + return Loaded.Attach(control, container); } + + return false; } public void Detach() diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index a5cb5a8c06..673eb51901 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -2,7 +2,6 @@ netstandard2.0 Avalonia - false diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 076d69ae4a..353354da5e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; @@ -20,7 +21,7 @@ namespace Avalonia.Skia /// public class DrawingContextImpl : IDrawingContextImpl { - private readonly IDisposable[] _disposables; + private IDisposable[] _disposables; private readonly Vector _dpi; private readonly Stack _maskStack = new Stack(); private readonly Stack _opacityStack = new Stack(); @@ -30,7 +31,7 @@ namespace Avalonia.Skia private readonly bool _canTextUseLcdRendering; private Matrix _currentTransform; private GRContext _grContext; - + private bool _disposed; /// /// Context create info. /// @@ -74,6 +75,8 @@ namespace Avalonia.Skia _disposables = disposables; _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; + if (_grContext != null) + Monitor.Enter(_grContext); Canvas = createInfo.Canvas; @@ -257,14 +260,26 @@ namespace Avalonia.Skia /// public virtual void Dispose() { - if (_disposables == null) - { + if(_disposed) return; - } + try + { + if (_grContext != null) + { + Monitor.Exit(_grContext); + _grContext = null; + } - foreach (var disposable in _disposables) + if (_disposables != null) + { + foreach (var disposable in _disposables) + disposable?.Dispose(); + _disposables = null; + } + } + finally { - disposable?.Dispose(); + _disposed = true; } } @@ -386,15 +401,15 @@ namespace Avalonia.Skia /// Tile brush image. private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) { - var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSize(_dpi), targetSize); + var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetSize); var intermediate = CreateRenderTarget(calc.IntermediateSize); paintWrapper.AddDisposable(intermediate); using (var context = intermediate.CreateDrawingContext(null)) { - var sourceRect = new Rect(tileBrushImage.PixelSize.ToSize(96)); - var targetRect = new Rect(tileBrushImage.PixelSize.ToSize(_dpi)); + var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96)); + var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); @@ -619,7 +634,7 @@ namespace Avalonia.Skia /// private SurfaceRenderTarget CreateRenderTarget(Size size, PixelFormat? format = null) { - var pixelSize = PixelSize.FromSize(size, _dpi); + var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi); var createInfo = new SurfaceRenderTarget.CreateInfo { Width = pixelSize.Width, diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 56c5c57f7b..7e3f4aa40a 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -272,7 +272,7 @@ namespace Avalonia.Skia } var textLine = Text.Substring(line.Start, line.Length); - currX -= paint.MeasureText(textLine) * factor; + currX -= textLine.Length == 0 ? 0 : paint.MeasureText(textLine) * factor; for (int i = line.Start; i < line.Start + line.Length;) { diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 5efbc0861e..0cb4c3db67 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -42,7 +42,8 @@ namespace Avalonia.Skia { var framebuffer = _platformSurface.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, - framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); + framebuffer.Format.ToSkColorType(), + framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); @@ -60,7 +61,7 @@ namespace Avalonia.Skia DisableTextLcdRendering = true }; - return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, framebuffer); + return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); } /// @@ -118,11 +119,7 @@ namespace Avalonia.Skia _conversionShim = null; _preFramebufferCopyHandler = null; - if (_conversionShim != null) - { - _framebufferSurface?.Dispose(); - } - + _framebufferSurface?.Dispose(); _framebufferSurface = null; _currentFramebufferAddress = IntPtr.Zero; } diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index a6269473a6..cd8c334b53 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -30,40 +30,46 @@ namespace Avalonia.Skia var size = session.Size; var scaling = session.Scaling; - - GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc + if (size.Width <= 0 || size.Height <= 0 || scaling < 0) { - Width = size.Width, - Height = size.Height, - SampleCount = disp.SampleCount, - StencilBits = disp.StencilSize, - Config = GRPixelConfig.Rgba8888, - Origin=GRSurfaceOrigin.BottomLeft, - RenderTargetHandle = new IntPtr(fb) - }; + throw new InvalidOperationException( + $"Can't create drawing context for surface with {size} size and {scaling} scaling"); + } - gl.Viewport(0, 0, desc.Width, desc.Height); + gl.Viewport(0, 0, size.Width, size.Height); gl.ClearStencil(0); gl.ClearColor(0, 0, 0, 0); gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + lock (_grContext) + { + _grContext.ResetContext(); - var surface = SKSurface.Create(_grContext, desc); + GRBackendRenderTarget renderTarget = + new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize, + new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat())); + var surface = SKSurface.Create(_grContext, renderTarget, + GRSurfaceOrigin.BottomLeft, + GRPixelConfig.Rgba8888.ToColorType()); - var nfo = new DrawingContextImpl.CreateInfo - { - GrContext = _grContext, - Canvas = surface.Canvas, - Dpi = SkiaPlatform.DefaultDpi * scaling, - VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = true - }; + var nfo = new DrawingContextImpl.CreateInfo + { + GrContext = _grContext, + Canvas = surface.Canvas, + Dpi = SkiaPlatform.DefaultDpi * scaling, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true + }; - return new DrawingContextImpl(nfo, Disposable.Create(() => - { - surface.Canvas.Flush(); - surface.Dispose(); - session.Dispose(); - })); + return new DrawingContextImpl(nfo, Disposable.Create(() => + { + + surface.Canvas.Flush(); + surface.Dispose(); + renderTarget.Dispose(); + _grContext.Flush(); + session.Dispose(); + })); + } } } } diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index f94a226bbc..49992df395 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -24,7 +24,8 @@ namespace Avalonia.Skia { using (var skiaStream = new SKManagedStream(stream)) { - _image = SKImage.FromEncodedData(SKData.Create(skiaStream)); + using (var data = SKData.Create(skiaStream)) + _image = SKImage.FromEncodedData(data); if (_image == null) { diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 9b10d74c64..255e84355a 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -19,17 +19,21 @@ namespace Avalonia.Skia { private GRContext GrContext { get; } + public IEnumerable InstalledFontNames => SKFontManager.Default.FontFamilies; + public PlatformRenderInterface() { var gl = AvaloniaLocator.Current.GetService(); if (gl != null) { var display = gl.ImmediateContext.Display; - var iface = display.Type == GlDisplayType.OpenGL2 + using (var iface = display.Type == GlDisplayType.OpenGL2 ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc)) - : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)); - gl.ImmediateContext.MakeCurrent(); - GrContext = GRContext.Create(GRBackend.OpenGL, iface); + : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc))) + { + gl.ImmediateContext.MakeCurrent(); + GrContext = GRContext.Create(GRBackend.OpenGL, iface); + } } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 687fe7feca..17c51dbb6e 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs @@ -1,72 +1,115 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + using Avalonia.Media; + using SkiaSharp; namespace Avalonia.Skia { internal class SKTypefaceCollection { - private readonly ConcurrentDictionary _cachedTypefaces = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _fontFamilies = + new ConcurrentDictionary>(); public void AddTypeFace(SKTypeface typeface) { - var key = new FontKey(typeface.FamilyName, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); + var key = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant); - _cachedTypefaces.TryAdd(key, typeface); + if (!_fontFamilies.TryGetValue(typeface.FamilyName, out var fontFamily)) + { + fontFamily = new ConcurrentDictionary(); + + _fontFamilies.TryAdd(typeface.FamilyName, fontFamily); + } + + fontFamily.TryAdd(key, typeface); } public SKTypeface GetTypeFace(Typeface typeface) { - SKFontStyleSlant skStyle = SKFontStyleSlant.Upright; + var styleSlant = SKFontStyleSlant.Upright; switch (typeface.Style) { case FontStyle.Italic: - skStyle = SKFontStyleSlant.Italic; + styleSlant = SKFontStyleSlant.Italic; break; case FontStyle.Oblique: - skStyle = SKFontStyleSlant.Oblique; + styleSlant = SKFontStyleSlant.Oblique; break; } - var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle); + if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily)) + { + return TypefaceCache.Default; + } + + var weight = (SKFontStyleWeight)typeface.Weight; + + var key = new FontKey(weight, styleSlant); + + return fontFamily.GetOrAdd(key, GetFallback(fontFamily, key)); + } + + private static SKTypeface GetFallback(IDictionary fontFamily, FontKey key) + { + var keys = fontFamily.Keys.Where( + x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Slant == key.Slant).ToArray(); + + if (!keys.Any()) + { + keys = fontFamily.Keys.Where( + x => x.Weight == key.Weight && (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray(); + + if (!keys.Any()) + { + keys = fontFamily.Keys.Where( + x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && + (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray(); + } + } + + key = keys.FirstOrDefault(); + + fontFamily.TryGetValue(key, out var typeface); - return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : TypefaceCache.Default; + return typeface; } private struct FontKey { - public readonly string Name; public readonly SKFontStyleSlant Slant; public readonly SKFontStyleWeight Weight; - public FontKey(string name, SKFontStyleWeight weight, SKFontStyleSlant slant) + public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant) { - Name = name; Slant = slant; Weight = weight; } public override int GetHashCode() { - int hash = 17; - hash = hash * 31 + Name.GetHashCode(); - hash = hash * 31 + (int)Slant; - hash = hash * 31 + (int)Weight; + var hash = 17; + hash = (hash * 31) + (int)Slant; + hash = (hash * 31) + (int)Weight; return hash; } public override bool Equals(object other) { - return other is FontKey ? Equals((FontKey)other) : false; + return other is FontKey key && this.Equals(key); } private bool Equals(FontKey other) { - return Name == other.Name && Slant == other.Slant && + return Slant == other.Slant && Weight == other.Weight; } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index b9a8c9b98b..ab8ee85a54 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System.Collections.Concurrent; using Avalonia.Media; using Avalonia.Media.Fonts; diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 6955cc666f..4b7eae1af4 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -45,6 +45,7 @@ namespace Avalonia.Skia /// /// Create backing Skia surface. /// + /// GPU. /// Width. /// Height. /// Format. @@ -137,7 +138,7 @@ namespace Avalonia.Skia { var colorType = PixelFormatHelper.ResolveColorType(format); - return new SKImageInfo(width, height, colorType, SKAlphaType.Premul); + return new SKImageInfo(Math.Max(width, 1), Math.Max(height, 1), colorType, SKAlphaType.Premul); } /// diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 36ccd7930a..c9d6fa6c11 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul); var blob = runtimePlatform.AllocBlob(nfo.BytesSize); - _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); + _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, s_releaseDelegate, blob); } else { @@ -123,6 +123,7 @@ namespace Avalonia.Skia /// /// Create framebuffer from given bitmap. /// + /// Parent bitmap impl. /// Bitmap. public BitmapFramebuffer(WriteableBitmapImpl parent, SKBitmap bitmap) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 3ec18dac5e..8412a65e23 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -41,6 +41,19 @@ namespace Avalonia.Direct2D1 public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } + public IEnumerable InstalledFontNames + { + get + { + var cache = Direct2D1FontCollectionCache.s_installedFontCollection; + var length = cache.FontFamilyCount; + for (int i = 0; i < length; i++) + { + var names = cache.GetFontFamily(i).FamilyNames; + yield return names.GetString(0); + } + } + } private static readonly object s_initLock = new object(); private static bool s_initialized = false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index d60aa15a5e..d93a59d384 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,7 +7,7 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; + internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index aaca1a3b00..5e150ff647 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -330,7 +330,7 @@ namespace Avalonia.Direct2D1.Media { var platform = AvaloniaLocator.Current.GetService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); - var pixelSize = PixelSize.FromSize(size, dpi); + var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); return platform.CreateRenderTargetBitmap(pixelSize, dpi); } } @@ -438,12 +438,12 @@ namespace Avalonia.Direct2D1.Media // D2D alters the DPI of the render target, which messes stuff up. PixelSize.FromSize // will do the rounding for us. var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); - var pixelSize = PixelSize.FromSize(intermediateSize, dpi); + var pixelSize = PixelSize.FromSizeWithDpi(intermediateSize, dpi); using (var intermediate = new BitmapRenderTarget( _deviceContext, CompatibleRenderTargetOptions.None, - pixelSize.ToSize(dpi).ToSharpDX())) + pixelSize.ToSizeWithDpi(dpi).ToSharpDX())) { using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 19d320b783..fbc6d21cb7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -21,7 +21,7 @@ namespace Avalonia.Direct2D1.Media Size targetSize) { var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); - var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(dpi), targetSize); + var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSizeWithDpi(dpi), targetSize); if (!calc.NeedsIntermediate) { @@ -101,7 +101,7 @@ namespace Avalonia.Direct2D1.Media using (var context = new RenderTarget(result).CreateDrawingContext(null)) { var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); - var rect = new Rect(bitmap.PixelSize.ToSize(dpi)); + var rect = new Rect(bitmap.PixelSize.ToSizeWithDpi(dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index fbe7f840d7..8ec368c999 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -61,8 +61,8 @@ namespace Avalonia.Direct2D1.Media.Imaging dc.DrawImage( RefCountable.CreateUnownedNotClonable(this), 1, - new Rect(PixelSize.ToSize(Dpi.X)), - new Rect(PixelSize.ToSize(Dpi.X))); + new Rect(PixelSize.ToSizeWithDpi(Dpi.X)), + new Rect(PixelSize.ToSizeWithDpi(Dpi.X))); } wic.Save(stream); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs index 44c4c84cd6..cbfc259eda 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs @@ -3,7 +3,9 @@ static class WpfInteropExtensions { public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y); + public static System.Windows.Point ToWpfPoint(this PixelPoint pt) => new System.Windows.Point(pt.X, pt.Y); public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y); + public static PixelPoint ToAvaloniaPixelPoint(this System.Windows.Point pt) => new PixelPoint((int)pt.X, (int)pt.Y); public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height); public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height); } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index b5fb158168..7005459487 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -135,9 +135,9 @@ namespace Avalonia.Win32.Interop.Wpf void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; - Point ITopLevelImpl.PointToClient(Point point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint(); + Point ITopLevelImpl.PointToClient(PixelPoint point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint(); - Point ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPoint(); + PixelPoint ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPixelPoint(); protected override void OnLostFocus(RoutedEventArgs e) => LostFocus?.Invoke(); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9e8ff20e44..7d6e8fc8ce 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -909,8 +909,17 @@ namespace Avalonia.Win32.Interop public enum ClassLongIndex : int { - GCL_HCURSOR = -12, - GCL_HICON = -14 + GCLP_MENUNAME = -8, + GCLP_HBRBACKGROUND = -10, + GCLP_HCURSOR = -12, + GCLP_HICON = -14, + GCLP_HMODULE = -16, + GCL_CBWNDEXTRA = -18, + GCL_CBCLSEXTRA = -20, + GCLP_WNDPROC = -24, + GCL_STYLE = -26, + GCLP_HICONSM = -34, + GCW_ATOM = -32 } [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")] @@ -929,6 +938,20 @@ namespace Avalonia.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) + { + if (IntPtr.Size > 4) + return GetClassLongPtr64(hWnd, nIndex); + else + return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); + } + + [DllImport("user32.dll", EntryPoint = "GetClassLong")] + public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", EntryPoint = "SetCursor")] internal static extern IntPtr SetCursor(IntPtr hCursor); @@ -1172,6 +1195,8 @@ namespace Avalonia.Win32.Interop public int right; public int bottom; + public int Width => right - left; + public int Height => bottom - top; public RECT(Rect rect) { left = (int)rect.X; @@ -1179,6 +1204,34 @@ namespace Avalonia.Win32.Interop right = (int)(rect.X + rect.Width); bottom = (int)(rect.Y + rect.Height); } + + public void Offset(POINT pt) + { + left += pt.X; + right += pt.X; + top += pt.Y; + bottom += pt.Y; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public uint flags; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; } public struct TRACKMOUSEEVENT diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 7013931be9..28065566d0 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -177,7 +177,7 @@ namespace Avalonia.Win32 int x = (int)dragPoint; int y = (int)(dragPoint >> 32); - Point screenPt = new Point(x, y); + var screenPt = new PixelPoint(x, y); return _target.PointToClient(screenPt); } } diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 7849bd6a9d..39f1a95466 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW | UnmanagedMethods.WindowStyles.WS_EX_TOPMOST; - return UnmanagedMethods.CreateWindowEx( + var result = UnmanagedMethods.CreateWindowEx( (int)exStyle, atom, null, @@ -37,6 +37,14 @@ namespace Avalonia.Win32 IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE); + + classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; + + UnmanagedMethods.SetClassLong(result, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes)); + + return result; } protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 5cfceb68b7..aa3b23eb50 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -31,10 +31,10 @@ namespace Avalonia.Win32 { RECT bounds = monitorInfo.rcMonitor; RECT workingArea = monitorInfo.rcWork; - Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left, + PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); - Rect avaloniaWorkArea = - new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, + PixelRect avaloniaWorkArea = + new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, workingArea.bottom - workingArea.top); screens[index] = new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index 2f8dcb0f3e..e849800e62 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -7,7 +7,7 @@ namespace Avalonia.Win32 { private readonly IntPtr _hMonitor; - public WinScreen(Rect bounds, Rect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary) + public WinScreen(PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary) { this._hMonitor = hMonitor; } @@ -22,4 +22,4 @@ namespace Avalonia.Win32 return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 18f0696cd8..081a713e95 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -35,6 +35,7 @@ namespace Avalonia.Win32 private bool _decorated = true; private bool _resizable = true; private bool _topmost = false; + private bool _taskbarIcon = true; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; @@ -86,7 +87,7 @@ namespace Avalonia.Win32 public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public Action WindowStateChanged { get; set; } @@ -94,17 +95,25 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - var padding = new UnmanagedMethods.RECT(); - - if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) + if (_decorated) { - return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + + var padding = new RECT(); + + if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) + { + return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + } + else + { + throw new Win32Exception(); + } } else { - throw new Win32Exception(); + return new Thickness(); } } } @@ -144,18 +153,24 @@ namespace Avalonia.Win32 public void Resize(Size value) { - if (value != ClientSize) + int requestedClientWidth = (int)(value.Width * Scaling); + int requestedClientHeight = (int)(value.Height * Scaling); + UnmanagedMethods.RECT clientRect; + UnmanagedMethods.GetClientRect(_hwnd, out clientRect); + + // do comparison after scaling to avoid rounding issues + if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height) { - value *= Scaling; - value += BorderThickness; + UnmanagedMethods.RECT windowRect; + UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); UnmanagedMethods.SetWindowPos( _hwnd, IntPtr.Zero, 0, 0, - (int)value.Width, - (int)value.Height, + requestedClientWidth + (windowRect.Width - clientRect.Width), + requestedClientHeight + (windowRect.Height - clientRect.Height), UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE); } } @@ -265,63 +280,7 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - - if (!value) - { - style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - } - - UnmanagedMethods.RECT windowRect; - - UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); - - Rect newRect; - var oldThickness = BorderThickness; - - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); - - if (value) - { - var thickness = BorderThickness; - - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } - - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - - _decorated = value; - - if(_decorated) - { - if (_resizable) - { - // If we switch decorations back on we need to restore WS_SizeFrame. - _resizable = false; - CanResize(true); - } - else - { - _resizable = true; - CanResize(false); - } - } + UpdateWMStyles(()=> _decorated = value); } public void Invalidate(Rect rect) @@ -329,28 +288,28 @@ namespace Avalonia.Win32 var f = Scaling; var r = new UnmanagedMethods.RECT { - left = (int)(rect.X * f), - top = (int)(rect.Y * f), - right = (int)(rect.Right * f), - bottom = (int)(rect.Bottom * f), + left = (int)Math.Floor(rect.X * f), + top = (int)Math.Floor(rect.Y * f), + right = (int)Math.Ceiling(rect.Right * f), + bottom = (int)Math.Ceiling(rect.Bottom * f), }; UnmanagedMethods.InvalidateRect(_hwnd, ref r, false); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); return new Point(p.X, p.Y) / Scaling; } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { point *= Scaling; var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ClientToScreen(_hwnd, ref p); - return new Point(p.X, p.Y); + return new PixelPoint(p.X, p.Y); } public void SetInputRoot(IInputRoot inputRoot) @@ -398,21 +357,21 @@ namespace Avalonia.Win32 #endif } - public Point Position + public PixelPoint Position { get { UnmanagedMethods.RECT rc; UnmanagedMethods.GetWindowRect(_hwnd, out rc); - return new Point(rc.left, rc.top); + return new PixelPoint(rc.left, rc.top); } set { UnmanagedMethods.SetWindowPos( Handle.Handle, IntPtr.Zero, - (int)value.X, - (int)value.Y, + value.X, + value.Y, 0, 0, UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); @@ -432,7 +391,7 @@ namespace Avalonia.Win32 public void SetCursor(IPlatformHandle cursor) { var hCursor = cursor?.Handle ?? DefaultCursor; - UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor); + UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCLP_HCURSOR, hCursor); if (_owner.IsPointerOver) UnmanagedMethods.SetCursor(hCursor); @@ -486,6 +445,13 @@ namespace Avalonia.Win32 return IntPtr.Zero; + case WindowsMessage.WM_NCCALCSIZE: + if (ToInt32(wParam) == 1 && !_decorated) + { + return IntPtr.Zero; + } + break; + case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); if (preventClosing == true) @@ -646,6 +612,20 @@ namespace Avalonia.Win32 new Point(0, 0), GetMouseModifiers(wParam)); break; + case WindowsMessage.WM_NCPAINT: + if (!_decorated) + { + return IntPtr.Zero; + } + break; + + case WindowsMessage.WM_NCACTIVATE: + if (!_decorated) + { + return new IntPtr(1); + } + break; + case UnmanagedMethods.WindowsMessage.WM_PAINT: UnmanagedMethods.PAINTSTRUCT ps; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) @@ -681,14 +661,14 @@ namespace Avalonia.Win32 return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_MOVE: - PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); + PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO: MINMAXINFO mmi = Marshal.PtrToStructure(lParam); - if (_minSize.Width > 0) + if (_minSize.Width > 0) mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); if (_minSize.Height > 0) @@ -805,9 +785,9 @@ namespace Avalonia.Win32 return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; } - private Point PointFromLParam(IntPtr lParam) + private PixelPoint PointFromLParam(IntPtr lParam) { - return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); + return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); } private Point ScreenToClient(Point point) @@ -859,7 +839,7 @@ namespace Avalonia.Win32 { MONITORINFO monitorInfo = MONITORINFO.Create(); - if (GetMonitorInfo(monitor,ref monitorInfo)) + if (GetMonitorInfo(monitor, ref monitorInfo)) { RECT rcMonitorArea = monitorInfo.rcMonitor; @@ -888,13 +868,22 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } + public void ShowTaskbarIcon(bool value) { + if (_taskbarIcon == value) + { + return; + } + + _taskbarIcon = value; + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; + if (value) style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; else @@ -910,26 +899,68 @@ namespace Avalonia.Win32 } } - public void CanResize(bool value) + private void UpdateWMStyles(Action change) { - if (value == _resizable) + var oldDecorated = _decorated; + + var oldThickness = BorderThickness; + + change(); + + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + + const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; + + style = style | controlledFlags ^ controlledFlags; + + style |= WindowStyles.WS_OVERLAPPEDWINDOW; + + if (!_decorated) { - return; + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } - if (_decorated) + if (!_resizable) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + style ^= (WindowStyles.WS_SIZEFRAME); + } - if (value) - style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; - else - style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + GetClientRect(_hwnd, out var oldClientRect); + var oldClientRectOrigin = new UnmanagedMethods.POINT(); + ClientToScreen(_hwnd, ref oldClientRectOrigin); + oldClientRect.Offset(oldClientRectOrigin); + + + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); + + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + bool frameUpdated = false; + if (oldDecorated != _decorated) + { + var newRect = oldClientRect; + if (_decorated) + AdjustWindowRectEx(ref newRect, (uint)style, false, + GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); + SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + frameUpdated = true; + } - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + if (!frameUpdated) + SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOZORDER | + SetWindowPosFlags.SWP_NOACTIVATE + | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE); + } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; } - _resizable = value; + UpdateWMStyles(()=> _resizable = value); } public void SetTopmost(bool value) diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 1101f53222..83d10b8b44 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -68,9 +68,9 @@ namespace Avalonia.iOS public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; - public Point PointToClient(Point point) => point; + public Point PointToClient(PixelPoint point) => point.ToPoint(1); - public Point PointToScreen(Point point) => point; + public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1); public void SetCursor(IPlatformHandle cursor) { diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs new file mode 100644 index 0000000000..f7a8774689 --- /dev/null +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Avalonia.Data; +using Xunit; +using Avalonia.Animation.Easings; + +namespace Avalonia.Animation.UnitTests +{ + public class AnimationIterationTests + { + [Fact] + public void Check_Initial_Inter_and_Trailing_Delay_Values() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 200d), + }, + Cue = new Cue(1d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 100d), + }, + Cue = new Cue(0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(3), + Delay = TimeSpan.FromSeconds(3), + DelayBetweenIterations = TimeSpan.FromSeconds(3), + IterationCount = new IterationCount(2), + Children = + { + keyframe2, + keyframe1 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(border, clock); + + clock.Step(TimeSpan.Zero); + + // Initial Delay. + clock.Step(TimeSpan.FromSeconds(1)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(6)); + + // First Inter-Iteration delay. + clock.Step(TimeSpan.FromSeconds(8)); + Assert.Equal(border.Width, 200d); + + // Trailing Delay should be non-existent. + clock.Step(TimeSpan.FromSeconds(14)); + Assert.True(animationRun.Status == TaskStatus.RanToCompletion); + Assert.Equal(border.Width, 100d); + } + + [Fact] + public void Check_FillModes_Start_and_End_Values_if_Retained() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 0d), + }, + Cue = new Cue(0.0d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 300d), + }, + Cue = new Cue(1.0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(0.05d), + Delay = TimeSpan.FromSeconds(0.05d), + Easing = new SineEaseInOut(), + FillMode = FillMode.Both, + Children = + { + keyframe1, + keyframe2 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d, + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(border, clock); + + clock.Step(TimeSpan.FromSeconds(0d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.050d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.100d)); + Assert.Equal(border.Width, 300d); + } + } +} diff --git a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj new file mode 100644 index 0000000000..ef515ce155 --- /dev/null +++ b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj @@ -0,0 +1,27 @@ + + + netcoreapp2.0;net47 + Library + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b00c205f10 --- /dev/null +++ b/tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Reflection; +using Xunit; + +[assembly: AssemblyTitle("Avalonia.Animation.UnitTests")] + +// Don't run tests in parallel. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/tests/Avalonia.Animation.UnitTests/TestClock.cs b/tests/Avalonia.Animation.UnitTests/TestClock.cs new file mode 100644 index 0000000000..a1c4ff9277 --- /dev/null +++ b/tests/Avalonia.Animation.UnitTests/TestClock.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Animation.UnitTests +{ + internal class TestClock : IClock, IDisposable + { + private IObserver _observer; + + public PlayState PlayState { get; set; } = PlayState.Run; + + public void Dispose() + { + _observer?.OnCompleted(); + } + + public void Step(TimeSpan time) + { + _observer?.OnNext(time); + } + + public IDisposable Subscribe(IObserver observer) + { + _observer = observer; + return this; + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 373b3f7196..6e885f33be 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index c90683959e..b56afa33a4 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -558,11 +558,29 @@ namespace Avalonia.Base.UnitTests.Data.Core var result = run(); result.Item1.Subscribe(x => { }); - GC.Collect(); + // Mono trickery + GC.Collect(2); + GC.WaitForPendingFinalizers(); + GC.WaitForPendingFinalizers(); + GC.Collect(2); + Assert.Null(result.Item2.Target); } + [Fact] + public void Should_Not_Throw_Exception_On_Unsubscribe_When_Already_Unsubscribed() + { + var source = new Class1 { Foo = "foo" }; + var target = new PropertyAccessorNode("Foo", false); + Assert.NotNull(target); + target.Target = new WeakReference(source); + target.Subscribe(_ => { }); + target.Unsubscribe(); + target.Unsubscribe(); + Assert.True(true); + } + private interface INext { int PropertyChangedSubscriptionCount { get; } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs index 2bffb7b84a..378c225e23 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using Avalonia.Data; using Avalonia.Data.Core.Plugins; using Avalonia.UnitTests; @@ -86,17 +87,18 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins validator.SetValue("123456", BindingPriority.LocalValue); validator.SetValue("abcdefghijklm", BindingPriority.LocalValue); - Assert.Equal(new[] - { - new BindingNotification(null), - new BindingNotification("123456"), - new BindingNotification( - new AggregateException( - new ValidationException("The PhoneNumber field is not a valid phone number."), - new ValidationException("The field PhoneNumber must be a string or array type with a maximum length of '10'.")), - BindingErrorType.DataValidationError, - "abcdefghijklm"), - }, result); + Assert.Equal(3, result.Count); + Assert.Equal(new BindingNotification(null), result[0]); + Assert.Equal(new BindingNotification("123456"), result[1]); + var errorResult = (BindingNotification)result[2]; + Assert.Equal(BindingErrorType.DataValidationError, errorResult.ErrorType); + Assert.Equal("abcdefghijklm", errorResult.Value); + var exceptions = ((AggregateException)(errorResult.Error)).InnerExceptions; + Assert.True(exceptions.Any(ex => + ex.Message.Contains("The PhoneNumber field is not a valid phone number."))); + Assert.True(exceptions.Any(ex => + ex.Message.Contains("The field PhoneNumber must be a string or array type with a maximum length of '10'."))); + } private class Data diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index b418d56fcd..aa470bce9d 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -1,10 +1,12 @@  - netcoreapp2.0 + netcoreapp2.0;net47 latest Library + true + @@ -26,4 +28,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 76f2898700..91b37596b7 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -238,9 +238,9 @@ namespace Avalonia.Controls.UnitTests public void Invalidate(Rect rect) => throw new NotImplementedException(); - public Point PointToClient(Point point) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); - public Point PointToScreen(Point point) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton) diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 66f0cc3a40..834c49ba6b 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests private IDisposable Application() { - var screen = new Rect(new Point(), new Size(100, 100)); + var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screenImpl = new Mock(); screenImpl.Setup(x => x.ScreenCount).Returns(1); screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) }); diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs index fbb90de505..93163f4a92 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Controls.Utils; @@ -16,7 +17,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100, 200, 300", 600d, 600d, new[] { 100d, 200d, 300d })] [InlineData("100, 200, 300", 400d, 400d, new[] { 100d, 200d, 100d })] public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -25,7 +26,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] [InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })] public void MeasureArrange_AllStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -36,7 +37,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100,2*,3*", 100d, 100d, new[] { 100d, 0d, 0d })] [InlineData("100,2*,3*", 50d, 50d, new[] { 50d, 0d, 0d })] public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -49,7 +50,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100,200,Auto", 100d, 100d, new[] { 100d, 0d, 0d })] [InlineData("100,200,Auto", 50d, 50d, new[] { 50d, 0d, 0d })] public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -58,7 +59,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] [InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })] public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -69,14 +70,24 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,200,Auto", 200d, 200d, new[] { 0d, 200d, 0d })] [InlineData("*,200,Auto", 100d, 100d, new[] { 0d, 100d, 0d })] public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } + + /// + /// This is needed because Mono somehow converts double array to object array in attribute metadata + /// + static void AssertEqual(IList expected, IReadOnlyList actual) + { + var conv = expected.Cast().ToArray(); + Assert.Equal(conv, actual); + } + [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] private static void TestRowDefinitionsOnly(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { // Arrange var layout = new GridLayout(new RowDefinitions(length)); @@ -84,11 +95,11 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedLengthList, measure.LengthList); + AssertEqual(expectedLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(containerLength, measure); - Assert.Equal(expectedLengthList, arrange.LengthList); + AssertEqual(expectedLengthList, arrange.LengthList); } [Theory] @@ -99,7 +110,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,Auto", 0d, new[] { Inf, Inf, 0d }, new[] { 0d, 0d, 0d })] [InlineData("*,200,Auto", 200d, new[] { Inf, 200d, 0d }, new[] { 0d, 200d, 0d })] public void MeasureArrange_InfiniteMeasure_Correct(string length, double expectedDesiredLength, - IList expectedMeasureList, IList expectedArrangeList) + IList expectedMeasureList, IList expectedArrangeList) { // Arrange var layout = new GridLayout(new RowDefinitions(length)); @@ -107,34 +118,34 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(Inf); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedMeasureList, measure.LengthList); + AssertEqual(expectedMeasureList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(measure.DesiredLength, measure); - Assert.Equal(expectedArrangeList, arrange.LengthList); + AssertEqual(expectedArrangeList, arrange.LengthList); } [Theory] [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })] public void MeasureArrange_ChildHasSize_Correct(string length, - IList childLengthList, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + IList childLengthList, double containerLength, + double expectedDesiredLength, IList expectedLengthList) { // Arrange var lengthList = new ColumnDefinitions(length); var layout = new GridLayout(lengthList); layout.AppendMeasureConventions( Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, 1)), - x => childLengthList[x]); + x => (double)childLengthList[x]); // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedLengthList, measure.LengthList); + AssertEqual(expectedLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(containerLength, measure); - Assert.Equal(expectedLengthList, arrange.LengthList); + AssertEqual(expectedLengthList, arrange.LengthList); } [Theory] @@ -145,7 +156,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })] public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct( double containerLength, double expectedDesiredLength, - IList expectedMeasureLengthList, IList expectedArrangeLengthList) + IList expectedMeasureLengthList, IList expectedArrangeLengthList) { var length = "100,*,2*"; var childLengthList = new[] { 150d, 150d, 150d }; @@ -161,13 +172,13 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedMeasureLengthList, measure.LengthList); + AssertEqual(expectedMeasureLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange( double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength, measure); - Assert.Equal(expectedArrangeLengthList, arrange.LengthList); + AssertEqual(expectedArrangeLengthList, arrange.LengthList); } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index ba4ccb0527..3ea32ed719 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -336,25 +336,11 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; public ILayoutManager LayoutManager { get; } = new LayoutManager(); - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - public Point PointToClient(Point point) - { - throw new NotImplementedException(); - } - - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } + public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); + public void Invalidate(Rect rect) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private class TestItemsPresenter : ItemsPresenter diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index b992453fb0..9921a8de6c 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -717,6 +717,45 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(10, target.Panel.Children.Count); } + [Fact] + public void Scroll_To_Last_Should_Work() + { + var target = CreateTarget(itemCount: 11); + var scroller = (TestScroller)target.Parent; + + scroller.Width = scroller.Height = 100; + scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + + var last = (target.Items as IList)[10]; + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + } + + [Fact] + public void Second_Scroll_To_Last_Should_Work() + { + var target = CreateTarget(itemCount: 11); + var scroller = (TestScroller)target.Parent; + + scroller.Width = scroller.Height = 100; + scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + + var last = (target.Items as IList)[10]; + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + + target.ScrollIntoView(last); + + Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset); + Assert.Same(target.Panel.Children[9].DataContext, last); + } + public class Vertical { [Fact] @@ -1038,30 +1077,10 @@ namespace Avalonia.Controls.UnitTests.Presenters public ILayoutManager LayoutManager { get; } = new LayoutManager(); - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - - public Point PointToClient(Point point) - { - throw new NotImplementedException(); - } - - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } - - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } + public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); + public void Invalidate(Rect rect) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private class TestItemsPresenter : ItemsPresenter @@ -1090,4 +1109,4 @@ namespace Avalonia.Controls.UnitTests.Presenters } } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index a5c3881d37..e00084eba4 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -267,26 +267,46 @@ namespace Avalonia.Controls.UnitTests Assert.Null(page.Content); } + [Fact] + public void DataTemplate_Created_Content_Should_Be_Logical_Child_After_ApplyTemplate() + { + TabControl target = new TabControl + { + Template = TabControlTemplate(), + ContentTemplate = new FuncDataTemplate(x => + new TextBlock { Tag = "bar", Text = x }), + Items = new[] { "Foo" }, + }; + + ApplyTemplate(target); + target.ContentPart.UpdateChild(); + + var content = Assert.IsType(target.ContentPart.Child); + Assert.Equal("bar", content.Tag); + Assert.Same(target, content.GetLogicalParent()); + Assert.Single(target.GetLogicalChildren(), content); + } + private IControlTemplate TabControlTemplate() { return new FuncControlTemplate(parent => - new StackPanel { - Children = { - new ItemsPresenter - { - Name = "PART_ItemsPresenter", - [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], - }, - new ContentPresenter - { - Name = "PART_Content", - [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], - [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], - } - } + Children = + { + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], + }, + new ContentPresenter + { + Name = "PART_SelectedContentHost", + [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], + } + } }); } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index adf9bd9cec..c0b5342934 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -278,8 +278,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { - var screen1 = new Mock(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true); - var screen2 = new Mock(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false); + var screen1 = new Mock(new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + var screen2 = new Mock(new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); var screens = new Mock(); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); @@ -294,13 +294,13 @@ namespace Avalonia.Controls.UnitTests { var window = new Window(windowImpl.Object); window.WindowStartupLocation = WindowStartupLocation.CenterScreen; - window.Position = new Point(60, 40); + window.Position = new PixelPoint(60, 40); window.Show(); - var expectedPosition = new Point( - screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2, - screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2); + var expectedPosition = new PixelPoint( + (int)(screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2), + (int)(screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2)); Assert.Equal(window.Position, expectedPosition); } @@ -330,7 +330,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(parentWindowServices)) { var parentWindow = new Window(); - parentWindow.Position = new Point(60, 40); + parentWindow.Position = new PixelPoint(60, 40); parentWindow.Show(); @@ -338,14 +338,14 @@ namespace Avalonia.Controls.UnitTests { var window = new Window(); window.WindowStartupLocation = WindowStartupLocation.CenterOwner; - window.Position = new Point(60, 40); + window.Position = new PixelPoint(60, 40); window.Owner = parentWindow; window.Show(); - var expectedPosition = new Point( - parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2, - parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2); + var expectedPosition = new PixelPoint( + (int)(parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2), + (int)(parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2)); Assert.Equal(window.Position, expectedPosition); } diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 1b2ba3c7de..ef515ce155 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -22,4 +24,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj index bc883d0251..6b19d81034 100644 --- a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj +++ b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj @@ -1,9 +1,12 @@  netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -20,4 +23,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 4dcd98cb93..b7aa452a18 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -348,6 +348,7 @@ namespace Avalonia.LeakTests { public bool DrawFps { get; set; } public bool DrawDirtyRects { get; set; } + public event EventHandler SceneInvalidated; public void AddDirty(IVisual visual) { diff --git a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj index 1680ecf798..aba5f2d469 100644 --- a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj +++ b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -23,4 +25,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 123aadfda5..1ede4ee0b0 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -22,14 +22,14 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { - Converter = StringConverters.NullOrEmpty, + Converter = StringConverters.IsNullOrEmpty, }; var expressionObserver = (BindingExpression)target.Initiate( textBlock, TextBlock.TextProperty).Observable; - Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter); + Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter); } public class When_Binding_To_String @@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { - Converter = StringConverters.NotNullOrEmpty, + Converter = StringConverters.IsNotNullOrEmpty, StringFormat = "Hello {0}", }; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 735d5c421a..8b47bcec30 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -36,4 +38,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs new file mode 100644 index 0000000000..6ffaaaee5c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs @@ -0,0 +1,23 @@ +using System; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class ConverterTests + { + [Fact] + public void Bug_2228_Relative_Uris_Should_Be_Correctly_Parsed() + { + var testClass = typeof(TestClassWithUri); + var parsed = AvaloniaXamlLoader.Parse( + $"<{testClass.Name} xmlns='clr-namespace:{testClass.Namespace}' Uri='/test'/>", testClass.Assembly); + + Assert.False(parsed.Uri.IsAbsoluteUri); + } + } + + public class TestClassWithUri + { + public Uri Uri { get; set; } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs new file mode 100644 index 0000000000..abe6fa84b0 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class ClassWithNullableProperties + { + public Thickness? Thickness { get; set; } + public Orientation? Orientation { get; set; } + } + + public class NullableConverterTests + { + [Fact] + public void Nullable_Types_Should_Still_Be_Converted_Properly() + { + using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + { + var xaml = @""; + var loader = new AvaloniaXamlLoader(); + var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly); + Assert.Equal(new Thickness(5), data.Thickness); + Assert.Equal(Orientation.Vertical, data.Orientation); + } + } + } +} diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs index c82d616094..4f4004e159 100644 --- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Horizontally() { Decorator target = new Decorator @@ -215,7 +215,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Vertically() { Decorator target = new Decorator @@ -296,7 +296,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Left_Aligns_Content() { Decorator target = new Decorator @@ -323,7 +323,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Right_Aligns_Content() { Decorator target = new Decorator @@ -350,7 +350,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Top_Aligns_Content() { Decorator target = new Decorator @@ -377,7 +377,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Bottom_Aligns_Content() { Decorator target = new Decorator diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs index 8a8ed4cb4a..14f6f78a8d 100644 --- a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { } - [Fact] + [Win32Fact("Has text")] public async Task Wrapping_NoWrap() { Decorator target = new Decorator diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 4c8abd85f7..2efd28c2d5 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests public TestBase(string outputPath) { + outputPath = outputPath.Replace('\\', Path.DirectorySeparatorChar); var testPath = GetTestsDirectory(); var testFiles = Path.Combine(testPath, "TestFiles"); #if AVALONIA_SKIA diff --git a/tests/Avalonia.RenderTests/TestSkip.cs b/tests/Avalonia.RenderTests/TestSkip.cs new file mode 100644 index 0000000000..75407332e3 --- /dev/null +++ b/tests/Avalonia.RenderTests/TestSkip.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests +#endif +{ + public class Win32Fact : FactAttribute + { + public Win32Fact(string message) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Skip = message; + } + } +} + diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index e65dd848dd..3fd280f7ab 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -1,10 +1,12 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library CS0067 + true + @@ -23,4 +25,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d837f2cb2f..0561837ad1 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result); } [Fact] @@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Child().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; var result = new List(); Assert.False(await activator.Take(1)); @@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests var control = new TestLogical3(); var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(control).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result); } [Fact] diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs index 75599925b7..496998ecd9 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.False(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.False(await match.Activator.Take(1)); } [Fact] @@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests var control = new Control1(); var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.True(await activator.Take(1)); control.Classes.Remove("foo"); @@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests { var control = new Control1(); var target = default(Selector).Class("foo").Class("bar"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; var result = new List(); using (activator.Subscribe(x => result.Add(x))) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index b75b59c212..56dad13186 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.True(await activator.Take(1)); } @@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); } @@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); parent.Classes.Add("foo"); diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs index 067b08b296..ba9b443d92 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests public class SelectorTests_Multiple { [Fact] - public void Template_Child_Of_Control_With_Two_Classes() + public void Named_Template_Child_Of_Control_With_Two_Classes() { var template = new FuncControlTemplate(parent => { @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests var border = (Border)((IVisual)control).VisualChildren.Single(); var values = new List(); - var activator = selector.Match(border).ObservableResult; + var match = selector.Match(border); - activator.Subscribe(x => values.Add(x)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + match.Activator.Subscribe(x => values.Add(x)); Assert.Equal(new[] { false }, values); control.Classes.AddRange(new[] { "foo", "bar" }); @@ -51,6 +52,77 @@ namespace Avalonia.Styling.UnitTests Assert.Equal(new[] { false, true, false }, values); } + [Fact] + public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type() + { + var template = new FuncControlTemplate(parent => + { + return new Border + { + Name = "border", + }; + }); + + var control = new Button + { + Template = template, + }; + + control.ApplyTemplate(); + + var selector = default(Selector) + .OfType