Browse Source

Merge branch 'master' into value-lookup

pull/2209/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
7c47070a18
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      .github/PULL_REQUEST_TEMPLATE.md
  2. 5
      .ncrunch/Avalonia.Desktop.v3.ncrunchproject
  3. 5
      .ncrunch/Avalonia.net461.v3.ncrunchproject
  4. 81
      Avalonia.sln
  5. 1
      Avalonia.v3.ncrunchsolution
  6. 8
      azure-pipelines.yml
  7. 2
      build/SkiaSharp.props
  8. 6
      readme.md
  9. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  10. 2
      samples/ControlCatalog/MainView.xaml
  11. 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  12. 19
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  13. 9
      samples/ControlCatalog/Pages/DropDownPage.xaml
  14. 3
      samples/ControlCatalog/Pages/DropDownPage.xaml.cs
  15. 4
      samples/ControlCatalog/Pages/ScreenPage.cs
  16. 2
      samples/ControlCatalog/SideBar.xaml
  17. 6
      samples/PlatformSanityChecks/App.xaml
  18. 13
      samples/PlatformSanityChecks/App.xaml.cs
  19. 13
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  20. 132
      samples/PlatformSanityChecks/Program.cs
  21. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  22. 8
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  23. 21
      src/Avalonia.Animation/AnimationInstance`1.cs
  24. 24
      src/Avalonia.Base/Data/Converters/ObjectConverters.cs
  25. 4
      src/Avalonia.Base/Data/Converters/StringConverters.cs
  26. 2
      src/Avalonia.Controls/ContentControl.cs
  27. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  28. 9
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  29. 27
      src/Avalonia.Controls/PixelPointEventArgs.cs
  30. 4
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  31. 6
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  32. 8
      src/Avalonia.Controls/Platform/Screen.cs
  33. 27
      src/Avalonia.Controls/PointEventArgs.cs
  34. 30
      src/Avalonia.Controls/Primitives/Popup.cs
  35. 10
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  36. 14
      src/Avalonia.Controls/Screens.cs
  37. 7
      src/Avalonia.Controls/TabControl.cs
  38. 31
      src/Avalonia.Controls/TopLevel.cs
  39. 60
      src/Avalonia.Controls/Window.cs
  40. 13
      src/Avalonia.Controls/WindowBase.cs
  41. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  42. 10
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  43. 6
      src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs
  44. 4
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  45. 2
      src/Avalonia.Input/IMouseDevice.cs
  46. 2
      src/Avalonia.Input/IPointerDevice.cs
  47. 57
      src/Avalonia.Input/MouseDevice.cs
  48. 2
      src/Avalonia.Input/Raw/RawDragEvent.cs
  49. 4
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  50. 2
      src/Avalonia.Input/Raw/RawKeyEventArgs.cs
  51. 2
      src/Avalonia.Input/Raw/RawMouseEventArgs.cs
  52. 2
      src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
  53. 2
      src/Avalonia.Input/Raw/RawTextInputEventArgs.cs
  54. 15
      src/Avalonia.Native/Helpers.cs
  55. 5
      src/Avalonia.Native/ScreenImpl.cs
  56. 14
      src/Avalonia.Native/WindowImplBase.cs
  57. 6
      src/Avalonia.OpenGL/EglContext.cs
  58. 29
      src/Avalonia.OpenGL/EglDisplay.cs
  59. 21
      src/Avalonia.OpenGL/EglErrors.cs
  60. 33
      src/Avalonia.OpenGL/EglInterface.cs
  61. 3
      src/Avalonia.OpenGL/GlConsts.cs
  62. 5
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  63. 15
      src/Avalonia.OpenGL/GlErrors.cs
  64. 37
      src/Avalonia.OpenGL/GlInterface.cs
  65. 27
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  66. 30
      src/Avalonia.OpenGL/OpenGlException.cs
  67. 6
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  68. 4
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  69. 72
      src/Avalonia.Styling/Styling/NotSelector.cs
  70. 11
      src/Avalonia.Styling/Styling/Selectors.cs
  71. 18
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  72. 9
      src/Avalonia.Themes.Default/CheckBox.xaml
  73. 2
      src/Avalonia.Themes.Default/TabControl.xaml
  74. 4
      src/Avalonia.Themes.Default/TextBox.xaml
  75. 7
      src/Avalonia.Visuals/Media/FontFamily.cs
  76. 2
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  77. 201
      src/Avalonia.Visuals/Media/PixelPoint.cs
  78. 436
      src/Avalonia.Visuals/Media/PixelRect.cs
  79. 50
      src/Avalonia.Visuals/Media/PixelSize.cs
  80. 5
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  81. 80
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  82. 8
      src/Avalonia.Visuals/Rendering/IRenderRoot.cs
  83. 13
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  84. 5
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  85. 12
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  86. 6
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  87. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  88. 36
      src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs
  89. 10
      src/Avalonia.Visuals/VisualExtensions.cs
  90. 14
      src/Avalonia.X11/Avalonia.X11.csproj
  91. 91
      src/Avalonia.X11/Glx/Glx.cs
  92. 106
      src/Avalonia.X11/Glx/GlxConsts.cs
  93. 41
      src/Avalonia.X11/Glx/GlxContext.cs
  94. 136
      src/Avalonia.X11/Glx/GlxDisplay.cs
  95. 86
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  96. 44
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  97. 2108
      src/Avalonia.X11/Keysyms.cs
  98. 17
      src/Avalonia.X11/Stubs.cs
  99. 207
      src/Avalonia.X11/X11Atoms.cs
  100. 234
      src/Avalonia.X11/X11Clipboard.cs

27
.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

5
.ncrunch/Avalonia.Desktop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.net461.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

81
Avalonia.sln

@ -196,6 +196,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1767,6 +1773,78 @@ Global
{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
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1821,6 +1899,9 @@ Global
{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}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
Avalonia.v3.ncrunchsolution

@ -2,6 +2,7 @@
<Settings>
<AdditionalFilesToIncludeForSolution>
<Value>tests\TestFiles\**.*</Value>
<Value>src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll</Value>
</AdditionalFilesToIncludeForSolution>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<ProjectConfigStoragePathRelativeToSolutionDir>.ncrunch</ProjectConfigStoragePathRelativeToSolutionDir>

8
azure-pipelines.yml

@ -92,13 +92,13 @@ 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:
@ -127,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()

2
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" />
</ItemGroup>
</Project>

6
readme.md

@ -2,9 +2,9 @@
# Avalonia
| Gitter Chat | Build Status (Win, Linux, OSX) | 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) | [![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

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -9,6 +9,7 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup>

2
samples/ControlCatalog/MainView.xaml

@ -32,10 +32,10 @@
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
</TabControl>
</Grid>

1
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -6,6 +6,7 @@
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<Button Name="DecoratedWindow">Decorated window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button>
</StackPanel>
</UserControl>

19
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -31,15 +31,22 @@ namespace ControlCatalog.Pages
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().ShowDialog(GetWindow());
};
{
new DecoratedWindow().Show();
};
this.FindControl<Button>("DecoratedWindowDialog").Click += delegate
{
new DecoratedWindow().ShowDialog(GetWindow());
};
this.FindControl<Button>("Dialog").Click += delegate
{
new MainWindow().ShowDialog(GetWindow());
var window = new Window();
window.Height = 200;
window.Width = 200;
window.Content = new TextBlock { Text = "Hello world!" };
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.ShowDialog(GetWindow());
};
}
Window GetWindow() => (Window)this.VisualRoot;

9
samples/ControlCatalog/Pages/DropDownPage.xaml

@ -27,6 +27,15 @@
<TextBox Text="TextBox"/>
</DropDownItem>
</DropDown>
<DropDown x:Name="fontDropDown" SelectedIndex="0">
<DropDown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontFamily="{Binding}" />
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
</StackPanel>
</StackPanel>

3
samples/ControlCatalog/Pages/DropDownPage.xaml.cs

@ -13,6 +13,9 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var fontDropDown = this.Find<DropDown>("fontDropDown");
fontDropDown.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
fontDropDown.SelectedIndex = 0;
}
}
}

4
samples/ControlCatalog/Pages/ScreenPage.cs

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace ControlCatalog.Pages
{
@ -23,6 +24,7 @@ namespace ControlCatalog.Pages
base.Render(context);
Window w = (Window)VisualRoot;
Screen[] screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
Pen p = new Pen(Brushes.Black);
if (screens != null)
@ -56,7 +58,7 @@ namespace ControlCatalog.Pages
text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}";
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
}

2
samples/ControlCatalog/SideBar.xaml

@ -26,7 +26,7 @@
</ItemsPresenter>
</ScrollViewer>
<ContentPresenter
Name="PART_Content"
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

6
samples/PlatformSanityChecks/App.xaml

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

13
samples/PlatformSanityChecks/App.xaml.cs

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Markup.Xaml;
namespace PlatformSanityChecks
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
}
}

13
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup>
</Project>

132
samples/PlatformSanityChecks/Program.cs

@ -0,0 +1,132 @@
using System;
using System.Diagnostics;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.X11;
namespace PlatformSanityChecks
{
public class Program
{
static Thread UiThread;
static void Main(string[] args)
{
UiThread = Thread.CurrentThread;
AppBuilder.Configure<App>().RuntimePlatformServicesInitializer();
var app = new App();
AvaloniaX11PlatformExtensions.InitializeX11Platform();
CheckPlatformThreading();
}
static bool CheckAccess() => UiThread == Thread.CurrentThread;
static void VerifyAccess()
{
if (!CheckAccess())
Die("Call from invalid thread");
}
static Exception Die(string error)
{
Console.Error.WriteLine(error);
Console.Error.WriteLine(Environment.StackTrace);
Process.GetCurrentProcess().Kill();
throw new Exception(error);
}
static IDisposable Enter([CallerMemberName] string caller = null)
{
Console.WriteLine("Entering " + caller);
return Disposable.Create(() => { Console.WriteLine("Leaving " + caller); });
}
static void EnterLoop(Action<CancellationTokenSource> cb, [CallerMemberName] string caller = null)
{
using (Enter(caller))
{
var cts = new CancellationTokenSource();
cb(cts);
Dispatcher.UIThread.MainLoop(cts.Token);
if (!cts.IsCancellationRequested)
Die("Unexpected loop exit");
}
}
static void CheckTimerOrdering() => EnterLoop(cts =>
{
bool firstFired = false, secondFired = false;
DispatcherTimer.Run(() =>
{
Console.WriteLine("Second tick");
VerifyAccess();
if (!firstFired)
throw Die("Invalid timer ordering");
if (secondFired)
throw Die("Invocation of finished timer");
secondFired = true;
cts.Cancel();
return false;
}, TimeSpan.FromSeconds(2));
DispatcherTimer.Run(() =>
{
Console.WriteLine("First tick");
VerifyAccess();
if (secondFired)
throw Die("Invalid timer ordering");
if (firstFired)
throw Die("Invocation of finished timer");
firstFired = true;
return false;
}, TimeSpan.FromSeconds(1));
});
static void CheckTimerTicking() => EnterLoop(cts =>
{
int ticks = 0;
var st = Stopwatch.StartNew();
DispatcherTimer.Run(() =>
{
ticks++;
Console.WriteLine($"Tick {ticks} at {st.Elapsed}");
if (ticks == 5)
{
if (st.Elapsed.TotalSeconds < 4.5)
Die("Timer is too fast");
if (st.Elapsed.TotalSeconds > 6)
Die("Timer is too slow");
cts.Cancel();
return false;
}
return true;
}, TimeSpan.FromSeconds(1));
});
static void CheckSignaling() => EnterLoop(cts =>
{
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(100);
Dispatcher.UIThread.Post(() =>
{
VerifyAccess();
cts.Cancel();
});
});
});
static void CheckPlatformThreading()
{
CheckSignaling();
CheckTimerOrdering();
CheckTimerTicking();
}
}
}

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -10,10 +10,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
class PopupImpl : TopLevelImpl, IPopupImpl
{
private Point _position;
private PixelPoint _position;
private bool _isAdded;
Action IWindowBaseImpl.Activated { get; set; }
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public PopupImpl() : base(ActivityTracker.Current, true)
@ -36,7 +36,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IScreenImpl Screen { get; }
public Point Position
public PixelPoint Position
{
get { return _position; }
set

8
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -99,14 +99,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
}
public Point PointToClient(Point point)
public Point PointToClient(PixelPoint point)
{
return point;
return point.ToPoint(1);
}
public Point PointToScreen(Point point)
public PixelPoint PointToScreen(Point point)
{
return point;
return PixelPoint.FromPoint(point, 1);
}
public void SetCursor(IPlatformHandle cursor)

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

@ -19,6 +19,7 @@ namespace Avalonia.Animation
private ulong? _iterationCount;
private ulong _currentIteration;
private bool _gotFirstKFValue;
private bool _playbackReversed;
private FillMode _fillMode;
private PlaybackDirection _playbackDirection;
private Animator<T> _animator;
@ -160,9 +161,14 @@ namespace Avalonia.Animation
_currentIteration = (ulong)(opsTime / iterationTime);
// Stop animation when the current iteration is beyond the iteration count.
// Stop animation when the current iteration is beyond the iteration count
// and snap the last iteration value to exact values.
if ((_currentIteration + 1) > _iterationCount)
{
var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
}
if (playbackTime <= iterDuration)
{
@ -170,27 +176,26 @@ namespace Avalonia.Animation
var normalizedTime = playbackTime / iterDuration;
// Check if normalized time needs to be reversed according to PlaybackDirection
bool playbackReversed;
switch (_playbackDirection)
{
case PlaybackDirection.Normal:
playbackReversed = false;
_playbackReversed = false;
break;
case PlaybackDirection.Reverse:
playbackReversed = true;
_playbackReversed = true;
break;
case PlaybackDirection.Alternate:
playbackReversed = (_currentIteration % 2 == 0) ? false : true;
_playbackReversed = (_currentIteration % 2 == 0) ? false : true;
break;
case PlaybackDirection.AlternateReverse:
playbackReversed = (_currentIteration % 2 == 0) ? true : false;
_playbackReversed = (_currentIteration % 2 == 0) ? true : false;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
}
if (playbackReversed)
if (_playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate

24
src/Avalonia.Base/Data/Converters/ObjectConverters.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with objects.
/// </summary>
public static class ObjectConverters
{
/// <summary>
/// A value converter that returns true if the input object is a null reference.
/// </summary>
public static readonly IValueConverter IsNull =
new FuncValueConverter<object, bool>(x => x is null);
/// <summary>
/// A value converter that returns true if the input object is not null.
/// </summary>
public static readonly IValueConverter IsNotNull =
new FuncValueConverter<object, bool>(x => !(x is null));
}
}

4
src/Avalonia.Base/Data/Converters/StringConverters.cs

@ -12,13 +12,13 @@ namespace Avalonia.Data.Converters
/// <summary>
/// A value converter that returns true if the input string is null or an empty string.
/// </summary>
public static readonly IValueConverter NullOrEmpty =
public static readonly IValueConverter IsNullOrEmpty =
new FuncValueConverter<string, bool>(string.IsNullOrEmpty);
/// <summary>
/// A value converter that returns true if the input string is not null or empty.
/// </summary>
public static readonly IValueConverter NotNullOrEmpty =
public static readonly IValueConverter IsNotNullOrEmpty =
new FuncValueConverter<string, bool>(x => !string.IsNullOrEmpty(x));
}
}

2
src/Avalonia.Controls/ContentControl.cs

@ -45,8 +45,6 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
PseudoClass<ContentControl, object>(ContentProperty, x => x != null, ":valid");
PseudoClass<ContentControl, object>(ContentProperty, x => x == null, ":invalid");
}
/// <summary>

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -49,9 +49,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action<double> ScalingChanged { get; set; }
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
public virtual Point PointToClient(Point point) => point;
public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);
public virtual Point PointToScreen(Point point) => point;
public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public virtual void SetCursor(IPlatformHandle cursor)
{

9
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -287,6 +287,13 @@ namespace Avalonia.Controls
ValueProperty.Changed.Subscribe(OnValueChanged);
}
/// <inheritdoc />
protected override void OnLostFocus(RoutedEventArgs e)
{
CommitInput();
base.OnLostFocus(e);
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
@ -812,7 +819,7 @@ namespace Avalonia.Controls
/// <summary>
/// Raised when the <see cref="Value"/> changes.
/// </summary>
public event EventHandler<SpinEventArgs> ValueChanged
public event EventHandler<NumericUpDownValueChangedEventArgs> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }

27
src/Avalonia.Controls/PixelPointEventArgs.cs

@ -0,0 +1,27 @@
// 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.Controls
{
/// <summary>
/// Provides <see cref="PixelPoint"/> data for events.
/// </summary>
public class PixelPointEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelPointEventArgs"/> class.
/// </summary>
/// <param name="point">The <see cref=PixelPoint"/> data.</param>
public PixelPointEventArgs(PixelPoint point)
{
Point = point;
}
/// <summary>
/// Gets the <see cref="PixelPoint"/> data.
/// </summary>
public PixelPoint Point { get; }
}
}

4
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -82,14 +82,14 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="point">The point in screen coordinates.</param>
/// <returns>The point in client coordinates.</returns>
Point PointToClient(Point point);
Point PointToClient(PixelPoint point);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
Point PointToScreen(Point point);
PixelPoint PointToScreen(Point point);
/// <summary>
/// Sets the cursor associated with the toplevel.

6
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -27,14 +27,14 @@ namespace Avalonia.Platform
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Gets position of the window relatively to the screen
/// Gets the position of the window in device pixels.
/// </summary>
Point Position { get; set; }
PixelPoint Position { get; set; }
/// <summary>
/// Gets or sets a method called when the window's position changes.
/// </summary>
Action<Point> PositionChanged { get; set; }
Action<PixelPoint> PositionChanged { get; set; }
/// <summary>
/// Activates the window.

8
src/Avalonia.Controls/Platform/Screen.cs

@ -2,17 +2,17 @@
{
public class Screen
{
public Rect Bounds { get; }
public PixelRect Bounds { get; }
public Rect WorkingArea { get; }
public PixelRect WorkingArea { get; }
public bool Primary { get; }
public Screen(Rect bounds, Rect workingArea, bool primary)
public Screen(PixelRect bounds, PixelRect workingArea, bool primary)
{
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;
}
}
}
}

27
src/Avalonia.Controls/PointEventArgs.cs

@ -1,27 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides <see cref="Point"/> data for events.
/// </summary>
public class PointEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PointEventArgs"/> class.
/// </summary>
/// <param name="point">The <see cref="Point"/> data.</param>
public PointEventArgs(Point point)
{
Point = point;
}
/// <summary>
/// Gets the <see cref="Point"/> data.
/// </summary>
public Point Point { get; }
}
}

30
src/Avalonia.Controls/Primitives/Popup.cs

@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives
/// Gets the position for the popup based on the placement properties.
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
@ -388,35 +388,31 @@ namespace Avalonia.Controls.Primitives
return result;
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var zero = default(Point);
var mode = placement;
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
}
var root = target?.GetVisualRoot();
var mode = root != null ? placement : PlacementMode.Pointer;
var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling,
verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling);
return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
return new PixelPoint(
screenOffset.X + mouseOffset.X,
screenOffset.Y + mouseOffset.Y);
}
return default(Point);
return default;
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ??
zero;
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero;
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");

10
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -85,18 +85,18 @@ namespace Avalonia.Controls.Primitives
if (screen != null)
{
var scaling = VisualRoot.RenderScaling;
var screenX = Position.X + (Bounds.Width * scaling) - screen.Bounds.X;
var screenY = Position.Y + (Bounds.Height * scaling) - screen.Bounds.Y;
var bounds = PixelRect.FromRect(Bounds, scaling);
var screenX = Position.X + bounds.Width - screen.Bounds.X;
var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
Position = Position.WithX(Position.X - screenX - bounds.Width);
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
Position = Position.WithY(Position.Y - screenY - bounds.Height);
}
}
}

14
src/Avalonia.Controls/Screens.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
_iScreenImpl = iScreenImpl;
}
public Screen ScreenFromBounds(Rect bounds){
public Screen ScreenFromBounds(PixelRect bounds){
Screen currMaxScreen = null;
double maxAreaSize = 0;
@ -39,16 +39,16 @@ namespace Avalonia.Controls
return currMaxScreen;
}
public Screen ScreenFromPoint(Point point)
public Screen ScreenFromPoint(PixelPoint point)
{
return All.FirstOrDefault(x=>x.Bounds.Contains(point));
return All.FirstOrDefault(x => x.Bounds.Contains(point));
}
public Screen ScreenFromVisual(IVisual visual)
{
Point tl = visual.PointToScreen(visual.Bounds.TopLeft);
Point br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new Rect(tl,br));
var tl = visual.PointToScreen(visual.Bounds.TopLeft);
var br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new PixelRect(tl, br));
}
}
}
}

7
src/Avalonia.Controls/TabControl.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -65,6 +66,10 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
ContentControlMixin.Attach<TabControl>(
SelectedContentProperty,
x => x.LogicalChildren,
"PART_SelectedContentHost");
}
/// <summary>
@ -142,7 +147,7 @@ namespace Avalonia.Controls
ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_Content");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_SelectedContentHost");
}
/// <inheritdoc/>

31
src/Avalonia.Controls/TopLevel.cs

@ -98,6 +98,11 @@ namespace Avalonia.Controls
Renderer = impl.CreateRenderer(this);
if (Renderer != null)
{
Renderer.SceneInvalidated += SceneInvalidated;
}
impl.SetInputRoot(this);
impl.Closed = HandleClosed;
@ -131,6 +136,11 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Fired when the window is opened.
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Fired when the window is closed.
/// </summary>
@ -231,17 +241,17 @@ namespace Avalonia.Controls
{
PlatformImpl?.Invalidate(rect);
}
/// <inheritdoc/>
Point IRenderRoot.PointToClient(Point p)
Point IRenderRoot.PointToClient(PixelPoint p)
{
return PlatformImpl?.PointToClient(p) ?? default(Point);
return PlatformImpl?.PointToClient(p) ?? default;
}
/// <inheritdoc/>
Point IRenderRoot.PointToScreen(Point p)
PixelPoint IRenderRoot.PointToScreen(Point p)
{
return PlatformImpl?.PointToScreen(p) ?? default(Point);
return PlatformImpl?.PointToScreen(p) ?? default;
}
/// <summary>
@ -306,6 +316,12 @@ namespace Avalonia.Controls
$"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
}
/// <summary>
/// Raises the <see cref="Opened"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e);
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
/// warning if not found.
@ -349,5 +365,10 @@ namespace Avalonia.Controls
{
_inputManager.ProcessInput(e);
}
private void SceneInvalidated(object sender, SceneInvalidatedEventArgs e)
{
(this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect);
}
}
}

60
src/Avalonia.Controls/Window.cs

@ -388,45 +388,49 @@ namespace Avalonia.Controls
PlatformImpl?.Show();
Renderer?.Start();
}
SetWindowStartupLocation();
SetWindowStartupLocation(Owner?.PlatformImpl);
OnOpened(EventArgs.Empty);
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>
/// A task that can be used to track the lifetime of the dialog.
/// </returns>
public Task ShowDialog(Window parent)
public Task ShowDialog(Window owner)
{
return ShowDialog<object>(parent);
return ShowDialog<object>(owner);
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(Window parent) => ShowDialog<TResult>(parent.PlatformImpl);
public Task<TResult> ShowDialog<TResult>(Window owner) => ShowDialog<TResult>(owner.PlatformImpl);
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(IWindowImpl parent)
public Task<TResult> ShowDialog<TResult>(IWindowImpl owner)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
if(owner == null)
throw new ArgumentNullException(nameof(owner));
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
@ -435,15 +439,15 @@ namespace Avalonia.Controls
AddWindow(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.ExecuteInitialLayoutPass(this);
var result = new TaskCompletionSource<TResult>();
using (BeginAutoSizing())
{
PlatformImpl?.ShowDialog(parent);
var result = new TaskCompletionSource<TResult>();
PlatformImpl?.ShowDialog(owner);
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
@ -452,29 +456,43 @@ namespace Avalonia.Controls
.Take(1)
.Subscribe(_ =>
{
parent.Activate();
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
return result.Task;
OnOpened(EventArgs.Empty);
}
SetWindowStartupLocation(owner);
return result.Task;
}
void SetWindowStartupLocation()
private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
{
var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1;
// TODO: We really need non-client size here.
var rect = new PixelRect(
PixelPoint.Origin,
PixelSize.FromSize(ClientSize, scaling));
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(Bounds.Position);
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
{
if (Owner != null)
if (owner != null)
{
var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
// TODO: We really need non-client size here.
var ownerRect = new PixelRect(
owner.Position,
PixelSize.FromSize(owner.ClientSize, scaling));
Position = ownerRect.CenterRect(rect).Position;
}
}
}

13
src/Avalonia.Controls/WindowBase.cs

@ -81,7 +81,7 @@ namespace Avalonia.Controls
/// <summary>
/// Fired when the window position is changed.
/// </summary>
public event EventHandler<PointEventArgs> PositionChanged;
public event EventHandler<PixelPointEventArgs> PositionChanged;
[CanBeNull]
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
@ -98,9 +98,9 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the window position in screen coordinates.
/// </summary>
public Point Position
public PixelPoint Position
{
get { return PlatformImpl?.Position ?? default(Point); }
get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
set
{
if (PlatformImpl is IWindowBaseImpl impl)
@ -163,7 +163,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Shows the popup.
/// Shows the window.
/// </summary>
public virtual void Show()
{
@ -181,6 +181,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show();
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
finally
{
@ -267,9 +268,9 @@ namespace Avalonia.Controls
/// <see cref="IWindowBaseImpl.PositionChanged"/>.
/// </summary>
/// <param name="pos">The window position.</param>
private void HandlePositionChanged(Point pos)
private void HandlePositionChanged(PixelPoint pos)
{
PositionChanged?.Invoke(this, new PointEventArgs(pos));
PositionChanged?.Invoke(this, new PixelPointEventArgs(pos));
}
/// <summary>

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

@ -35,8 +35,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public Point Position { get; set; }
public Action<Point> PositionChanged { get; set; }
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Func<bool> Closing { get; set; }

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

@ -29,8 +29,8 @@ namespace Avalonia.DesignerSupport.Remote
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public Point Position { get; set; }
public Action<Point> PositionChanged { get; set; }
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
@ -45,9 +45,9 @@ namespace Avalonia.DesignerSupport.Remote
{
}
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)
{
@ -157,6 +157,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1;
public Screen[] AllScreens { get; } =
{new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)};
{new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true)};
}
}

6
src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs

@ -29,7 +29,7 @@ namespace Avalonia
}
else
{
LoadGtk3(builder);
LoadX11(builder);
LoadSkia(builder);
}
return builder;
@ -42,9 +42,9 @@ namespace Avalonia
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseWin32();
static void LoadGtk3<TAppBuilder>(TAppBuilder builder)
static void LoadX11<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseGtk3();
=> builder.UseX11();
static void LoadDirect2D1<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()

4
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" />
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" />
<ProjectReference Include="../../src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj" />
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" />
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" />
<ProjectReference Include="../Avalonia.X11/Avalonia.X11.csproj" />
</ItemGroup>
</Project>

2
src/Avalonia.Input/IMouseDevice.cs

@ -11,6 +11,6 @@ namespace Avalonia.Input
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
Point Position { get; }
PixelPoint Position { get; }
}
}

2
src/Avalonia.Input/IPointerDevice.cs

@ -12,5 +12,7 @@ namespace Avalonia.Input
void Capture(IInputElement control);
Point GetPosition(IVisual relativeTo);
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

57
src/Avalonia.Input/MouseDevice.cs

@ -18,7 +18,7 @@ namespace Avalonia.Input
{
private int _clickCount;
private Rect _lastClickRect;
private uint _lastClickTime;
private ulong _lastClickTime;
private IInputElement _captured;
private IDisposable _capturedSubscription;
@ -54,7 +54,7 @@ namespace Avalonia.Input
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
public Point Position
public PixelPoint Position
{
get;
protected set;
@ -104,6 +104,23 @@ namespace Avalonia.Input
ProcessRawEvent(margs);
}
public void SceneInvalidated(IInputRoot root, Rect rect)
{
var clientPoint = root.PointToClient(Position);
if (rect.Contains(clientPoint))
{
if (Captured == null)
{
SetPointerOver(this, root, clientPoint);
}
else
{
SetPointerOver(this, root, Captured);
}
}
}
private void ProcessRawEvent(RawMouseEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
@ -152,7 +169,7 @@ namespace Avalonia.Input
ClearPointerOver(this, root);
}
private bool MouseDown(IMouseDevice device, uint timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -305,16 +322,41 @@ namespace Avalonia.Input
Device = device,
};
if (element!=null && !element.IsAttachedToVisualTree)
{
// element has been removed from visual tree so do top down cleanup
if (root.IsPointerOver)
ClearChildrenPointerOver(e, root,true);
}
while (element != null)
{
e.Source = element;
e.Handled = false;
element.RaiseEvent(e);
element = (IInputElement)element.VisualParent;
}
root.PointerOverElement = null;
}
private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot)
{
foreach (IInputElement el in element.VisualChildren)
{
if (el.IsPointerOver)
{
ClearChildrenPointerOver(e, el, true);
break;
}
}
if(clearRoot)
{
e.Source = element;
e.Handled = false;
element.RaiseEvent(e);
}
}
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p)
{
Contract.Requires<ArgumentNullException>(device != null);
@ -361,13 +403,18 @@ namespace Avalonia.Input
el = root.PointerOverElement;
e.RoutedEvent = InputElement.PointerLeaveEvent;
if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
{
ClearChildrenPointerOver(e,branch,false);
}
while (el != null && el != branch)
{
e.Source = el;
e.Handled = false;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
}
}
el = root.PointerOverElement = element;
e.RoutedEvent = InputElement.PointerEnterEvent;

2
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -3,7 +3,7 @@
public class RawDragEvent : RawInputEventArgs
{
public IInputElement InputRoot { get; }
public Point Location { get; }
public Point Location { get; set; }
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }

4
src/Avalonia.Input/Raw/RawInputEventArgs.cs

@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw
/// </summary>
/// <param name="device">The associated device.</param>
/// <param name="timestamp">The event timestamp.</param>
public RawInputEventArgs(IInputDevice device, uint timestamp)
public RawInputEventArgs(IInputDevice device, ulong timestamp)
{
Contract.Requires<ArgumentNullException>(device != null);
@ -47,6 +47,6 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the timestamp associated with the event.
/// </summary>
public uint Timestamp { get; private set; }
public ulong Timestamp { get; private set; }
}
}

2
src/Avalonia.Input/Raw/RawKeyEventArgs.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input.Raw
{
public RawKeyEventArgs(
IKeyboardDevice device,
uint timestamp,
ulong timestamp,
RawKeyEventType type,
Key key, InputModifiers modifiers)
: base(device, timestamp)

2
src/Avalonia.Input/Raw/RawMouseEventArgs.cs

@ -35,7 +35,7 @@ namespace Avalonia.Input.Raw
/// <param name="inputModifiers">The input modifiers.</param>
public RawMouseEventArgs(
IInputDevice device,
uint timestamp,
ulong timestamp,
IInputRoot root,
RawMouseEventType type,
Point position,

2
src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs

@ -8,7 +8,7 @@ namespace Avalonia.Input.Raw
{
public RawMouseWheelEventArgs(
IInputDevice device,
uint timestamp,
ulong timestamp,
IInputRoot root,
Point position,
Vector delta, InputModifiers inputModifiers)

2
src/Avalonia.Input/Raw/RawTextInputEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input.Raw
{
public string Text { get; set; }
public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp)
public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp)
{
Text = text;
}

15
src/Avalonia.Native/Helpers.cs

@ -12,11 +12,21 @@ namespace Avalonia.Native
return new Point(pt.X, pt.Y);
}
public static PixelPoint ToAvaloniaPixelPoint(this AvnPoint pt)
{
return new PixelPoint((int)pt.X, (int)pt.Y);
}
public static AvnPoint ToAvnPoint (this Point pt)
{
return new AvnPoint { X = pt.X, Y = pt.Y };
}
public static AvnPoint ToAvnPoint(this PixelPoint pt)
{
return new AvnPoint { X = pt.X, Y = pt.Y };
}
public static AvnSize ToAvnSize (this Size size)
{
return new AvnSize { Height = size.Height, Width = size.Width };
@ -31,5 +41,10 @@ namespace Avalonia.Native
{
return new Rect(rect.X, rect.Y, rect.Width, rect.Height);
}
public static PixelRect ToAvaloniaPixelRect(this AvnRect rect)
{
return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
}
}
}

5
src/Avalonia.Native/ScreenImpl.cs

@ -29,7 +29,10 @@ namespace Avalonia.Native
{
var screen = _native.GetScreen(i);
result[i] = new Screen(screen.Bounds.ToAvaloniaRect(), screen.WorkingArea.ToAvaloniaRect(), screen.Primary);
result[i] = new Screen(
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);
}
return result;

14
src/Avalonia.Native/WindowImplBase.cs

@ -159,7 +159,7 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
{
_parent.PositionChanged?.Invoke(position.ToAvaloniaPoint());
_parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint());
}
void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
@ -275,20 +275,20 @@ namespace Avalonia.Native
}
public Point Position
public PixelPoint Position
{
get => _native.GetPosition().ToAvaloniaPoint();
get => _native.GetPosition().ToAvaloniaPixelPoint();
set => _native.SetPosition(value.ToAvnPoint());
}
public Point PointToClient(Point point)
public Point PointToClient(PixelPoint point)
{
return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
}
public Point PointToScreen(Point point)
public PixelPoint PointToScreen(Point point)
{
return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPoint();
return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
}
public void Hide()
@ -320,7 +320,7 @@ namespace Avalonia.Native
_native.Cursor = newCursor.Cursor;
}
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action<RawInputEventArgs> Input { get; set; }

6
src/Avalonia.OpenGL/EglContext.cs

@ -31,14 +31,14 @@ namespace Avalonia.OpenGL
public void MakeCurrent()
{
if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public void MakeCurrent(EglSurface surface)
{
var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface;
var surf = surface?.DangerousGetHandle() ?? OffscreenSurface;
if (!_egl.MakeCurrent(_disp.Handle, surf, surf, Context))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
}
}

29
src/Avalonia.OpenGL/EglDisplay.cs

@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
using static Avalonia.OpenGL.EglConsts;
@ -34,11 +33,11 @@ namespace Avalonia.OpenGL
if (_display == IntPtr.Zero)
_display = _egl.GetDisplay(IntPtr.Zero);
if(_display == IntPtr.Zero)
throw new OpenGlException("eglGetDisplay failed");
if (_display == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglGetDisplay", _egl);
if (!_egl.Initialize(_display, out var major, out var minor))
throw new OpenGlException("eglInitialize failed");
throw OpenGlException.GetFormattedException("eglInitialize", _egl);
foreach (var cfg in new[]
{
@ -87,18 +86,8 @@ namespace Avalonia.OpenGL
if (_contextAttributes == null)
throw new OpenGlException("No suitable EGL config was found");
GlInterface = new GlInterface((proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = _egl.GetProcAddress(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
});
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b));
}
public EglDisplay() : this(new EglInterface())
@ -113,7 +102,7 @@ namespace Avalonia.OpenGL
var shareCtx = (EglContext)share;
var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw new OpenGlException("eglCreateContext failed");
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var surf = _egl.CreatePBufferSurface(_display, _config, new[]
{
EGL_WIDTH, 1,
@ -121,7 +110,7 @@ namespace Avalonia.OpenGL
EGL_NONE
});
if (surf == IntPtr.Zero)
throw new OpenGlException("eglCreatePbufferSurface failed");
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
var rv = new EglContext(this, _egl, ctx, surf);
rv.MakeCurrent(null);
return rv;
@ -130,14 +119,14 @@ namespace Avalonia.OpenGL
public void ClearContext()
{
if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public EglSurface CreateWindowSurface(IntPtr window)
{
var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE});
if (s == IntPtr.Zero)
throw new OpenGlException("eglCreateWindowSurface failed");
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl);
return new EglSurface(this, _egl, s);
}

21
src/Avalonia.OpenGL/EglErrors.cs

@ -0,0 +1,21 @@
namespace Avalonia.OpenGL
{
public enum EglErrors
{
EGL_SUCCESS = EglConsts.EGL_SUCCESS,
EGL_NOT_INITIALIZED = EglConsts.EGL_NOT_INITIALIZED,
EGL_BAD_ACCESS = EglConsts.EGL_BAD_ACCESS,
EGL_BAD_ALLOC = EglConsts.EGL_BAD_ALLOC,
EGL_BAD_ATTRIBUTE = EglConsts.EGL_BAD_ATTRIBUTE,
EGL_BAD_CONTEXT = EglConsts.EGL_BAD_CONTEXT,
EGL_BAD_CONFIG = EglConsts.EGL_BAD_CONFIG,
EGL_BAD_CURRENT_SURFACE = EglConsts.EGL_BAD_CURRENT_SURFACE,
EGL_BAD_DISPLAY = EglConsts.EGL_BAD_DISPLAY,
EGL_BAD_SURFACE = EglConsts.EGL_BAD_SURFACE,
EGL_BAD_MATCH = EglConsts.EGL_BAD_MATCH,
EGL_BAD_PARAMETER = EglConsts.EGL_BAD_PARAMETER,
EGL_BAD_NATIVE_PIXMAP = EglConsts.EGL_BAD_NATIVE_PIXMAP,
EGL_BAD_NATIVE_WINDOW = EglConsts.EGL_BAD_NATIVE_WINDOW,
EGL_CONTEXT_LOST = EglConsts.EGL_CONTEXT_LOST
}
}

33
src/Avalonia.OpenGL/EglInterface.cs

@ -1,7 +1,6 @@
using System;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL
{
@ -32,60 +31,64 @@ namespace Avalonia.OpenGL
var lib = dyn.LoadLibrary(library);
return (s, o) => dyn.GetProcAddress(lib, s, o);
}
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int EglGetError();
[GlEntryPoint("eglGetError")]
public EglGetError GetError { get; }
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
[EntryPoint("eglGetDisplay")]
[GlEntryPoint("eglGetDisplay")]
public EglGetDisplay GetDisplay { get; }
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
[EntryPoint("eglGetPlatformDisplayEXT", true)]
[GlEntryPoint("eglGetPlatformDisplayEXT", true)]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
[EntryPoint("eglInitialize")]
[GlEntryPoint("eglInitialize")]
public EglInitialize Initialize { get; }
public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
[EntryPoint("eglGetProcAddress")]
[GlEntryPoint("eglGetProcAddress")]
public EglGetProcAddress GetProcAddress { get; }
public delegate bool EglBindApi(int api);
[EntryPoint("eglBindAPI")]
[GlEntryPoint("eglBindAPI")]
public EglBindApi BindApi { get; }
public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[EntryPoint("eglChooseConfig")]
[GlEntryPoint("eglChooseConfig")]
public EglChooseConfig ChooseConfig { get; }
public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs);
[EntryPoint("eglCreateContext")]
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[EntryPoint("eglCreatePbufferSurface")]
[GlEntryPoint("eglCreatePbufferSurface")]
public EglCreatePBufferSurface CreatePBufferSurface { get; }
public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[EntryPoint("eglMakeCurrent")]
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[EntryPoint("eglDestroySurface")]
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
[EntryPoint("eglSwapBuffers")]
[GlEntryPoint("eglSwapBuffers")]
public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
public delegate IntPtr
EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[EntryPoint("eglCreateWindowSurface")]
[GlEntryPoint("eglCreateWindowSurface")]
public EglCreateWindowSurface CreateWindowSurface { get; }
public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[EntryPoint("eglGetConfigAttrib")]
[GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty

3
src/Avalonia.OpenGL/GlConsts.cs

@ -456,12 +456,15 @@ namespace Avalonia.OpenGL
public const int GL_RENDERER = 0x1F01;
public const int GL_VERSION = 0x1F02;
public const int GL_EXTENSIONS = 0x1F03;
public const int GL_NO_ERROR = 0;
public const int GL_INVALID_ENUM = 0x0500;
public const int GL_INVALID_VALUE = 0x0501;
public const int GL_INVALID_OPERATION = 0x0502;
public const int GL_STACK_OVERFLOW = 0x0503;
public const int GL_STACK_UNDERFLOW = 0x0504;
public const int GL_OUT_OF_MEMORY = 0x0505;
public const int GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506;
public const int GL_CONTEXT_LOST = 0x0507;
public const int GL_CURRENT_BIT = 0x00000001;
public const int GL_POINT_BIT = 0x00000002;
public const int GL_LINE_BIT = 0x00000004;

5
src/Avalonia.OpenGL/EntryPointAttribute.cs → src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,12 +2,13 @@ using System;
namespace Avalonia.OpenGL
{
class EntryPointAttribute : Attribute
[AttributeUsage(AttributeTargets.Property)]
public class GlEntryPointAttribute : Attribute
{
public string EntryPoint { get; }
public bool Optional { get; }
public EntryPointAttribute(string entryPoint, bool optional = false)
public GlEntryPointAttribute(string entryPoint, bool optional = false)
{
EntryPoint = entryPoint;
Optional = optional;

15
src/Avalonia.OpenGL/GlErrors.cs

@ -0,0 +1,15 @@
namespace Avalonia.OpenGL
{
public enum GlErrors
{
GL_NO_ERROR = GlConsts.GL_NO_ERROR,
GL_INVALID_ENUM = GlConsts.GL_INVALID_ENUM,
GL_INVALID_VALUE = GlConsts.GL_INVALID_VALUE,
GL_INVALID_OPERATION = GlConsts.GL_INVALID_OPERATION,
GL_INVALID_FRAMEBUFFER_OPERATION = GlConsts.GL_INVALID_FRAMEBUFFER_OPERATION,
GL_STACK_OVERFLOW = GlConsts.GL_STACK_OVERFLOW,
GL_STACK_UNDERFLOW = GlConsts.GL_STACK_UNDERFLOW,
GL_OUT_OF_MEMORY = GlConsts.GL_OUT_OF_MEMORY,
GL_CONTEXT_LOST = GlConsts.GL_CONTEXT_LOST
}
}

37
src/Avalonia.OpenGL/GlInterface.cs

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
@ -7,40 +8,56 @@ namespace Avalonia.OpenGL
public class GlInterface : GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public string Version { get; }
public GlInterface(Func<string, bool, IntPtr> getProcAddress) : base(getProcAddress)
{
_getProcAddress = getProcAddress;
var versionPtr = GetString(GlConsts.GL_VERSION);
if (versionPtr != IntPtr.Zero)
Version = Marshal.PtrToStringAnsi(versionPtr);
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
{
}
public static GlInterface FromNativeUtf8GetProcAddress(Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface(getProcAddress);
public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate void GlClearStencil(int s);
[EntryPoint("glClearStencil")]
[GlEntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; }
public delegate void GlClearColor(int r, int g, int b, int a);
[EntryPoint("glClearColor")]
[GlEntryPoint("glClearColor")]
public GlClearColor ClearColor { get; }
public delegate void GlClear(int bits);
[EntryPoint("glClear")]
[GlEntryPoint("glClear")]
public GlClear Clear { get; }
public delegate void GlViewport(int x, int y, int width, int height);
[EntryPoint("glViewport")]
[GlEntryPoint("glViewport")]
public GlViewport Viewport { get; }
[EntryPoint("glFlush")]
[GlEntryPoint("glFlush")]
public Action Flush { get; }
public delegate IntPtr GlGetString(int v);
[GlEntryPoint("glGetString")]
public GlGetString GetString { get; }
public delegate void GlGetIntegerv(int name, out int rv);
[EntryPoint("glGetIntegerv")]
[GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty

27
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,16 +1,20 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress)
{
_getProcAddress = getProcAddress;
foreach (var prop in this.GetType().GetProperties())
{
var a = prop.GetCustomAttribute<EntryPointAttribute>();
var a = prop.GetCustomAttribute<GlEntryPointAttribute>();
if (a != null)
{
var fieldName = $"<{prop.Name}>k__BackingField";
@ -24,5 +28,26 @@ namespace Avalonia.OpenGL
}
}
}
protected static Func<string, bool, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = func(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
};
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress))
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional);
}
}

30
src/Avalonia.OpenGL/OpenGlException.cs

@ -4,9 +4,39 @@ namespace Avalonia.OpenGL
{
public class OpenGlException : Exception
{
public int? ErrorCode { get; private set; }
public OpenGlException(string message) : base(message)
{
}
private OpenGlException(string message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}
public static OpenGlException GetFormattedException(string funcName, EglInterface egl)
{
return GetFormattedException(typeof(EglErrors), funcName, egl.GetError());
}
public static OpenGlException GetFormattedException(string funcName, GlInterface gl)
{
return GetFormattedException(typeof(GlErrors), funcName, gl.GetError());
}
private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode)
{
try
{
string errorName = Enum.GetName(consts, errorCode);
return new OpenGlException(
$"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode);
}
catch (ArgumentException)
{
return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", errorCode);
}
}
}
}

6
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -29,8 +29,8 @@ namespace Avalonia
{
var windowLoaded = Observable
.FromEventPattern(
x => window.Initialized += x,
x => window.Initialized -= x)
x => window.Opened += x,
x => window.Opened -= x)
.Select(args => true);
var windowUnloaded = Observable
.FromEventPattern(
@ -59,4 +59,4 @@ namespace Avalonia
.DistinctUntilChanged();
}
}
}
}

4
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -16,7 +16,7 @@ namespace Avalonia
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
{
public static readonly AvaloniaProperty<TViewModel> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel>(nameof(ViewModel));
.Register<ReactiveUserControl<TViewModel>, TViewModel>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveUserControl{TViewModel}"/> class.
@ -41,4 +41,4 @@ namespace Avalonia
set => ViewModel = (TViewModel)value;
}
}
}
}

72
src/Avalonia.Styling/Styling/NotSelector.cs

@ -0,0 +1,72 @@
// 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.Styling
{
/// <summary>
/// The `:not()` style selector.
/// </summary>
internal class NotSelector : Selector
{
private readonly Selector _previous;
private readonly Selector _argument;
private string _selectorString;
/// <summary>
/// Initializes a new instance of the <see cref="NotSelector"/> class.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
public NotSelector(Selector previous, Selector argument)
{
_previous = previous;
_argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
}
/// <inheritdoc/>
public override bool InTemplate => _argument.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <inheritdoc/>
public override Type TargetType => _previous?.TargetType;
/// <inheritdoc/>
public override string ToString()
{
if (_selectorString == null)
{
_selectorString = ":not(" + _argument.ToString() + ")";
}
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
var innerResult = _argument.Match(control, subscribe);
switch (innerResult.Result)
{
case SelectorMatchResult.AlwaysThisInstance:
return SelectorMatch.NeverThisInstance;
case SelectorMatchResult.AlwaysThisType:
return SelectorMatch.NeverThisType;
case SelectorMatchResult.NeverThisInstance:
return SelectorMatch.AlwaysThisInstance;
case SelectorMatchResult.NeverThisType:
return SelectorMatch.AlwaysThisType;
case SelectorMatchResult.Sometimes:
return new SelectorMatch(innerResult.Activator.Select(x => !x));
default:
throw new InvalidOperationException("Invalid SelectorMatchResult.");
}
}
protected override Selector MovePrevious() => _previous;
}
}

11
src/Avalonia.Styling/Styling/Selectors.cs

@ -94,6 +94,17 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Returns a selector which inverts the results of selector argument.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Func<Selector, Selector> argument)
{
return new NotSelector(previous, argument(null));
}
/// <summary>
/// Returns a selector which matches a type.
/// </summary>

18
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -116,11 +116,9 @@ namespace Avalonia.Styling
}
}
if (Name != null)
if (Name != null && control.Name != Name)
{
return control.Name == Name ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
return SelectorMatch.NeverThisInstance;
}
if (_classes.IsValueCreated && _classes.Value.Count > 0)
@ -130,17 +128,13 @@ namespace Avalonia.Styling
var observable = new ClassObserver(control.Classes, _classes.Value);
return new SelectorMatch(observable);
}
else
else if (!Matches(control.Classes))
{
return Matches(control.Classes) ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
return SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.AlwaysThisType;
}
return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
}
protected override Selector MovePrevious() => _previous;

9
src/Avalonia.Themes.Default/CheckBox.xaml

@ -1,9 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="CheckBox">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4,0,0,0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Template">
@ -38,17 +39,15 @@
TextBlock.Foreground="{TemplateBinding Foreground}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}"
Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CheckBox:invalid /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style>

2
src/Avalonia.Themes.Default/TabControl.xaml

@ -18,7 +18,7 @@
MemberSelector="{TemplateBinding MemberSelector}" >
</ItemsPresenter>
<ContentPresenter
Name="PART_Content"
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

4
src/Avalonia.Themes.Default/TextBox.xaml

@ -23,7 +23,7 @@
Path="UseFloatingWatermark"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="Text"
Converter="{x:Static StringConverters.NotNullOrEmpty}"/>
Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/>
</MultiBinding>
</TextBlock.IsVisible>
</TextBlock>
@ -36,7 +36,7 @@
<TextBlock Name="watermark"
Opacity="0.5"
Text="{TemplateBinding Watermark}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"

7
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
{
@ -51,6 +52,12 @@ namespace Avalonia.Media
/// </summary>
public static FontFamily Default => new FontFamily(String.Empty);
/// <summary>
/// Represents all font families in the system. This can be an expensive call depending on platform implementation.
/// </summary>
public static IEnumerable<FontFamily> SystemFontFamilies =>
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().InstalledFontNames.Select(name => new FontFamily(name));
/// <summary>
/// Gets the primary family name of the font family.
/// </summary>

2
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -75,7 +75,7 @@ namespace Avalonia.Media.Imaging
public Vector Dpi => PlatformImpl.Item.Dpi;
/// <inheritdoc/>
public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi);
public Size Size => PlatformImpl.Item.PixelSize.ToSizeWithDpi(Dpi);
/// <inheritdoc/>
public PixelSize PixelSize => PlatformImpl.Item.PixelSize;

201
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
{
/// <summary>
/// Represents a point in device pixels.
/// </summary>
public readonly struct PixelPoint
{
/// <summary>
/// A point representing 0,0.
/// </summary>
public static readonly PixelPoint Origin = new PixelPoint(0, 0);
/// <summary>
/// Initializes a new instance of the <see cref="PixelPoint"/> structure.
/// </summary>
/// <param name="x">The X co-ordinate.</param>
/// <param name="y">The Y co-ordinate.</param>
public PixelPoint(int x, int y)
{
X = x;
Y = y;
}
/// <summary>
/// Gets the X co-ordinate.
/// </summary>
public int X { get; }
/// <summary>
/// Gets the Y co-ordinate.
/// </summary>
public int Y { get; }
/// <summary>
/// Checks for equality between two <see cref="PixelPoint"/>s.
/// </summary>
/// <param name="left">The first point.</param>
/// <param name="right">The second point.</param>
/// <returns>True if the points are equal; otherwise false.</returns>
public static bool operator ==(PixelPoint left, PixelPoint right)
{
return left.X == right.X && left.Y == right.Y;
}
/// <summary>
/// Checks for inequality between two <see cref="PixelPoint"/>s.
/// </summary>
/// <param name="left">The first point.</param>
/// <param name="right">The second point.</param>
/// <returns>True if the points are unequal; otherwise false.</returns>
public static bool operator !=(PixelPoint left, PixelPoint right)
{
return !(left == right);
}
/// <summary>
/// Parses a <see cref="PixelPoint"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="PixelPoint"/>.</returns>
public static PixelPoint Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
{
return new PixelPoint(
tokenizer.ReadInt32(),
tokenizer.ReadInt32());
}
}
/// <summary>
/// Checks for equality between a point and an object.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// True if <paramref name="obj"/> is a point that equals the current point.
/// </returns>
public override bool Equals(object obj)
{
if (obj is PixelPoint other)
{
return this == other;
}
return false;
}
/// <summary>
/// Returns a hash code for a <see cref="PixelPoint"/>.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + X.GetHashCode();
hash = (hash * 23) + Y.GetHashCode();
return hash;
}
}
/// <summary>
/// Returns a new <see cref="PixelPoint"/> with the same Y co-ordinate and the specified X co-ordinate.
/// </summary>
/// <param name="x">The X co-ordinate.</param>
/// <returns>The new <see cref="PixelPoint"/>.</returns>
public PixelPoint WithX(int x) => new PixelPoint(x, Y);
/// <summary>
/// Returns a new <see cref="PixelPoint"/> with the same X co-ordinate and the specified Y co-ordinate.
/// </summary>
/// <param name="y">The Y co-ordinate.</param>
/// <returns>The new <see cref="PixelPoint"/>.</returns>
public PixelPoint WithY(int y) => new PixelPoint(X, y);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public Point ToPoint(double scale) => new Point(X / scale, Y / scale);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint(
(int)(point.X * scale),
(int)(point.Y * scale));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint(
(int)(point.X * scale.X),
(int)(point.Y * scale.Y));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="point">The point.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96);
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="point">The point.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Returns the string representation of the point.
/// </summary>
/// <returns>The string representation of the point.</returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y);
}
}
}

436
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
{
/// <summary>
/// Represents a rectangle in device pixels.
/// </summary>
public readonly struct PixelRect
{
/// <summary>
/// An empty rectangle.
/// </summary>
public static readonly PixelRect Empty = default;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="x">The X position.</param>
/// <param name="y">The Y position.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public PixelRect(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="size">The size of the rectangle.</param>
public PixelRect(PixelSize size)
{
X = 0;
Y = 0;
Width = size.Width;
Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="position">The position of the rectangle.</param>
/// <param name="size">The size of the rectangle.</param>
public PixelRect(PixelPoint position, PixelSize size)
{
X = position.X;
Y = position.Y;
Width = size.Width;
Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="topLeft">The top left position of the rectangle.</param>
/// <param name="bottomRight">The bottom right position of the rectangle.</param>
public PixelRect(PixelPoint topLeft, PixelPoint bottomRight)
{
X = topLeft.X;
Y = topLeft.Y;
Width = bottomRight.X - topLeft.X;
Height = bottomRight.Y - topLeft.Y;
}
/// <summary>
/// Gets the X position.
/// </summary>
public int X { get; }
/// <summary>
/// Gets the Y position.
/// </summary>
public int Y { get; }
/// <summary>
/// Gets the width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the position of the rectangle.
/// </summary>
public PixelPoint Position => new PixelPoint(X, Y);
/// <summary>
/// Gets the size of the rectangle.
/// </summary>
public PixelSize Size => new PixelSize(Width, Height);
/// <summary>
/// Gets the right position of the rectangle.
/// </summary>
public int Right => X + Width;
/// <summary>
/// Gets the bottom position of the rectangle.
/// </summary>
public int Bottom => Y + Height;
/// <summary>
/// Gets the top left point of the rectangle.
/// </summary>
public PixelPoint TopLeft => new PixelPoint(X, Y);
/// <summary>
/// Gets the top right point of the rectangle.
/// </summary>
public PixelPoint TopRight => new PixelPoint(Right, Y);
/// <summary>
/// Gets the bottom left point of the rectangle.
/// </summary>
public PixelPoint BottomLeft => new PixelPoint(X, Bottom);
/// <summary>
/// Gets the bottom right point of the rectangle.
/// </summary>
public PixelPoint BottomRight => new PixelPoint(Right, Bottom);
/// <summary>
/// Gets the center point of the rectangle.
/// </summary>
public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
/// <summary>
/// Gets a value that indicates whether the rectangle is empty.
/// </summary>
public bool IsEmpty => Width == 0 && Height == 0;
/// <summary>
/// Checks for equality between two <see cref="PixelRect"/>s.
/// </summary>
/// <param name="left">The first rect.</param>
/// <param name="right">The second rect.</param>
/// <returns>True if the rects are equal; otherwise false.</returns>
public static bool operator ==(PixelRect left, PixelRect right)
{
return left.Position == right.Position && left.Size == right.Size;
}
/// <summary>
/// Checks for inequality between two <see cref="PixelRect"/>s.
/// </summary>
/// <param name="left">The first rect.</param>
/// <param name="right">The second rect.</param>
/// <returns>True if the rects are unequal; otherwise false.</returns>
public static bool operator !=(PixelRect left, PixelRect right)
{
return !(left == right);
}
/// <summary>
/// Determines whether a point in in the bounds of the rectangle.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool Contains(PixelPoint p)
{
return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.
/// </summary>
/// <param name="r">The rectangle.</param>
/// <returns>true if the rectangle is fully contained; otherwise false.</returns>
public bool Contains(PixelRect r)
{
return Contains(r.TopLeft) && Contains(r.BottomRight);
}
/// <summary>
/// Centers another rectangle in this rectangle.
/// </summary>
/// <param name="rect">The rectangle to center.</param>
/// <returns>The centered rectangle.</returns>
public PixelRect CenterRect(PixelRect rect)
{
return new PixelRect(
X + ((Width - rect.Width) / 2),
Y + ((Height - rect.Height) / 2),
rect.Width,
rect.Height);
}
/// <summary>
/// Returns a boolean indicating whether the given object is equal to this rectangle.
/// </summary>
/// <param name="obj">The object to compare against.</param>
/// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is PixelRect other)
{
return this == other;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
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;
}
}
/// <summary>
/// Gets the intersection of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The intersection.</returns>
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;
}
}
/// <summary>
/// Determines whether a rectangle intersects with this rectangle.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>
/// True if the specified rectangle intersects with this one; otherwise false.
/// </returns>
public bool Intersects(PixelRect rect)
{
return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
}
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
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));
}
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified X position.
/// </summary>
/// <param name="x">The x position.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithX(int x)
{
return new PixelRect(x, Y, Width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified Y position.
/// </summary>
/// <param name="y">The y position.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithY(int y)
{
return new PixelRect(X, y, Width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified width.
/// </summary>
/// <param name="width">The width.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithWidth(int width)
{
return new PixelRect(X, Y, width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified height.
/// </summary>
/// <param name="height">The height.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithHeight(int height)
{
return new PixelRect(X, Y, Width, Height);
}
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public static PixelRect FromRect(Rect rect, double scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
/// <summary>
/// Returns the string representation of the rectangle.
/// </summary>
/// <returns>The string representation of the rectangle.</returns>
public override string ToString()
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}, {1}, {2}, {3}",
X,
Y,
Width,
Height);
}
/// <summary>
/// Parses a <see cref="PixelRect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The parsed <see cref="PixelRect"/>.</returns>
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()
);
}
}
}
}

50
src/Avalonia.Visuals/Media/PixelSize.cs

@ -72,7 +72,7 @@ namespace Avalonia
/// <returns>The <see cref="PixelSize"/>.</returns>
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
/// <returns>The new <see cref="PixelSize"/>.</returns>
public PixelSize WithHeight(int height) => new PixelSize(Width, height);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(double scale) => new Size(Width / scale, Height / scale);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(Vector scale) => new Size(Width / scale.X, Height / scale.Y);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96));
public Size ToSizeWithDpi(double dpi) => ToSize(dpi / 96);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
@ -140,7 +156,27 @@ namespace Avalonia
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
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));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, double scale) => new PixelSize(
(int)Math.Ceiling(size.Width * scale),
(int)Math.Ceiling(size.Height * scale));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, Vector scale) => new PixelSize(
(int)Math.Ceiling(size.Width * scale.X),
(int)Math.Ceiling(size.Height * scale.Y));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
@ -148,9 +184,7 @@ namespace Avalonia
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
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);
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
@ -158,9 +192,7 @@ namespace Avalonia
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
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));
/// <summary>
/// Returns the string representation of the size.

5
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -13,6 +13,11 @@ namespace Avalonia.Platform
/// </summary>
public interface IPlatformRenderInterface
{
/// <summary>
/// Get all installed fonts in the system
/// </summary>
IEnumerable<string> InstalledFontNames { get; }
/// <summary>
/// Creates a formatted text implementation.
/// </summary>

80
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> _scene;
private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay;
@ -99,6 +100,9 @@ namespace Avalonia.Rendering
/// </summary>
public string DebugFramesPath { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Gets the render layers.
/// </summary>
@ -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;
}
}
/// <inheritdoc/>
@ -152,7 +175,8 @@ namespace Avalonia.Rendering
var t = (IRenderLoopTask)this;
if(t.NeedsUpdate)
UpdateScene();
Render(true);
if(_scene?.Item != null)
Render(true);
}
/// <inheritdoc/>
@ -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

8
src/Avalonia.Visuals/Rendering/IRenderRoot.cs

@ -41,15 +41,15 @@ namespace Avalonia.Rendering
/// <summary>
/// Converts a point from screen to client coordinates.
/// </summary>
/// <param name="point">The point in screen coordinates.</param>
/// <param name="point">The point in screen device coordinates.</param>
/// <returns>The point in client coordinates.</returns>
Point PointToClient(Point point);
Point PointToClient(PixelPoint point);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
Point PointToScreen(Point point);
/// <returns>The point in screen device coordinates.</returns>
PixelPoint PointToScreen(Point point);
}
}

13
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -18,11 +18,20 @@ namespace Avalonia.Rendering
bool DrawFps { get; set; }
/// <summary>
/// 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.
/// </summary>
bool DrawDirtyRects { get; set; }
/// <summary>
/// Raised when a portion of the scene has been invalidated.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Mark a visual as dirty and needing re-rendering.
/// </summary>
@ -63,4 +72,4 @@ namespace Avalonia.Rendering
/// </summary>
void Stop();
}
}
}

5
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -42,6 +42,9 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public bool DrawDirtyRects { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <inheritdoc/>
public void Paint(Rect rect)
{
@ -81,6 +84,8 @@ namespace Avalonia.Rendering
_renderTarget.Dispose();
_renderTarget = null;
}
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
/// <inheritdoc/>

12
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<IRenderTargetBitmapImpl> 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;
}
}
}

6
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++;
}
}

4
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> 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;
}

36
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
{
/// <summary>
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
/// </summary>
public class SceneInvalidatedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
/// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
{
RenderRoot = root;
DirtyRect = dirtyRect;
}
/// <summary>
/// Gets the invalidated area.
/// </summary>
public Rect DirtyRect { get; }
/// <summary>
/// Gets the render root that has been invalidated.
/// </summary>
public IRenderRoot RenderRoot { get; }
}
}

10
src/Avalonia.Visuals/VisualExtensions.cs

@ -18,10 +18,12 @@ namespace Avalonia
/// <param name="visual">The visual.</param>
/// <param name="point">The point in screen coordinates.</param>
/// <returns>The point in client coordinates.</returns>
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);
}
/// <summary>
@ -30,7 +32,7 @@ namespace Avalonia
/// <param name="visual">The visual.</param>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
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);

14
src/Avalonia.X11/Avalonia.X11.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
</Project>

91
src/Avalonia.X11/Glx/Glx.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace Avalonia.X11.Glx
{
unsafe class GlxInterface : GlInterfaceBase
{
private const string libGL = "libGL.so.1";
[GlEntryPointAttribute("glXMakeContextCurrent")]
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
[GlEntryPointAttribute("glXCreateContext")]
public GlxCreateContext CreateContext { get; }
public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GlEntryPointAttribute("glXCreateContextAttribsARB")]
public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
bool direct, int[] attribs);
[DllImport(libGL, EntryPoint = "glXGetProcAddress")]
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
[GlEntryPointAttribute("glXDestroyContext")]
public GlxDestroyContext DestroyContext { get; }
public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
[GlEntryPointAttribute("glXChooseFBConfig")]
public GlxChooseFBConfig ChooseFBConfig { get; }
public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
{
var arr = attribs.Concat(new[]{0}).ToArray();
return ChooseFBConfig(dpy, screen, arr, out nelements);
}
[GlEntryPointAttribute("glXGetVisualFromFBConfig")]
public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[GlEntryPointAttribute("glXGetFBConfigAttrib")]
public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[GlEntryPointAttribute("glXSwapBuffers")]
public GlxSwapBuffers SwapBuffers { get; }
public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
[GlEntryPointAttribute("glXWaitX")]
public GlxWaitX WaitX { get; }
public delegate void GlxWaitX();
[GlEntryPointAttribute("glXWaitGL")]
public GlxWaitGL WaitGL { get; }
public delegate void GlxWaitGL();
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public GlxInterface() : base(GlxGetProcAddress)
{
}
}
}

106
src/Avalonia.X11/Glx/GlxConsts.cs

@ -0,0 +1,106 @@
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
// ReSharper disable UnusedMember.Global
#pragma warning disable 414
namespace Avalonia.X11.Glx
{
class GlxConsts
{
public const int GLX_USE_GL = 1;
public const int GLX_BUFFER_SIZE = 2;
public const int GLX_LEVEL = 3;
public const int GLX_RGBA = 4;
public const int GLX_DOUBLEBUFFER = 5;
public const int GLX_STEREO = 6;
public const int GLX_AUX_BUFFERS = 7;
public const int GLX_RED_SIZE = 8;
public const int GLX_GREEN_SIZE = 9;
public const int GLX_BLUE_SIZE = 10;
public const int GLX_ALPHA_SIZE = 11;
public const int GLX_DEPTH_SIZE = 12;
public const int GLX_STENCIL_SIZE = 13;
public const int GLX_ACCUM_RED_SIZE = 14;
public const int GLX_ACCUM_GREEN_SIZE = 15;
public const int GLX_ACCUM_BLUE_SIZE = 16;
public const int GLX_ACCUM_ALPHA_SIZE = 17;
public const int GLX_BAD_SCREEN = 1;
public const int GLX_BAD_ATTRIBUTE = 2;
public const int GLX_NO_EXTENSION = 3;
public const int GLX_BAD_VISUAL = 4;
public const int GLX_BAD_CONTEXT = 5;
public const int GLX_BAD_VALUE = 6;
public const int GLX_BAD_ENUM = 7;
public const int GLX_VENDOR = 1;
public const int GLX_VERSION = 2;
public const int GLX_EXTENSIONS= 3;
public const int GLX_CONFIG_CAVEAT = 0x20;
public const int GLX_DONT_CARE = unchecked((int)0xFFFFFFFF);
public const int GLX_X_VISUAL_TYPE = 0x22;
public const int GLX_TRANSPARENT_TYPE = 0x23;
public const int GLX_TRANSPARENT_INDEX_VALUE = 0x24;
public const int GLX_TRANSPARENT_RED_VALUE = 0x25;
public const int GLX_TRANSPARENT_GREEN_VALUE = 0x26;
public const int GLX_TRANSPARENT_BLUE_VALUE = 0x27;
public const int GLX_TRANSPARENT_ALPHA_VALUE = 0x28;
public const int GLX_WINDOW_BIT = 0x00000001;
public const int GLX_PIXMAP_BIT = 0x00000002;
public const int GLX_PBUFFER_BIT = 0x00000004;
public const int GLX_AUX_BUFFERS_BIT = 0x00000010;
public const int GLX_FRONT_LEFT_BUFFER_BIT = 0x00000001;
public const int GLX_FRONT_RIGHT_BUFFER_BIT = 0x00000002;
public const int GLX_BACK_LEFT_BUFFER_BIT = 0x00000004;
public const int GLX_BACK_RIGHT_BUFFER_BIT = 0x00000008;
public const int GLX_DEPTH_BUFFER_BIT = 0x00000020;
public const int GLX_STENCIL_BUFFER_BIT = 0x00000040;
public const int GLX_ACCUM_BUFFER_BIT = 0x00000080;
public const int GLX_NONE = 0x8000;
public const int GLX_SLOW_CONFIG = 0x8001;
public const int GLX_TRUE_COLOR = 0x8002;
public const int GLX_DIRECT_COLOR = 0x8003;
public const int GLX_PSEUDO_COLOR = 0x8004;
public const int GLX_STATIC_COLOR = 0x8005;
public const int GLX_GRAY_SCALE = 0x8006;
public const int GLX_STATIC_GRAY = 0x8007;
public const int GLX_TRANSPARENT_RGB = 0x8008;
public const int GLX_TRANSPARENT_INDEX = 0x8009;
public const int GLX_VISUAL_ID = 0x800B;
public const int GLX_SCREEN = 0x800C;
public const int GLX_NON_CONFORMANT_CONFIG = 0x800D;
public const int GLX_DRAWABLE_TYPE = 0x8010;
public const int GLX_RENDER_TYPE = 0x8011;
public const int GLX_X_RENDERABLE = 0x8012;
public const int GLX_FBCONFIG_ID = 0x8013;
public const int GLX_RGBA_TYPE = 0x8014;
public const int GLX_COLOR_INDEX_TYPE = 0x8015;
public const int GLX_MAX_PBUFFER_WIDTH = 0x8016;
public const int GLX_MAX_PBUFFER_HEIGHT = 0x8017;
public const int GLX_MAX_PBUFFER_PIXELS = 0x8018;
public const int GLX_PRESERVED_CONTENTS = 0x801B;
public const int GLX_LARGEST_PBUFFER = 0x801C;
public const int GLX_WIDTH = 0x801D;
public const int GLX_HEIGHT = 0x801E;
public const int GLX_EVENT_MASK = 0x801F;
public const int GLX_DAMAGED = 0x8020;
public const int GLX_SAVED = 0x8021;
public const int GLX_WINDOW = 0x8022;
public const int GLX_PBUFFER = 0x8023;
public const int GLX_PBUFFER_HEIGHT = 0x8040;
public const int GLX_PBUFFER_WIDTH = 0x8041;
public const int GLX_RGBA_BIT = 0x00000001;
public const int GLX_COLOR_INDEX_BIT = 0x00000002;
public const int GLX_PBUFFER_CLOBBER_MASK = 0x08000000;
public const int GLX_SAMPLE_BUFFERS = 0x186a0 /*100000*/;
public const int GLX_SAMPLES = 0x186a1 /*100001*/;
public const int GLX_PbufferClobber = 0;
public const int GLX_BufferSwapComplete = 1;
public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
public const int GLX_CONTEXT_FLAGS_ARB = 0x2094;
public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
}
}

41
src/Avalonia.X11/Glx/GlxContext.cs

@ -0,0 +1,41 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxContext : IGlContext
{
public IntPtr Handle { get; }
public GlxInterface Glx { get; }
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly object _lock = new object();
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
{
Handle = handle;
Glx = glx;
_x11 = x11;
_defaultXid = defaultXid;
Display = display;
}
public GlxDisplay Display { get; }
IGlDisplay IGlContext.Display => Display;
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent() => MakeCurrent(_defaultXid);
public void MakeCurrent(IntPtr xid)
{
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
throw new OpenGlException("glXMakeContextCurrent failed ");
}
}
}

136
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -0,0 +1,136 @@
using System;
using System.Linq;
using Avalonia.OpenGL;
using static Avalonia.X11.Glx.GlxConsts;
namespace Avalonia.X11.Glx
{
unsafe class GlxDisplay : IGlDisplay
{
private readonly X11Info _x11;
private readonly IntPtr _fbconfig;
private readonly XVisualInfo* _visual;
public GlDisplayType Type => GlDisplayType.OpenGL2;
public GlInterface GlInterface { get; }
public XVisualInfo* VisualInfo => _visual;
public int SampleCount { get; }
public int StencilSize { get; }
public GlxContext ImmediateContext { get; }
public GlxContext DeferredContext { get; }
public GlxInterface Glx { get; } = new GlxInterface();
public GlxDisplay(X11Info x11)
{
_x11 = x11;
var baseAttribs = new[]
{
GLX_X_RENDERABLE, 1,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT,
GLX_DOUBLEBUFFER, 1,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 1,
GLX_STENCIL_SIZE, 8,
};
foreach (var attribs in new[]
{
//baseAttribs.Concat(multiattribs),
baseAttribs,
})
{
var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen,
attribs, out var count);
for (var c = 0 ; c < count; c++)
{
var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]);
// We prefer 32 bit visuals
if (_fbconfig == IntPtr.Zero || visual->depth == 32)
{
_fbconfig = ptr[c];
_visual = visual;
if(visual->depth == 32)
break;
}
}
if (_fbconfig != IntPtr.Zero)
break;
}
if (_fbconfig == IntPtr.Zero)
throw new OpenGlException("Unable to choose FBConfig");
if (_visual == null)
throw new OpenGlException("Unable to get visual info from FBConfig");
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
SampleCount = samples;
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
StencilSize = stencil;
var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[]
{
GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0
})).ToList();
XLib.XFlush(_x11.Display);
ImmediateContext = CreateContext(pbuffers[0],null);
DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
}
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
IntPtr handle = default;
foreach (var ver in new[]
{
new Version(4, 0), new Version(3, 2),
new Version(3, 0), new Version(2, 0)
})
{
var attrs = new[]
{
GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major,
GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor,
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
try
{
handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs);
if (handle != IntPtr.Zero)
break;
}
catch
{
break;
}
}
if (handle == IntPtr.Zero)
throw new OpenGlException("Unable to create direct GLX context");
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
}
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);
}
}

86
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -0,0 +1,86 @@
using System;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxGlPlatformSurface: IGlPlatformSurface
{
private readonly GlxDisplay _display;
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_display = display;
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
return new RenderTarget(_context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public RenderTarget(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
}
public void Dispose()
{
// No-op
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var l = _context.Lock();
try
{
_context.MakeCurrent(_info.Handle);
return new Session(_context, _info, l);
}
catch
{
l.Dispose();
throw;
}
}
class Session : IGlPlatformSurfaceRenderingSession
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IDisposable _lock;
public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
{
_context = context;
_info = info;
_lock = @lock;
}
public void Dispose()
{
_context.Display.GlInterface.Flush();
_context.Glx.WaitGL();
_context.Display.SwapBuffers(_info.Handle);
_context.Glx.WaitX();
_context.Display.ClearContext();
_lock.Dispose();
}
public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
}
}
}
}

44
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -0,0 +1,44 @@
using System;
using Avalonia.Logging;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxGlPlatformFeature : IWindowingPlatformGlFeature
{
public GlxDisplay Display { get; private set; }
public IGlContext ImmediateContext { get; private set; }
public GlxContext DeferredContext { get; private set; }
public static bool TryInitialize(X11Info x11)
{
var feature = TryCreate(x11);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature);
return true;
}
return false;
}
public static GlxGlPlatformFeature TryCreate(X11Info x11)
{
try
{
var disp = new GlxDisplay(x11);
return new GlxGlPlatformFeature
{
Display = disp,
ImmediateContext = disp.ImmediateContext,
DeferredContext = disp.DeferredContext
};
}
catch(Exception e)
{
Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e);
return null;
}
}
}
}

2108
src/Avalonia.X11/Keysyms.cs

File diff suppressed because it is too large

17
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);
}
}

207
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]);
}
}
}

234
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<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _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<IntPtr[]> SendFormatRequest()
{
if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, _handle,
IntPtr.Zero);
return _requestedFormatsTcs.Task;
}
Task<string> SendTextRequest(IntPtr format)
{
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedTextTcs.Task;
}
public async Task<string> 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);
}
}
}

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

Loading…
Cancel
Save