Browse Source

merge release/0.10.13 Squashed commit of the following:

commit 345ff4417e
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Wed Mar 2 18:31:16 2022 +0000

    bump version.

commit 892960c83d
Author: Max Katz <maxkatz6@outlook.com>
Date:   Wed Mar 2 14:18:06 2022 -0400

    Merge pull request #7736 from AvaloniaUI/fixes/mac-os-set-window-title-null

    allow setting the window title to null on osx.

commit d8848478ea
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Wed Mar 2 16:48:43 2022 +0000

    Merge pull request #7723 from AvaloniaUI/fix-expander

    Fix expander header stretching

commit 2eb51b8672
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Wed Mar 2 14:21:55 2022 +0000

    Merge pull request #7730 from AvaloniaUI/fixes/7582-centerowner-minimized

    Fall back from CenterOwner to CenterScreen when owner window is minimized.

commit 84f04f429b
Author: Max Katz <maxkatz6@outlook.com>
Date:   Sun Feb 20 14:31:19 2022 -0500

    Merge pull request #7660 from timunie/fix/gh-7636

    Add missing call to base class in ReactiveUserControl.OnDataContextChanged

commit 5b4f5da127
Author: Max Katz <maxkatz6@outlook.com>
Date:   Sun Feb 20 14:09:49 2022 -0500

    Merge pull request #7658 from trympet/7657-fix-brush-opacity-animation

    fix brush opacity animation

commit d5359603ac
Author: Max Katz <maxkatz6@outlook.com>
Date:   Fri Feb 18 20:18:16 2022 -0500

    Merge pull request #7645 from timunie/fix/CalendarDatePickerBindingMode

    Change default binding mode of SelectedDateProperty to TwoWay
    # Conflicts:
    #	src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

commit 480dfdfe0d
Author: Nikita Tsukanov <keks9n@gmail.com>
Date:   Wed Dec 29 16:26:59 2021 +0300

    Merge pull request #7259 from AvaloniaUI/features/use-external-microcom-generator

    Use microcom generator from nuget
    # Conflicts:
    #	build/MicroCom.targets

commit 3f11b014a6
Author: Max Katz <maxkatz6@outlook.com>
Date:   Wed Jan 26 15:21:00 2022 -0500

    Merge pull request #7440 from emmauss/diagnostic-key

    Ensure Control Inspection in Diagnostics tool window is triggered on Key Down

commit 870f62fb8f
Author: Tako <53405089+Takoooooo@users.noreply.github.com>
Date:   Sat Jan 29 17:35:18 2022 +0200

    Merge pull request #7449 from emmauss/fluent-compact

    Add DensityStyle property in Fluent Theme provider

commit 7d771c86e6
Author: Nikita Tsukanov <keks9n@gmail.com>
Date:   Fri Jan 28 12:38:54 2022 +0300

    Merge pull request #7455 from Mikolaytis/DeadlockFix

    [Deadlock] Fix Monitor.Enter in finally

commit 859793b122
Author: Max Katz <maxkatz6@outlook.com>
Date:   Sun Jan 30 18:46:45 2022 -0500

    Merge pull request #7475 from wieslawsoltes/ViewboxPageXamlOnly

    [ControlCatalog] Set ComboBox items from xaml on ViewboxPage

commit 256bba51f7
Author: Max Katz <maxkatz6@outlook.com>
Date:   Sun Jan 30 17:30:39 2022 -0500

    Merge pull request #7484 from wieslawsoltes/ClippingPageXamlOnly

    [RenderDemo] Set Border clip from xaml on ClippingPage

commit 039fa5b7ad
Author: Max Katz <maxkatz6@outlook.com>
Date:   Tue Feb 15 15:22:22 2022 -0500

    Merge pull request #7520 from timunie/fix/ScrollViewerShiftAndPointerWheel

    Fix [Shift] + [PointerWheel] should scroll horizontally

commit 2c53e06c26
Author: Olivier DALET <olivier.dalet@addupsolutions.com>
Date:   Fri Feb 4 17:55:12 2022 +0100

    Fix #7519 - Reset fb and depth buffer Ids once they are deleted

commit 8bc795be04
Author: Andrii Kurdiumov <kant2002@gmail.com>
Date:   Sat Feb 5 17:51:15 2022 +0600

    Fix AOT incompatible code (#7534)

    * Fix AOT incompatible code
    Use code patterns which are AOT-friendly. That improves R2R and Native AOT scenarios

commit 7b4462163a
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Mon Feb 7 22:39:02 2022 +0000

    Merge pull request #7537 from fr-Pursuit/master

    Minimization bugfix on Windows

commit 80319bf257
Author: Nikita Tsukanov <keks9n@gmail.com>
Date:   Mon Feb 7 22:13:51 2022 +0300

    Merge pull request #7548 from ahopper/fix-32bit-linux-session-manger-pinvoke

    fix 32 bit raspberry pi session manager seg fault

commit e89b6dcbc1
Author: Steve <hez2010@outlook.com>
Date:   Tue Feb 8 20:30:48 2022 +0800

    Fix COM issue in Cursor (#7551)

    Bump S.D.Common to 6.0.0 for non-netstandard2.0 builds

commit 4dc4c993de
Author: Dariusz Komosiński <darek.komosinski@gmail.com>
Date:   Wed Feb 9 14:41:07 2022 +0100

    Merge pull request #7569 from MarchingCube/win32-filepicker-no-exceptions

    Avoid using COM exceptions for dialog control flow.

commit 03bc5a4752
Author: Max Katz <maxkatz6@outlook.com>
Date:   Thu Feb 10 22:17:17 2022 -0500

    Merge pull request #7576 from pr8x/button-flyout-diagnostics

    DevTools: Enable inspection for Button.Flyout

commit bfff7d9e01
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Mon Feb 21 21:15:56 2022 +0000

    Merge pull request #7622 from pr8x/child-window-property2

    Exposing `Window.ChildWindows` collection

commit da3004d1d6
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Wed Feb 16 22:24:27 2022 +0000

    Merge pull request #7628 from wieslawsoltes/UpdateNuGetPackageDescription

    Update PackageDescription for NuGet

commit 4b6d1223db
Author: Max Katz <maxkatz6@outlook.com>
Date:   Thu Feb 17 10:22:03 2022 -0500

    Merge pull request #7634 from AvaloniaUI/fixes/7633-date-time-picker-popup

    Fix Date/Time picker popups
    # Conflicts:
    #	src/Avalonia.Controls/DateTimePickers/DatePicker.cs
    #	src/Avalonia.Controls/DateTimePickers/TimePicker.cs

commit 0419426061
Merge: 645ce6ada a9d683bb8
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Wed Feb 16 13:46:20 2022 +0000

    Merge branch 'stable/0.10.x' of https://github.com/AvaloniaUI/Avalonia into stable/0.10.x

commit 645ce6ada3
Author: Jumar Macato <16554748+jmacato@users.noreply.github.com>
Date:   Wed Feb 16 21:42:16 2022 +0800

    Merge pull request #7611 from AvaloniaUI/feature/transitioning-content-control

    add transitioning content control.
    # Conflicts:
    #	src/Avalonia.Themes.Default/DefaultTheme.xaml
    #	src/Avalonia.Themes.Default/TransitioningContentControl.xaml

commit a9d683bb8f
Author: Nikita Tsukanov <keks9n@gmail.com>
Date:   Sat Feb 12 15:25:42 2022 +0300

    Introduced RawPointerPoint for usage with IntermediatePoints (#7581)

    Introduced RawPointerPoint for usage with IntermediatePoints

commit 8b3b65496f
Author: Max Katz <maxkatz6@outlook.com>
Date:   Sun Jan 23 19:55:40 2022 -0500

    Merge pull request #7413 from AvaloniaUI/feature/intermediate-points

    Added GetIntermediatePoints support for X11, libinput and evdev
    # Conflicts:
    #	src/Avalonia.Base/Threading/JobRunner.cs

commit d22e627112
Author: Dan Walmsley <dan@walms.co.uk>
Date:   Tue Feb 15 17:45:22 2022 +0000

    Merge pull request #7605 from AvaloniaUI/feature/skia-layering-extensions

    Add Skia Helper Methods to allow applying Skia Filter Effects (Blur, DropShadow, Lighting) to DC content

# Conflicts:
#	build/SharedVersion.props
Andrey Kunchev 4 years ago
parent
commit
6351cfcda1
  1. 27
      Avalonia.sln
  2. 6
      azure-pipelines.yml
  3. 34
      build/MicroCom.targets
  4. 4
      build/SharedVersion.props
  5. 3
      build/System.Drawing.Common.props
  6. 8
      nukebuild/MicroComGen.cs
  7. 6
      nukebuild/_build.csproj
  8. 220
      samples/ControlCatalog/Pages/PointersPage.cs
  9. 22
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  10. 19
      samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
  11. 54
      samples/RenderDemo/Pages/ClippingPage.xaml
  12. 17
      samples/RenderDemo/Pages/ClippingPage.xaml.cs
  13. 7
      src/Avalonia.Base/Threading/Dispatcher.cs
  14. 17
      src/Avalonia.Base/Threading/JobRunner.cs
  15. 4
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  16. 18
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  17. 3
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  18. 19
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  19. 3
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  20. 96
      src/Avalonia.Controls/TransitioningContentControl.cs
  21. 20
      src/Avalonia.Controls/Window.cs
  22. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  23. 5
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  24. 19
      src/Avalonia.Input/MouseDevice.cs
  25. 45
      src/Avalonia.Input/PointerEventArgs.cs
  26. 2
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  27. 65
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  28. 2
      src/Avalonia.Input/TouchDevice.cs
  29. 6
      src/Avalonia.MicroCom/MicroComRuntime.cs
  30. 5
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  31. 5
      src/Avalonia.Native/Avalonia.Native.csproj
  32. 5
      src/Avalonia.Native/WindowImpl.cs
  33. 2
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  34. 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  35. 1
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  36. 1
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  37. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  38. 59
      src/Avalonia.Themes.Default/Expander.xaml
  39. 20
      src/Avalonia.Themes.Default/TransitioningContentControl.xaml
  40. 8
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  41. 1
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  42. 20
      src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml
  43. 48
      src/Avalonia.Themes.Fluent/FluentTheme.cs
  44. 6
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  45. 2
      src/Avalonia.Visuals/Animation/PageSlide.cs
  46. 1
      src/Avalonia.X11/Avalonia.X11.csproj
  47. 2
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  48. 16
      src/Avalonia.X11/SMLib.cs
  49. 21
      src/Avalonia.X11/X11PlatformLifetimeEvents.cs
  50. 39
      src/Avalonia.X11/X11Window.cs
  51. 6
      src/Directory.Build.props
  52. 1
      src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
  53. 38
      src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs
  54. 31
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  55. 43
      src/Linux/Avalonia.LinuxFramebuffer/Input/RawEventGroupingThreadingHelper.cs
  56. 10
      src/Shared/ModuleInitializer.cs
  57. 133
      src/Shared/RawEventGrouping.cs
  58. 71
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  59. 7
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  60. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  61. 31
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  62. 2
      src/Windows/Avalonia.Win32/Win32Com/win32.idl
  63. 10
      src/Windows/Avalonia.Win32/WindowImpl.cs
  64. 241
      src/tools/MicroComGenerator/Ast.cs
  65. 232
      src/tools/MicroComGenerator/AstParser.cs
  66. 484
      src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
  67. 111
      src/tools/MicroComGenerator/CSharpGen.Utils.cs
  68. 155
      src/tools/MicroComGenerator/CSharpGen.cs
  69. 119
      src/tools/MicroComGenerator/CppGen.cs
  70. 97
      src/tools/MicroComGenerator/Extensions.cs
  71. 10
      src/tools/MicroComGenerator/MicroComGenerator.csproj
  72. 27
      src/tools/MicroComGenerator/ParseException.cs
  73. 52
      src/tools/MicroComGenerator/Program.cs
  74. 417
      src/tools/MicroComGenerator/TokenParser.cs
  75. 54
      tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs

27
Avalonia.sln

@ -223,8 +223,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@ -2029,30 +2027,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2255,7 +2229,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}

6
azure-pipelines.yml

@ -58,8 +58,10 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
export PATH="`pwd`/sdk:$PATH"
cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
export COREHOST_TRACE=0
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5
inputs:

34
build/MicroCom.targets

@ -1,34 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Ensure that code generator is actually built -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
</ProjectReference>
</ItemGroup>
<Target Name="GenerateAvaloniaNativeComInterop"
BeforeTargets="CoreCompile"
DependsOnTargets="ResolveReferences"
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs"
Outputs="%(AvnComIdl.OutputFile)">
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" />
<Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll&quot; -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)"
LogStandardErrorAsError="true" />
<ItemGroup>
<!-- Remove and re-add generated file, this is needed for the clean build -->
<Compile Remove="%(AvnComIdl.OutputFile)"/>
<Compile Include="%(AvnComIdl.OutputFile)"/>
</ItemGroup>
</Target>
<ItemGroup>
<UpToDateCheckInput Include="@(AvnComIdl)"/>
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/>
</ItemGroup>
<PropertyGroup>
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" />
</Project>

4
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.10.12</Version>
<Version>0.10.13</Version>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
@ -11,7 +11,7 @@
<LangVersion>latest</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
<PackageDescription>Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.</PackageDescription>
<PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, MacOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>
<PackageTags>avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin</PackageTags>
<PackageReleaseNotes>https://github.com/AvaloniaUI/Avalonia/releases</PackageReleaseNotes>
<RepositoryType>git</RepositoryType>

3
build/System.Drawing.Common.props

@ -1,5 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
<PackageReference Condition="'$(TargetFramework)'!='netstandard2.0'" Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Condition="'$(TargetFramework)'=='netstandard2.0'" Include="System.Drawing.Common" Version="4.5.0" />
</ItemGroup>
</Project>

8
nukebuild/MicroComGen.cs

@ -1,14 +1,14 @@
using System.IO;
using MicroComGenerator;
using MicroCom.CodeGenerator;
using Nuke.Common;
partial class Build : NukeBuild
{
Target GenerateCppHeaders => _ => _.Executes(() =>
{
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl");
var ast = AstParser.Parse(text);
var file = MicroComCodeGenerator.Parse(
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
CppGen.GenerateCpp(ast));
file.GenerateCppHeader());
});
}

6
nukebuild/_build.csproj

@ -15,7 +15,7 @@
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
<PackageReference Include="Pharmacist.Core" Version="1.8.1" />
@ -38,10 +38,6 @@
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
<Compile Include="..\src\tools\MicroComGenerator\**\*.cs" Exclude="..\src\tools\MicroComGenerator\obj\**">
<Link>MicroComGenerator\%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\src\tools\MicroComGenerator\Program.cs" />
</ItemGroup>
</Project>

220
samples/ControlCatalog/Pages/PointersPage.cs

@ -1,15 +1,37 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace ControlCatalog.Pages
namespace ControlCatalog.Pages;
public class PointersPage : Decorator
{
public class PointersPage : Control
public PointersPage()
{
Child = new TabControl
{
Items = new[]
{
new TabItem() { Header = "Contacts", Content = new PointerContactsTab() },
new TabItem() { Header = "IntermediatePoints", Content = new PointerIntermediatePointsTab() }
}
};
}
class PointerContactsTab : Control
{
class PointerInfo
{
@ -45,7 +67,7 @@ namespace ControlCatalog.Pages
private Dictionary<IPointer, PointerInfo> _pointers = new Dictionary<IPointer, PointerInfo>();
public PointersPage()
public PointerContactsTab()
{
ClipToBounds = true;
}
@ -104,4 +126,196 @@ namespace ControlCatalog.Pages
}
}
}
public class PointerIntermediatePointsTab : Decorator
{
public PointerIntermediatePointsTab()
{
this[TextBlock.ForegroundProperty] = Brushes.Black;
var slider = new Slider
{
Margin = new Thickness(5),
Minimum = 0,
Maximum = 500
};
var status = new TextBlock()
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
};
Child = new Grid
{
Children =
{
new PointerCanvas(slider, status),
new Border
{
Background = Brushes.LightYellow,
Child = new StackPanel
{
Children =
{
new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new TextBlock { Text = "Thread sleep:" },
new TextBlock()
{
[!TextBlock.TextProperty] =slider.GetObservable(Slider.ValueProperty)
.Select(x=>x.ToString()).ToBinding()
}
}
},
slider
}
},
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Width = 300,
Height = 60
},
status
}
};
}
class PointerCanvas : Control
{
private readonly Slider _slider;
private readonly TextBlock _status;
private int _events;
private Stopwatch _stopwatch = Stopwatch.StartNew();
private Dictionary<int, PointerPoints> _pointers = new();
class PointerPoints
{
struct CanvasPoint
{
public IBrush Brush;
public Point Point;
public double Radius;
}
readonly CanvasPoint[] _points = new CanvasPoint[1000];
int _index;
public void Render(DrawingContext context)
{
CanvasPoint? prev = null;
for (var c = 0; c < _points.Length; c++)
{
var i = (c + _index) % _points.Length;
var pt = _points[i];
if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null)
context.DrawLine(new Pen(Brushes.Black), prev.Value.Point, pt.Point);
prev = pt;
if (pt.Brush != null)
context.DrawEllipse(pt.Brush, null, pt.Point, pt.Radius, pt.Radius);
}
}
void AddPoint(Point pt, IBrush brush, double radius)
{
_points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius };
_index = (_index + 1) % _points.Length;
}
public void HandleEvent(PointerEventArgs e, Visual v)
{
e.Handled = true;
if (e.RoutedEvent == PointerPressedEvent)
AddPoint(e.GetPosition(v), Brushes.Green, 10);
else if (e.RoutedEvent == PointerReleasedEvent)
AddPoint(e.GetPosition(v), Brushes.Red, 10);
else
{
var pts = e.GetIntermediatePoints(v);
for (var c = 0; c < pts.Count; c++)
{
var pt = pts[c];
AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black,
c == pts.Count - 1 ? 5 : 2);
}
}
}
}
public PointerCanvas(Slider slider, TextBlock status)
{
_slider = slider;
_status = status;
DispatcherTimer.Run(() =>
{
if (_stopwatch.Elapsed.TotalSeconds > 1)
{
_status.Text = "Events per second: " + (_events / _stopwatch.Elapsed.TotalSeconds);
_stopwatch.Restart();
_events = 0;
}
return this.GetVisualRoot() != null;
}, TimeSpan.FromMilliseconds(10));
}
void HandleEvent(PointerEventArgs e)
{
_events++;
Thread.Sleep((int)_slider.Value);
InvalidateVisual();
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)
{
_pointers.Remove(e.Pointer.Id);
return;
}
if (!_pointers.TryGetValue(e.Pointer.Id, out var pt))
_pointers[e.Pointer.Id] = pt = new PointerPoints();
pt.HandleEvent(e, this);
}
public override void Render(DrawingContext context)
{
context.FillRectangle(Brushes.White, Bounds);
foreach(var pt in _pointers.Values)
pt.Render(context);
base.Render(context);
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (e.ClickCount == 2)
{
_pointers.Clear();
InvalidateVisual();
return;
}
HandleEvent(e);
base.OnPointerPressed(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
HandleEvent(e);
base.OnPointerMoved(e);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
HandleEvent(e);
base.OnPointerReleased(e);
}
}
}
}

22
samples/ControlCatalog/Pages/ViewboxPage.xaml

@ -1,5 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=netstandard"
x:Class="ControlCatalog.Pages.ViewboxPage">
<Grid RowDefinitions="Auto,*,*">
@ -12,8 +13,8 @@
<Border HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="Orange" Width="200" Height="200">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="CornflowerBlue" Width="{Binding #WidthSlider.Value}" Height="{Binding #HeightSlider.Value}" >
<Viewbox
Stretch="{Binding #StretchSelector.SelectedItem}"
StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
Stretch="{Binding #StretchSelector.SelectedItem, FallbackValue={x:Static Stretch.Uniform}}"
StretchDirection="{Binding #StretchDirectionSelector.SelectedItem, FallbackValue={x:Static StretchDirection.Both}}">
<Ellipse Width="50" Height="50" Fill="CornflowerBlue" />
</Viewbox>
</Border>
@ -25,9 +26,22 @@
<TextBlock Text="Height" />
<Slider Minimum="10" Maximum="200" Value="100" x:Name="HeightSlider" TickFrequency="25" TickPlacement="TopLeft" />
<TextBlock Text="Stretch" />
<ComboBox x:Name="StretchSelector" HorizontalAlignment="Stretch" Margin="0,0,0,2" />
<ComboBox x:Name="StretchSelector" HorizontalAlignment="Stretch" SelectedIndex="0" Margin="0,0,0,2">
<collections:ArrayList>
<Stretch>Uniform</Stretch>
<Stretch>UniformToFill</Stretch>
<Stretch>Fill</Stretch>
<Stretch>None</Stretch>
</collections:ArrayList>
</ComboBox>
<TextBlock Text="Stretch Direction" />
<ComboBox x:Name="StretchDirectionSelector" HorizontalAlignment="Stretch" />
<ComboBox x:Name="StretchDirectionSelector" SelectedIndex="0" HorizontalAlignment="Stretch">
<collections:ArrayList>
<StretchDirection>Both</StretchDirection>
<StretchDirection>DownOnly</StretchDirection>
<StretchDirection>UpOnly</StretchDirection>
</collections:ArrayList>
</ComboBox>
</StackPanel>
</Grid>

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

@ -1,6 +1,5 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace ControlCatalog.Pages
{
@ -9,24 +8,6 @@ namespace ControlCatalog.Pages
public ViewboxPage()
{
InitializeComponent();
var stretchSelector = this.FindControl<ComboBox>("StretchSelector");
stretchSelector.Items = new[]
{
Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None
};
stretchSelector.SelectedIndex = 0;
var stretchDirectionSelector = this.FindControl<ComboBox>("StretchDirectionSelector");
stretchDirectionSelector.Items = new[]
{
StretchDirection.Both, StretchDirection.DownOnly, StretchDirection.UpOnly
};
stretchDirectionSelector.SelectedIndex = 0;
}
private void InitializeComponent()

54
samples/RenderDemo/Pages/ClippingPage.xaml

@ -19,30 +19,36 @@
</Style>
</Styles>
</Grid.Styles>
<Border Name="clipped"
Background="Yellow"
Width="100"
Height="100"
Clip="M 58.625 0.07421875
C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703
C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594
C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312
C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875
C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422
C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125
C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172
C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438
C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953
C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078
C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594
C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859
C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766
C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359
C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531
C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609
C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344
C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
<Border Name="clipped"
Background="Yellow"
Width="100"
Height="100"
Classes.clip="{Binding #useMask.IsChecked}">
<Border.Styles>
<Style Selector="Border.clip">
<Setter Property="Clip"
Value="M 58.625 0.07421875
C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703
C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594
C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312
C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875
C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422
C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125
C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172
C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438
C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953
C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078
C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594
C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859
C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766
C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359
C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531
C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609
C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344
C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z " />
</Style>
</Border.Styles>
<Border Name="clipChild" Background="Red" Margin="4">
<!-- Setting opacity puts the TextBox on a new layer -->
<TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>

17
samples/RenderDemo/Pages/ClippingPage.xaml.cs

@ -1,35 +1,18 @@
using System;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace RenderDemo.Pages
{
public class ClippingPage : UserControl
{
private Geometry _clip;
public ClippingPage()
{
InitializeComponent();
WireUpCheckbox();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void WireUpCheckbox()
{
var useMask = this.FindControl<CheckBox>("useMask");
var clipped = this.FindControl<Border>("clipped");
_clip = clipped.Clip;
useMask.Click += (s, e) => clipped.Clip = clipped.Clip == null ? _clip : null;
}
}
}

7
src/Avalonia.Base/Threading/Dispatcher.cs

@ -78,6 +78,13 @@ namespace Avalonia.Threading
/// </summary>
/// <param name="minimumPriority"></param>
public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
/// <summary>
/// Use this method to check if there are more prioritized tasks
/// </summary>
/// <param name="minimumPriority"></param>
public bool HasJobsWithPriority(DispatcherPriority minimumPriority) =>
_jobRunner.HasJobsWithPriority(minimumPriority);
/// <inheritdoc/>
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)

17
src/Avalonia.Base/Threading/JobRunner.cs

@ -109,7 +109,22 @@ namespace Avalonia.Threading
}
return null;
}
public bool HasJobsWithPriority(DispatcherPriority minimumPriority)
{
for (int c = (int)minimumPriority; c < (int)DispatcherPriority.MaxValue; c++)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return true;
}
}
return false;
}
private interface IJob
{
/// <summary>

4
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -185,7 +185,9 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
nameof(SelectedDate),
o => o.SelectedDate,
(o, v) => o.SelectedDate = v);
(o, v) => o.SelectedDate = v,
enableDataValidation: true,
defaultBindingMode:BindingMode.TwoWay);
public static readonly StyledProperty<CalendarDatePickerFormat> SelectedDateFormatProperty =
AvaloniaProperty.Register<CalendarDatePicker, CalendarDatePickerFormat>(

18
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -398,18 +399,27 @@ namespace Avalonia.Controls
private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e)
{
if (_presenter == null)
throw new InvalidOperationException("No DatePickerPresenter found");
throw new InvalidOperationException("No DatePickerPresenter found.");
if (_popup == null)
throw new InvalidOperationException("No Popup found.");
_presenter.Date = SelectedDate ?? DateTimeOffset.Now;
_popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
_popup.IsOpen = true;
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
// The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
_popup.VerticalOffset = deltaY + 5;
}
protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e)

3
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -534,6 +534,9 @@ namespace Avalonia.Controls
internal double GetOffsetForPopup()
{
if (_monthSelector is null)
return 0;
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2);
}

19
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using System;
using System.Globalization;
@ -254,16 +255,28 @@ namespace Avalonia.Controls
private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e)
{
if (_presenter == null)
throw new InvalidOperationException("No DatePickerPresenter found.");
if (_popup == null)
throw new InvalidOperationException("No Popup found.");
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
_popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
_popup.IsOpen = true;
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
// The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
_popup.VerticalOffset = deltaY + 5;
}
private void OnDismissPicker(object sender, EventArgs e)

3
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -253,6 +253,9 @@ namespace Avalonia.Controls
internal double GetOffsetForPopup()
{
if (_hourSelector is null)
return 0;
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2);
}

96
src/Avalonia.Controls/TransitioningContentControl.cs

@ -0,0 +1,96 @@
using System;
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls.Templates;
using Avalonia.Threading;
namespace Avalonia.Controls;
/// <summary>
/// Displays <see cref="ContentControl.Content"/> according to a <see cref="FuncDataTemplate"/>.
/// Uses <see cref="PageTransition"/> to move between the old and new content values.
/// </summary>
public class TransitioningContentControl : ContentControl
{
private CancellationTokenSource? _lastTransitionCts;
private object? _currentContent;
/// <summary>
/// Defines the <see cref="PageTransition"/> property.
/// </summary>
public static readonly StyledProperty<IPageTransition?> PageTransitionProperty =
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition?>(nameof(PageTransition),
new CrossFade(TimeSpan.FromSeconds(0.125)));
/// <summary>
/// Defines the <see cref="CurrentContent"/> property.
/// </summary>
public static readonly DirectProperty<TransitioningContentControl, object?> CurrentContentProperty =
AvaloniaProperty.RegisterDirect<TransitioningContentControl, object?>(nameof(CurrentContent),
o => o.CurrentContent);
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
public IPageTransition? PageTransition
{
get => GetValue(PageTransitionProperty);
set => SetValue(PageTransitionProperty, value);
}
/// <summary>
/// Gets the content currently displayed on the screen.
/// </summary>
public object? CurrentContent
{
get => _currentContent;
private set => SetAndRaise(CurrentContentProperty, ref _currentContent, value);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content));
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_lastTransitionCts?.Cancel();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == ContentProperty)
{
Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content));
}
}
/// <summary>
/// Updates the content with transitions.
/// </summary>
/// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object? content)
{
if (VisualRoot is null)
{
return;
}
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (PageTransition != null)
await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
CurrentContent = content;
if (PageTransition != null)
await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
}
}

20
src/Avalonia.Controls/Window.cs

@ -255,6 +255,11 @@ namespace Avalonia.Controls
[CanBeNull]
public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
/// <summary>
/// Gets a collection of child windows owned by this window.
/// </summary>
public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToList();
/// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content.
/// </summary>
@ -854,6 +859,17 @@ namespace Avalonia.Controls
private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
{
var startupLocation = WindowStartupLocation;
if (startupLocation == WindowStartupLocation.CenterOwner &&
Owner is Window ownerWindow &&
ownerWindow.WindowState == WindowState.Minimized)
{
// If startup location is CenterOwner, but owner is minimized then fall back
// to CenterScreen. This behavior is consistent with WPF.
startupLocation = WindowStartupLocation.CenterScreen;
}
var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1;
// TODO: We really need non-client size here.
@ -861,7 +877,7 @@ namespace Avalonia.Controls
PixelPoint.Origin,
PixelSize.FromSize(ClientSize, scaling));
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
if (startupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
@ -870,7 +886,7 @@ namespace Avalonia.Controls
Position = screen.WorkingArea.CenterRect(rect).Position;
}
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
else if (startupLocation == WindowStartupLocation.CenterOwner)
{
if (owner != null)
{

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -77,7 +77,8 @@ namespace Avalonia.Diagnostics.ViewModels
c.GetObservable(Control.ContextMenuProperty),
c.GetObservable(FlyoutBase.AttachedFlyoutProperty),
c.GetObservable(ToolTipDiagnostics.ToolTipProperty),
(ContextFlyout, ContextMenu, AttachedFlyout, ToolTip) =>
c.GetObservable(Button.FlyoutProperty),
(ContextFlyout, ContextMenu, AttachedFlyout, ToolTip, ButtonFlyout) =>
{
if (ContextMenu != null)
//Note: ContextMenus are special since all the items are added as visual children.
@ -93,6 +94,9 @@ namespace Avalonia.Diagnostics.ViewModels
if (ToolTip != null)
return GetPopupHostObservable(ToolTip, "ToolTip");
if (ButtonFlyout != null)
return GetPopupHostObservable(ButtonFlyout, "Flyout");
return Observable.Return<PopupRoot?>(null);
})
.Switch(),

5
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -147,6 +147,7 @@ namespace Avalonia.Diagnostics.Views
ProcessProperty(control, ContextMenuProperty);
ProcessProperty(control, FlyoutBase.AttachedFlyoutProperty);
ProcessProperty(control, ToolTipDiagnostics.ToolTipProperty);
ProcessProperty(control, Button.FlyoutProperty);
}
return popupRoots;
@ -162,7 +163,9 @@ namespace Avalonia.Diagnostics.Views
switch (e.Modifiers)
{
case RawInputModifiers.Control | RawInputModifiers.Shift:
case RawInputModifiers.Control when (e.Key == Key.LeftShift || e.Key == Key.RightShift):
case RawInputModifiers.Shift when (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl):
case RawInputModifiers.Shift | RawInputModifiers.Control:
{
IControl? control = null;

19
src/Avalonia.Input/MouseDevice.cs

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -159,7 +161,7 @@ namespace Avalonia.Input
case RawPointerEventType.XButton1Down:
case RawPointerEventType.XButton2Down:
if (ButtonCount(props) > 1)
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
else
e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
props, keyModifiers);
@ -170,12 +172,12 @@ namespace Avalonia.Input
case RawPointerEventType.XButton1Up:
case RawPointerEventType.XButton2Up:
if (ButtonCount(props) != 0)
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
else
e.Handled = MouseUp(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
break;
case RawPointerEventType.Move:
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
break;
case RawPointerEventType.Wheel:
e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, keyModifiers);
@ -263,7 +265,7 @@ namespace Avalonia.Input
}
private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers)
KeyModifiers inputModifiers, Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
@ -283,7 +285,7 @@ namespace Avalonia.Input
if (source is object)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
p, timestamp, properties, inputModifiers);
p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e);
return e.Handled;
@ -324,6 +326,13 @@ namespace Avalonia.Input
var hit = HitTest(root, p);
var source = GetSource(hit);
// KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform.
// If Shift-Key is pressed and X is close to 0 we swap the Vector.
if (inputModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X))
{
delta = new Vector(delta.Y, delta.X);
}
if (source is not null)
{
var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta);

45
src/Avalonia.Input/PointerEventArgs.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -10,6 +11,7 @@ namespace Avalonia.Input
private readonly IVisual? _rootVisual;
private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties;
private Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
@ -28,6 +30,20 @@ namespace Avalonia.Input
Timestamp = timestamp;
KeyModifiers = modifiers;
}
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
IPointer pointer,
IVisual? rootVisual, Point rootVisualPosition,
ulong timestamp,
PointerPointProperties properties,
KeyModifiers modifiers,
Lazy<IReadOnlyList<RawPointerPoint>?>? previousPoints)
: this(routedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers)
{
_previousPoints = previousPoints;
}
class EmulatedDevice : IPointerDevice
{
@ -76,14 +92,16 @@ namespace Avalonia.Input
public KeyModifiers KeyModifiers { get; }
public Point GetPosition(IVisual? relativeTo)
private Point GetPosition(Point pt, IVisual? relativeTo)
{
if (_rootVisual == null)
return default;
if (relativeTo == null)
return _rootVisualPosition;
return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default;
return pt;
return pt * _rootVisual.TransformToVisual(relativeTo) ?? default;
}
public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
[Obsolete("Use GetCurrentPoint")]
public PointerPoint GetPointerPoint(IVisual? relativeTo) => GetCurrentPoint(relativeTo);
@ -96,6 +114,27 @@ namespace Avalonia.Input
public PointerPoint GetCurrentPoint(IVisual? relativeTo)
=> new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
/// <summary>
/// Returns the PointerPoint associated with the current event
/// </summary>
/// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param>
/// <returns></returns>
public IReadOnlyList<PointerPoint> GetIntermediatePoints(IVisual? relativeTo)
{
var previousPoints = _previousPoints?.Value;
if (previousPoints == null || previousPoints.Count == 0)
return new[] { GetCurrentPoint(relativeTo) };
var points = new PointerPoint[previousPoints.Count + 1];
for (var c = 0; c < previousPoints.Count; c++)
{
var pt = previousPoints[c];
points[c] = new PointerPoint(Pointer, GetPosition(pt.Position, relativeTo), _properties);
}
points[points.Length - 1] = GetCurrentPoint(relativeTo);
return points;
}
/// <summary>
/// Returns the current pointer point properties
/// </summary>

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

@ -51,6 +51,6 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the timestamp associated with the event.
/// </summary>
public ulong Timestamp { get; private set; }
public ulong Timestamp { get; set; }
}
}

65
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Input.Raw
{
@ -29,6 +30,8 @@ namespace Avalonia.Input.Raw
/// </summary>
public class RawPointerEventArgs : RawInputEventArgs
{
private RawPointerPoint _point;
/// <summary>
/// Initializes a new instance of the <see cref="RawPointerEventArgs"/> class.
/// </summary>
@ -54,11 +57,50 @@ namespace Avalonia.Input.Raw
Type = type;
InputModifiers = inputModifiers;
}
/// <summary>
/// Initializes a new instance of the <see cref="RawPointerEventArgs"/> class.
/// </summary>
/// <param name="device">The associated device.</param>
/// <param name="timestamp">The event timestamp.</param>
/// <param name="root">The root from which the event originates.</param>
/// <param name="type">The type of the event.</param>
/// <param name="point">The point properties and position, in client DIPs.</param>
/// <param name="inputModifiers">The input modifiers.</param>
public RawPointerEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
RawPointerEventType type,
RawPointerPoint point,
RawInputModifiers inputModifiers)
: base(device, timestamp, root)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Point = point;
Type = type;
InputModifiers = inputModifiers;
}
/// <summary>
/// Gets the pointer properties and position, in client DIPs.
/// </summary>
public RawPointerPoint Point
{
get => _point;
set => _point = value;
}
/// <summary>
/// Gets the mouse position, in client DIPs.
/// </summary>
public Point Position { get; set; }
public Point Position
{
get => _point.Position;
set => _point.Position = value;
}
/// <summary>
/// Gets the type of the event.
@ -68,6 +110,25 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the input modifiers.
/// </summary>
public RawInputModifiers InputModifiers { get; private set; }
public RawInputModifiers InputModifiers { get; set; }
/// <summary>
/// Points that were traversed by a pointer since the previous relevant event,
/// only valid for Move and TouchUpdate
/// </summary>
public Lazy<IReadOnlyList<RawPointerPoint>?>? IntermediatePoints { get; set; }
}
public struct RawPointerPoint
{
/// <summary>
/// Pointer position, in client DIPs.
/// </summary>
public Point Position { get; set; }
public RawPointerPoint()
{
Position = default;
}
}
}

2
src/Avalonia.Input/TouchDevice.cs

@ -107,7 +107,7 @@ namespace Avalonia.Input
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), PointerUpdateKind.Other),
GetKeyModifiers(args.InputModifiers)));
GetKeyModifiers(args.InputModifiers), args.IntermediatePoints));
}

6
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -36,7 +36,13 @@ namespace Avalonia.MicroCom
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class =>
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyOrNullFor<T>(IntPtr pObject, bool ownsHandle) where T : class =>
pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);
public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown

5
src/Avalonia.MicroCom/MicroComVtblBase.cs

@ -21,6 +21,11 @@ namespace Avalonia.MicroCom
AddMethod((AddRefDelegate)Release);
}
protected void AddMethod(void* f)
{
_methods.Add(new IntPtr(f));
}
protected void AddMethod(Delegate d)
{
GCHandle.Alloc(d);

5
src/Avalonia.Native/Avalonia.Native.csproj

@ -6,6 +6,7 @@
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">
@ -20,7 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<AvnComIdl Include="avn.idl" OutputFile="Interop.Generated.cs" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" />
</ItemGroup>
<Import Project="../../build/MicroCom.targets" />
</Project>

5
src/Avalonia.Native/WindowImpl.cs

@ -87,7 +87,10 @@ namespace Avalonia.Native
_native.SetTitleBarColor(new AvnColor { Alpha = color.A, Red = color.R, Green = color.G, Blue = color.B });
}
public void SetTitle(string title) => _native.SetTitle(title);
public void SetTitle(string title)
{
_native.SetTitle(title ?? "");
}
public WindowState WindowState
{

2
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -89,7 +89,9 @@ namespace Avalonia.OpenGL.Controls
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffers(1, new[] { _fb });
_fb = 0;
gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
_depthBuffer = 0;
_attachment?.Dispose();
_attachment = null;
_bitmap?.Dispose();

2
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -82,7 +82,7 @@ namespace Avalonia.OpenGL.Egl
finally
{
if(!success)
Monitor.Enter(_lock);
Monitor.Exit(_lock);
}
}

1
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -48,6 +48,7 @@ namespace Avalonia.ReactiveUI
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
ViewModel = DataContext as TViewModel;
}

1
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -10,6 +10,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// A ContentControl that animates the transition when its content is changed.
/// </summary>
[Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")]
public class TransitioningContentControl : ContentControl, IStyleable
{
/// <summary>

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

@ -38,6 +38,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.TabItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TransitioningContentControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TitleBar.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TreeView.xaml?assembly=Avalonia.Themes.Default"/>

59
src/Avalonia.Themes.Default/Expander.xaml

@ -1,5 +1,36 @@
<Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20" Width="350">
<Expander ExpandDirection="Up" Header="Expand Up" CornerRadius="25">
<Expander.Header>
<Grid ColumnDefinitions="*, Auto">
<TextBlock Grid.Column="0" Text="Expand" />
<TextBlock Grid.Column="1" Text="Up" />
</Grid>
</Expander.Header>
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Down" Header="Expand Down" CornerRadius="25">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Left" Header="Expand Left" CornerRadius="25">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Right" Header="Expand Right" CornerRadius="25">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="Expander">
<Setter Property="ContentTransition">
<Setter.Value>
@ -15,7 +46,10 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid RowDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Row="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ToggleButton Name="PART_toggle" Grid.Row="0"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}"
@ -37,7 +71,10 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid RowDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ToggleButton Name="PART_toggle" Grid.Row="1"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}"
@ -59,7 +96,10 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Column="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ToggleButton Name="PART_toggle" Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}"
@ -81,7 +121,10 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ToggleButton Name="PART_toggle" Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
@ -96,10 +139,12 @@
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="1" Background="Transparent">
<Grid ColumnDefinitions="Auto,Auto">
<Border BorderThickness="1" Background="{TemplateBinding Background}">
<Grid ColumnDefinitions="Auto,*">
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="{DynamicResource ThemeForegroundBrush}"
HorizontalAlignment="Center"

20
src/Avalonia.Themes.Default/TransitioningContentControl.xaml

@ -0,0 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TransitioningContentControl">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding CurrentContent}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>
</Styles>

8
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -5,6 +5,12 @@
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20" Width="350">
<Expander ExpandDirection="Up" Header="Expand Up" CornerRadius="25">
<Expander.Header>
<Grid ColumnDefinitions="*, Auto">
<TextBlock Grid.Column="0" Text="Expand" />
<TextBlock Grid.Column="1" Text="Up" />
</Grid>
</Expander.Header>
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
@ -90,7 +96,7 @@
</Style>
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="ToggleButtonBackground">

1
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@ -37,6 +37,7 @@
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TabItem.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TextBox.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/ToggleButton.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Expander.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TitleBar.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TreeView.xaml"/>

20
src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml

@ -0,0 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TransitioningContentControl">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding CurrentContent}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>
</Styles>

48
src/Avalonia.Themes.Fluent/FluentTheme.cs

@ -15,6 +15,12 @@ namespace Avalonia.Themes.Fluent
Dark,
}
public enum DensityStyle
{
Normal,
Compact
}
/// <summary>
/// Includes the fluent theme in an application.
/// </summary>
@ -24,6 +30,7 @@ namespace Avalonia.Themes.Fluent
private Styles _fluentDark = new();
private Styles _fluentLight = new();
private Styles _sharedStyles = new();
private Styles _densityStyles = new();
private bool _isLoading;
private IStyle? _loaded;
@ -47,9 +54,12 @@ namespace Avalonia.Themes.Fluent
InitStyles(_baseUri);
}
public static readonly StyledProperty<FluentThemeMode> ModeProperty =
AvaloniaProperty.Register<FluentTheme, FluentThemeMode>(nameof(Mode));
public static readonly StyledProperty<DensityStyle> DensityStyleProperty =
AvaloniaProperty.Register<FluentTheme, DensityStyle>(nameof(DensityStyle));
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
@ -58,6 +68,16 @@ namespace Avalonia.Themes.Fluent
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
/// <summary>
/// Gets or sets the density style of the fluent theme (normal, compact).
/// </summary>
public DensityStyle DensityStyle
{
get => GetValue(DensityStyleProperty);
set => SetValue(DensityStyleProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
@ -74,6 +94,18 @@ namespace Avalonia.Themes.Fluent
(Loaded as Styles)![2] = _fluentLight[1];
}
}
if (change.Property == DensityStyleProperty)
{
if (DensityStyle == DensityStyle.Compact)
{
(Loaded as Styles)!.Add(_densityStyles[0]);
}
else if (DensityStyle == DensityStyle.Normal)
{
(Loaded as Styles)!.Remove(_densityStyles[0]);
}
}
}
public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner;
@ -97,6 +129,12 @@ namespace Avalonia.Themes.Fluent
{
_loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] };
}
if (DensityStyle == DensityStyle.Compact)
{
(_loaded as Styles)!.Add(_densityStyles[0]);
}
_isLoading = false;
}
@ -183,6 +221,14 @@ namespace Avalonia.Themes.Fluent
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml")
}
};
_densityStyles = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/DensityStyles/Compact.xaml")
}
};
}
}
}

6
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -12,6 +12,8 @@ namespace Avalonia.Animation.Animators
/// </summary>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
{
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
{
if (oldValue is null || newValue is null)
@ -19,7 +21,9 @@ namespace Avalonia.Animation.Animators
return progress >= 0.5 ? newValue : oldValue;
}
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
return new ImmutableSolidColorBrush(
ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity));
}
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)

2
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -79,6 +79,7 @@ namespace Avalonia.Animation
var animation = new Animation
{
Easing = SlideOutEasing,
FillMode = FillMode.Forward,
Children =
{
new KeyFrame
@ -109,6 +110,7 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
FillMode = FillMode.Forward,
Easing = SlideInEasing,
Children =
{

1
src/Avalonia.X11/Avalonia.X11.csproj

@ -9,6 +9,7 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj" />
<Compile Include="..\Shared\RawEventGrouping.cs" />
</ItemGroup>
</Project>

2
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -63,7 +63,7 @@ namespace Avalonia.X11.NativeDialogs
public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler)
{
var handle = GCHandle.Alloc(handler);
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
var ptr = Marshal.GetFunctionPointerForDelegate<T>(handler);
using (var utf = new Utf8Buffer(name))
{
var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0);

16
src/Avalonia.X11/SMLib.cs

@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Runtime.InteropServices;
@ -7,20 +8,19 @@ namespace Avalonia.X11
{
private const string LibSm = "libSM.so.6";
[DllImport(LibSm, CharSet = CharSet.Ansi)]
[DllImport(LibSm, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SmcOpenConnection(
[MarshalAs(UnmanagedType.LPWStr)] string networkId,
[MarshalAs(UnmanagedType.LPStr)] string? networkId,
IntPtr content,
int xsmpMajorRev,
int xsmpMinorRev,
ulong mask,
nuint mask,
ref SmcCallbacks callbacks,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string previousId,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string clientIdRet,
[MarshalAs(UnmanagedType.LPStr)] string? previousId,
ref IntPtr clientIdRet,
int errorLength,
[Out] char[] errorStringRet);
[Out] byte[] errorStringRet
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern int SmcCloseConnection(

21
src/Avalonia.X11/X11PlatformLifetimeEvents.cs

@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Text;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
@ -14,10 +15,10 @@ namespace Avalonia.X11
internal unsafe class X11PlatformLifetimeEvents : IDisposable, IPlatformLifetimeEventsImpl
{
private readonly AvaloniaX11Platform _platform;
private const ulong SmcSaveYourselfProcMask = 1L;
private const ulong SmcDieProcMask = 2L;
private const ulong SmcSaveCompleteProcMask = 4L;
private const ulong SmcShutdownCancelledProcMask = 8L;
private const nuint SmcSaveYourselfProcMask = 1;
private const nuint SmcDieProcMask = 2;
private const nuint SmcSaveCompleteProcMask = 4;
private const nuint SmcShutdownCancelledProcMask = 8;
private static readonly ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents> s_nativeToManagedMapper =
new ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents>();
@ -62,24 +63,24 @@ namespace Avalonia.X11
return;
}
var errorBuf = new char[255];
var smcConn = SMLib.SmcOpenConnection(null!,
byte[] errorBuf = new byte[255];
IntPtr clientIdRet = IntPtr.Zero;
var smcConn = SMLib.SmcOpenConnection(null,
IntPtr.Zero, 1, 0,
SmcSaveYourselfProcMask |
SmcSaveCompleteProcMask |
SmcShutdownCancelledProcMask |
SmcDieProcMask,
ref s_callbacks,
out _,
out _,
null,
ref clientIdRet,
errorBuf.Length,
errorBuf);
if (smcConn == IntPtr.Zero)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
$"SMLib/ICELib reported a new error: {new string(errorBuf)}");
$"SMLib/ICELib reported a new error: {Encoding.ASCII.GetString(errorBuf)}");
return;
}

39
src/Avalonia.X11/X11Window.cs

@ -50,13 +50,7 @@ namespace Avalonia.X11
private double? _scalingOverride;
private bool _disabled;
private TransparencyHelper _transparencyHelper;
class InputEventContainer
{
public RawInputEventArgs Event;
}
private readonly Queue<InputEventContainer> _inputQueue = new Queue<InputEventContainer>();
private InputEventContainer _lastEvent;
private RawEventGrouper _rawEventGrouper;
private bool _useRenderWindow = false;
public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent)
{
@ -180,6 +174,8 @@ namespace Avalonia.X11
UpdateMotifHints();
UpdateSizeHints(null);
_rawEventGrouper = new RawEventGrouper(e => Input?.Invoke(e));
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None);
@ -721,33 +717,14 @@ namespace Avalonia.X11
if (args is RawDragEvent drag)
drag.Location = drag.Location / RenderScaling;
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
if (_inputQueue.Count == 1)
{
Dispatcher.UIThread.Post(() =>
{
while (_inputQueue.Count > 0)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var ev = _inputQueue.Dequeue();
Input?.Invoke(ev.Event);
}
}, DispatcherPriority.Input);
}
_rawEventGrouper.HandleEvent(args);
}
void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods)
{
var mev = new RawPointerEventArgs(
_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
if(type == RawPointerEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawPointerEventArgs ma)
if (ma.Type == RawPointerEventType.Move)
{
_lastEvent.Event = mev;
return;
}
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
ScheduleInput(mev, ref ev);
}
@ -775,6 +752,12 @@ namespace Avalonia.X11
void Cleanup()
{
if (_rawEventGrouper != null)
{
_rawEventGrouper.Dispose();
_rawEventGrouper = null;
}
if (_transparencyHelper != null)
{
_transparencyHelper.Dispose();

6
src/Directory.Build.props

@ -2,4 +2,10 @@
<Import Project="..\Directory.Build.props" />
<Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\Shared\ModuleInitializer.cs" >
<Link>Shared\_ModuleInitializer.cs</Link>
<Visible>false</Visible>
</Compile>
</ItemGroup>
</Project>

1
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@ -7,5 +7,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<Compile Include="..\..\Shared\RawEventGrouping.cs" />
</ItemGroup>
</Project>

38
src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs

@ -13,15 +13,16 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev
private readonly EvDevDeviceDescription[] _deviceDescriptions;
private readonly List<EvDevDeviceHandler> _handlers = new List<EvDevDeviceHandler>();
private int _epoll;
private Queue<RawInputEventArgs> _inputQueue = new Queue<RawInputEventArgs>();
private bool _isQueueHandlerTriggered;
private object _lock = new object();
private Action<RawInputEventArgs> _onInput;
private IInputRoot _inputRoot;
private RawEventGroupingThreadingHelper _inputQueue;
public EvDevBackend(EvDevDeviceDescription[] devices)
{
_deviceDescriptions = devices;
_inputQueue = new RawEventGroupingThreadingHelper(e => _onInput?.Invoke(e));
}
unsafe void InputThread()
@ -49,42 +50,9 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev
private void OnRawEvent(RawInputEventArgs obj)
{
lock (_lock)
{
_inputQueue.Enqueue(obj);
TriggerQueueHandler();
}
_inputQueue.OnEvent(obj);
}
void TriggerQueueHandler()
{
if (_isQueueHandlerTriggered)
return;
_isQueueHandlerTriggered = true;
Dispatcher.UIThread.Post(InputQueueHandler, DispatcherPriority.Input);
}
void InputQueueHandler()
{
RawInputEventArgs ev;
lock (_lock)
{
_isQueueHandlerTriggered = false;
if(_inputQueue.Count == 0)
return;
ev = _inputQueue.Dequeue();
}
_onInput?.Invoke(ev);
lock (_lock)
{
if (_inputQueue.Count > 0)
TriggerQueueHandler();
}
}
public void Initialize(IScreenInfoProvider info, Action<RawInputEventArgs> onInput)
{

31
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs

@ -17,15 +17,15 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
private TouchDevice _touch = new TouchDevice();
private MouseDevice _mouse = new MouseDevice();
private Point _mousePosition;
private readonly Queue<RawInputEventArgs> _inputQueue = new Queue<RawInputEventArgs>();
private readonly RawEventGroupingThreadingHelper _inputQueue;
private Action<RawInputEventArgs> _onInput;
private Dictionary<int, Point> _pointers = new Dictionary<int, Point>();
public LibInputBackend()
{
var ctx = libinput_path_create_context();
_inputQueue = new(e => _onInput?.Invoke(e));
new Thread(()=>InputThread(ctx)).Start();
}
@ -66,30 +66,7 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
}
}
private void ScheduleInput(RawInputEventArgs ev)
{
lock (_inputQueue)
{
_inputQueue.Enqueue(ev);
if (_inputQueue.Count == 1)
{
Dispatcher.UIThread.Post(() =>
{
while (true)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
RawInputEventArgs dequeuedEvent = null;
lock(_inputQueue)
if (_inputQueue.Count != 0)
dequeuedEvent = _inputQueue.Dequeue();
if (dequeuedEvent == null)
return;
_onInput?.Invoke(dequeuedEvent);
}
}, DispatcherPriority.Input);
}
}
}
private void ScheduleInput(RawInputEventArgs ev) => _inputQueue.OnEvent(ev);
private void HandleTouch(IntPtr ev, LibInputEventType type)
{

43
src/Linux/Avalonia.LinuxFramebuffer/Input/RawEventGroupingThreadingHelper.cs

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using Avalonia.Input.Raw;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer.Input;
internal class RawEventGroupingThreadingHelper : IDisposable
{
private readonly RawEventGrouper _grouper;
private readonly Queue<RawInputEventArgs> _rawQueue = new();
private readonly Action _queueHandler;
public RawEventGroupingThreadingHelper(Action<RawInputEventArgs> eventCallback)
{
_grouper = new RawEventGrouper(eventCallback);
_queueHandler = QueueHandler;
}
private void QueueHandler()
{
lock (_rawQueue)
{
while (_rawQueue.Count > 0)
_grouper.HandleEvent(_rawQueue.Dequeue());
}
}
public void OnEvent(RawInputEventArgs args)
{
lock (_rawQueue)
{
_rawQueue.Enqueue(args);
if (_rawQueue.Count == 1)
{
Dispatcher.UIThread.Post(_queueHandler, DispatcherPriority.Input);
}
}
}
public void Dispose() =>
Dispatcher.UIThread.Post(() => _grouper.Dispose(), DispatcherPriority.Input + 1);
}

10
src/Shared/ModuleInitializer.cs

@ -0,0 +1,10 @@
namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER
internal class ModuleInitializerAttribute : Attribute
{
}
#endif
}

133
src/Shared/RawEventGrouping.cs

@ -0,0 +1,133 @@
#nullable enable
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Threading;
using JetBrains.Annotations;
namespace Avalonia;
/*
This helper maintains an input queue for backends that handle input asynchronously.
While doing that it groups Move and TouchUpdate events so we could provide GetIntermediatePoints API
*/
internal class RawEventGrouper : IDisposable
{
private readonly Action<RawInputEventArgs> _eventCallback;
private readonly Queue<RawInputEventArgs> _inputQueue = new();
private readonly Action _dispatchFromQueue;
readonly Dictionary<long, RawTouchEventArgs> _lastTouchPoints = new();
RawInputEventArgs? _lastEvent;
public RawEventGrouper(Action<RawInputEventArgs> eventCallback)
{
_eventCallback = eventCallback;
_dispatchFromQueue = DispatchFromQueue;
}
private void AddToQueue(RawInputEventArgs args)
{
_lastEvent = args;
_inputQueue.Enqueue(args);
if (_inputQueue.Count == 1)
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input);
}
private void DispatchFromQueue()
{
while (true)
{
if(_inputQueue.Count == 0)
return;
var ev = _inputQueue.Dequeue();
if (_lastEvent == ev)
_lastEvent = null;
if (ev is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchUpdate)
_lastTouchPoints.Remove(touchUpdate.TouchPointId);
_eventCallback?.Invoke(ev);
if (ev is RawPointerEventArgs { IntermediatePoints.Value: PooledList<RawPointerPoint> list })
list.Dispose();
if (Dispatcher.UIThread.HasJobsWithPriority(DispatcherPriority.Input + 1))
{
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input);
return;
}
}
}
public void HandleEvent(RawInputEventArgs args)
{
/*
Try to update already enqueued events if
1) they are still not handled (_lastEvent and _lastTouchPoints shouldn't contain said event in that case)
2) previous event belongs to the same "event block", events in the same block:
- belong from the same device
- are pointer move events (Move/TouchUpdate)
- have the same type
- have same modifiers
Even if nothing is updated and the event is actually enqueued, we need to update the relevant tracking info
*/
if (
args is RawPointerEventArgs pointerEvent
&& _lastEvent != null
&& _lastEvent.Device == args.Device
&& _lastEvent is RawPointerEventArgs lastPointerEvent
&& lastPointerEvent.InputModifiers == pointerEvent.InputModifiers
&& lastPointerEvent.Type == pointerEvent.Type
&& lastPointerEvent.Type is RawPointerEventType.Move or RawPointerEventType.TouchUpdate)
{
if (args is RawTouchEventArgs touchEvent)
{
if (_lastTouchPoints.TryGetValue(touchEvent.TouchPointId, out var lastTouchEvent))
MergeEvents(lastTouchEvent, touchEvent);
else
{
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent;
AddToQueue(touchEvent);
}
}
else
MergeEvents(lastPointerEvent, pointerEvent);
return;
}
else
{
_lastTouchPoints.Clear();
if (args is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchEvent)
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent;
}
AddToQueue(args);
}
private static IReadOnlyList<RawPointerPoint> GetPooledList() => new PooledList<RawPointerPoint>();
private static readonly Func<IReadOnlyList<RawPointerPoint>> s_getPooledListDelegate = GetPooledList;
private static void MergeEvents(RawPointerEventArgs last, RawPointerEventArgs current)
{
last.IntermediatePoints ??= new Lazy<IReadOnlyList<RawPointerPoint>?>(s_getPooledListDelegate);
((PooledList<RawPointerPoint>)last.IntermediatePoints.Value!).Add(new RawPointerPoint { Position = last.Position });
last.Position = current.Position;
last.Timestamp = current.Timestamp;
last.InputModifiers = current.InputModifiers;
}
public void Dispose()
{
_inputQueue.Clear();
_lastEvent = null;
_lastTouchPoints.Clear();
}
}

71
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@ -1,10 +1,11 @@
using Avalonia.Platform;
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia.Helpers
{
public class DrawingContextHelper
public static class DrawingContextHelper
{
/// <summary>
/// Wrap Skia canvas in drawing context so we can use Avalonia api to render to external skia canvas
@ -27,5 +28,71 @@ namespace Avalonia.Skia.Helpers
return new DrawingContextImpl(createInfo);
}
/// <summary>
/// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext.
/// </summary>
[Obsolete]
public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables)
{
var createInfo = new DrawingContextImpl.CreateInfo
{
GrContext = grContext,
Surface = surface,
Dpi = dpi,
DisableTextLcdRendering = false,
};
return new DrawingContextImpl(createInfo, disposables);
}
/// <summary>
/// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext.
/// </summary>
[Obsolete]
public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables)
{
var createInfo = new DrawingContextImpl.CreateInfo
{
Surface = surface,
Dpi = dpi,
DisableTextLcdRendering = false,
};
return new DrawingContextImpl(createInfo, disposables);
}
[Obsolete]
public static ISkiaDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null)
{
if (grContext is null)
{
var surface = SKSurface.Create(
new SKImageInfo(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
SKImageInfo.PlatformColorType,
SKAlphaType.Premul));
return WrapSkiaSurface(surface, dpi, surface);
}
else
{
var surface = SKSurface.Create(grContext, false,
new SKImageInfo(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
SKImageInfo.PlatformColorType,
SKAlphaType.Premul));
return WrapSkiaSurface(surface, grContext, dpi, surface);
}
}
[Obsolete]
public static void DrawTo(this ISkiaDrawingContextImpl source, ISkiaDrawingContextImpl destination, SKPaint paint = null)
{
destination.SkCanvas.DrawSurface(source.SkSurface, new SKPoint(0, 0), paint);
}
}
}

7
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -3,14 +3,15 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Win32</PackageId>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvnComIdl Include="WinRT\winrt.idl" OutputFile="WinRT\WinRT.Generated.cs" />
<AvnComIdl Include="Win32Com\win32.idl" OutputFile="Win32Com\Win32.Generated.cs" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" />
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />
</ItemGroup>
<Import Project="../../../build/MicroCom.targets" />
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
</Project>

6
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -862,7 +862,7 @@ namespace Avalonia.Win32.Interop
public void Init()
{
biSize = (uint)Marshal.SizeOf(this);
biSize = (uint)sizeof(BITMAPINFOHEADER);
}
}
@ -1521,7 +1521,7 @@ namespace Avalonia.Win32.Interop
internal static Version RtlGetVersion()
{
RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX();
v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(v);
v.dwOSVersionInfoSize = (uint)Marshal.SizeOf<RTL_OSVERSIONINFOEX>();
if (RtlGetVersion(ref v) == 0)
{
return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber);
@ -1914,7 +1914,7 @@ namespace Avalonia.Win32.Interop
get
{
WINDOWPLACEMENT result = new WINDOWPLACEMENT();
result.Length = Marshal.SizeOf(result);
result.Length = Marshal.SizeOf<WINDOWPLACEMENT>();
return result;
}
}

31
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -79,7 +79,16 @@ namespace Avalonia.Win32
}
}
frm.Show(hWnd);
var showResult = frm.Show(hWnd);
if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
return result;
}
else if ((uint)showResult != (uint)UnmanagedMethods.HRESULT.S_OK)
{
throw new Win32Exception(showResult);
}
if (openDialog?.AllowMultiple == true)
{
@ -108,10 +117,6 @@ namespace Avalonia.Win32
}
catch (COMException ex)
{
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
return result;
}
throw new Win32Exception(ex.HResult);
}
})!;
@ -151,7 +156,17 @@ namespace Avalonia.Win32
}
}
frm.Show(hWnd);
var showResult = frm.Show(hWnd);
if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
return result;
}
else if ((uint)showResult != (uint)UnmanagedMethods.HRESULT.S_OK)
{
throw new Win32Exception(showResult);
}
if (frm.Result is not null)
{
result = GetAbsoluteFilePath(frm.Result);
@ -161,10 +176,6 @@ namespace Avalonia.Win32
}
catch (COMException ex)
{
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
return result;
}
throw new Win32Exception(ex.HResult);
}
});

2
src/Windows/Avalonia.Win32/Win32Com/win32.idl

@ -112,7 +112,7 @@ interface IShellItemArray : IUnknown
interface IModalWindow : IUnknown
{
[local]
HRESULT Show(
int Show(
[in, unique] HWND hwndOwner);
}

10
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -228,7 +228,7 @@ namespace Avalonia.Win32
return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling;
}
DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT)));
DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf<RECT>());
return new Size(rect.Width, rect.Height) / RenderScaling;
}
}
@ -265,7 +265,7 @@ namespace Avalonia.Win32
{
if (IsWindowVisible(_hwnd))
{
ShowWindow(value, true);
ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated
}
_showWindowState = value;
@ -337,7 +337,7 @@ namespace Avalonia.Win32
private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel)
{
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf(accent);
var accentStructSize = Marshal.SizeOf<AccentPolicy>();
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur)
{
@ -392,7 +392,7 @@ namespace Avalonia.Win32
bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628;
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf(accent);
var accentStructSize = Marshal.SizeOf<AccentPolicy>();
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic)
{
@ -973,7 +973,7 @@ namespace Avalonia.Win32
{
case WindowState.Minimized:
newWindowProperties.IsFullScreen = false;
command = activate ? ShowWindowCommand.Minimize : ShowWindowCommand.ShowMinNoActive;
command = ShowWindowCommand.Minimize;
break;
case WindowState.Maximized:
newWindowProperties.IsFullScreen = false;

241
src/tools/MicroComGenerator/Ast.cs

@ -1,241 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MicroComGenerator.Ast
{
public class AstAttributeNode
{
public string Name { get; set; }
public string Value { get; set; }
public AstAttributeNode(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString() => $"{Name} = {Value}";
public AstAttributeNode Clone() => new AstAttributeNode(Name, Value);
}
public class AstAttributes : List<AstAttributeNode>
{
public bool HasAttribute(string a) => this.Any(x => x.Name == a);
public AstAttributes Clone()
{
var rv= new AstAttributes();
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public interface IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; }
}
public class AstEnumNode : List<AstEnumMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public override string ToString() => "Enum " + Name;
public AstEnumNode Clone()
{
var rv = new AstEnumNode { Name = Name, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstEnumMemberNode
{
public string Name { get; set; }
public string Value { get; set; }
public AstEnumMemberNode(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString() => $"Enum member {Name} = {Value}";
public AstEnumMemberNode Clone() => new AstEnumMemberNode(Name, Value);
}
public class AstStructNode : List<AstStructMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public override string ToString() => "Struct " + Name;
public AstStructNode Clone()
{
var rv = new AstStructNode { Name = Name, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstTypeNode
{
public string Name { get; set; }
public int PointerLevel { get; set; }
public bool IsLink { get; set; }
public string Format() => Name + new string('*', PointerLevel)
+ (IsLink ? "&" : "");
public override string ToString() => Format();
public AstTypeNode Clone() => new AstTypeNode() {
Name = Name,
PointerLevel = PointerLevel,
IsLink = IsLink
};
}
public class AstStructMemberNode : IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode Type { get; set; }
public override string ToString() => $"Struct member {Type.Format()} {Name}";
public AstStructMemberNode Clone() => new AstStructMemberNode() { Name = Name, Type = Type.Clone() };
public AstAttributes Attributes { get; set; } = new AstAttributes();
}
public class AstInterfaceNode : List<AstInterfaceMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public string Inherits { get; set; }
public override string ToString()
{
if (Inherits == null)
return Name;
return $"Interface {Name} : {Inherits}";
}
public AstInterfaceNode Clone()
{
var rv = new AstInterfaceNode { Name = Name, Inherits = Inherits, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstInterfaceMemberNode : List<AstInterfaceMemberArgumentNode>, IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode ReturnType { get; set; }
public AstAttributes Attributes { get; set; } = new AstAttributes();
public AstInterfaceMemberNode Clone()
{
var rv = new AstInterfaceMemberNode()
{
Name = Name, Attributes = Attributes.Clone(), ReturnType = ReturnType
};
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
public override string ToString() =>
$"Interface member {ReturnType.Format()} {Name} ({string.Join(", ", this.Select(x => x.Format()))})";
}
public class AstInterfaceMemberArgumentNode : IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode Type { get; set; }
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Format() => $"{Type.Format()} {Name}";
public override string ToString() => "Argument " + Format();
public AstInterfaceMemberArgumentNode Clone() => new AstInterfaceMemberArgumentNode
{
Name = Name, Type = Type.Clone(), Attributes = Attributes.Clone()
};
}
public static class AstExtensions
{
public static bool HasAttribute(this IAstNodeWithAttributes node, string s) => node.Attributes.HasAttribute(s);
public static string GetAttribute(this IAstNodeWithAttributes node, string s)
{
var value = node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
if (value == null)
throw new CodeGenException("Expected attribute " + s + " for node " + node);
return value;
}
public static string GetAttributeOrDefault(this IAstNodeWithAttributes node, string s)
=> node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
}
class AstVisitor
{
protected virtual void VisitType(AstTypeNode type)
{
}
protected virtual void VisitArgument(AstInterfaceMemberArgumentNode argument)
{
VisitType(argument.Type);
}
protected virtual void VisitInterfaceMember(AstInterfaceMemberNode member)
{
foreach(var a in member)
VisitArgument(a);
VisitType(member.ReturnType);
}
protected virtual void VisitInterface(AstInterfaceNode iface)
{
foreach(var m in iface)
VisitInterfaceMember(m);
}
protected virtual void VisitStructMember(AstStructMemberNode member)
{
VisitType(member.Type);
}
protected virtual void VisitStruct(AstStructNode node)
{
foreach(var m in node)
VisitStructMember(m);
}
public virtual void VisitAst(AstIdlNode ast)
{
foreach(var iface in ast.Interfaces)
VisitInterface(iface);
foreach (var s in ast.Structs)
VisitStruct(s);
}
}
public class AstIdlNode : IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public List<AstEnumNode> Enums { get; set; } = new List<AstEnumNode>();
public List<AstStructNode> Structs { get; set; } = new List<AstStructNode>();
public List<AstInterfaceNode> Interfaces { get; set; } = new List<AstInterfaceNode>();
public AstIdlNode Clone() => new AstIdlNode()
{
Attributes = Attributes.Clone(),
Enums = Enums.Select(x => x.Clone()).ToList(),
Structs = Structs.Select(x => x.Clone()).ToList(),
Interfaces = Interfaces.Select(x => x.Clone()).ToList()
};
}
}

232
src/tools/MicroComGenerator/AstParser.cs

@ -1,232 +0,0 @@
using System.Collections.Generic;
using MicroComGenerator.Ast;
namespace MicroComGenerator
{
public class AstParser
{
public static AstIdlNode Parse(string source)
{
var parser = new TokenParser(source);
var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) };
while (!parser.Eof)
{
var attrs = ParseLocalAttributes(ref parser);
if (parser.TryConsume(";"))
continue;
if (parser.TryParseKeyword("enum"))
idl.Enums.Add(ParseEnum(attrs, ref parser));
else if (parser.TryParseKeyword("struct"))
idl.Structs.Add(ParseStruct(attrs, ref parser));
else if (parser.TryParseKeyword("interface"))
idl.Interfaces.Add(ParseInterface(attrs, ref parser));
else
throw new ParseException("Unexpected character", ref parser);
}
return idl;
}
static AstAttributes ParseGlobalAttributes(ref TokenParser parser)
{
var rv = new AstAttributes();
while (!parser.Eof)
{
parser.SkipWhitespace();
if (parser.TryConsume('@'))
{
var ident = parser.ParseIdentifier("-");
var value = parser.ReadToEol().Trim();
if (value == "@@")
{
parser.Advance(1);
value = "";
while (true)
{
var l = parser.ReadToEol();
if (l == "@@")
break;
else
value = value.Length == 0 ? l : (value + "\n" + l);
parser.Advance(1);
}
}
rv.Add(new AstAttributeNode(ident, value));
}
else
return rv;
}
return rv;
}
static AstAttributes ParseLocalAttributes(ref TokenParser parser)
{
var rv = new AstAttributes();
while (parser.TryConsume("["))
{
while (!parser.TryConsume("]") && !parser.Eof)
{
if (parser.TryConsume(','))
continue;
// Get identifier
var ident = parser.ParseIdentifier("-");
// No value, end of attribute list
if (parser.TryConsume(']'))
{
rv.Add(new AstAttributeNode(ident, null));
break;
}
// No value, next attribute
else if (parser.TryConsume(','))
rv.Add(new AstAttributeNode(ident, null));
// Has value
else if (parser.TryConsume('('))
{
var value = parser.ReadTo(')');
parser.Consume(')');
rv.Add(new AstAttributeNode(ident, value));
}
else
throw new ParseException("Unexpected character", ref parser);
}
if (parser.Eof)
throw new ParseException("Unexpected EOF", ref parser);
}
return rv;
}
static void EnsureOpenBracket(ref TokenParser parser)
{
if (!parser.TryConsume('{'))
throw new ParseException("{ expected", ref parser);
}
static AstEnumNode ParseEnum(AstAttributes attrs, ref TokenParser parser)
{
var name = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstEnumNode { Name = name, Attributes = attrs };
while (!parser.TryConsume('}') && !parser.Eof)
{
if (parser.TryConsume(','))
continue;
var ident = parser.ParseIdentifier();
// Automatic value
if (parser.TryConsume(',') || parser.Peek == '}')
{
rv.Add(new AstEnumMemberNode(ident, null));
continue;
}
if (!parser.TryConsume('='))
throw new ParseException("Unexpected character", ref parser);
var value = parser.ReadToAny(",}").Trim();
rv.Add(new AstEnumMemberNode(ident, value));
if (parser.Eof)
throw new ParseException("Unexpected EOF", ref parser);
}
return rv;
}
static AstTypeNode ParseType(ref TokenParser parser)
{
var ident = parser.ParseIdentifier();
var t = new AstTypeNode { Name = ident };
while (parser.TryConsume('*'))
t.PointerLevel++;
if (parser.TryConsume("&"))
t.IsLink = true;
return t;
}
static AstStructNode ParseStruct(AstAttributes attrs, ref TokenParser parser)
{
var name = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstStructNode { Name = name, Attributes = attrs };
while (!parser.TryConsume('}') && !parser.Eof)
{
var memberAttrs = ParseLocalAttributes(ref parser);
var t = ParseType(ref parser);
bool parsedAtLeastOneMember = false;
while (!parser.TryConsume(';'))
{
// Skip any ,
while (parser.TryConsume(',')) { }
var ident = parser.ParseIdentifier();
parsedAtLeastOneMember = true;
rv.Add(new AstStructMemberNode { Name = ident, Type = t, Attributes = memberAttrs});
}
if (!parsedAtLeastOneMember)
throw new ParseException("Expected at least one enum member with declared type " + t, ref parser);
}
return rv;
}
static AstInterfaceNode ParseInterface(AstAttributes interfaceAttrs, ref TokenParser parser)
{
var interfaceName = parser.ParseIdentifier();
string inheritsFrom = null;
if (parser.TryConsume(":"))
inheritsFrom = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstInterfaceNode
{
Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom
};
while (!parser.TryConsume('}') && !parser.Eof)
{
var memberAttrs = ParseLocalAttributes(ref parser);
var returnType = ParseType(ref parser);
var name = parser.ParseIdentifier();
var member = new AstInterfaceMemberNode
{
Name = name, ReturnType = returnType, Attributes = memberAttrs
};
rv.Add(member);
parser.Consume('(');
while (true)
{
if (parser.TryConsume(')'))
break;
var argumentAttrs = ParseLocalAttributes(ref parser);
var type = ParseType(ref parser);
var argName = parser.ParseIdentifier();
member.Add(new AstInterfaceMemberArgumentNode
{
Name = argName, Type = type, Attributes = argumentAttrs
});
if (parser.TryConsume(')'))
break;
if (parser.TryConsume(','))
continue;
throw new ParseException("Unexpected character", ref parser);
}
parser.Consume(';');
}
return rv;
}
}
}

484
src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs

@ -1,484 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ReSharper disable CoVariantArrayConversion
// HERE BE DRAGONS
namespace MicroComGenerator
{
public partial class CSharpGen
{
abstract class Arg
{
public string Name;
public string NativeType;
public AstAttributes Attributes { get; set; }
public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner;
public virtual void PreMarshal(List<StatementSyntax> body)
{
}
public virtual void PreMarshalForReturn(List<StatementSyntax> body) =>
throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return");
public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name);
public abstract string ManagedType { get; }
public virtual string ReturnManagedType => ManagedType;
public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") };
public virtual void BackPreMarshal(List<StatementSyntax> body)
{
}
public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name);
public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar);
}
class InterfaceReturnArg : Arg
{
public string InterfaceType;
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName);
public override string ManagedType => InterfaceType;
private string PName => "__marshal_" + Name;
public override void PreMarshalForReturn(List<StatementSyntax> body)
{
body.Add(ParseStatement("void* " + PName + " = null;"));
}
public override StatementSyntax[] ReturnMarshalResult() => new[]
{
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
PName + ", true);")
};
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression("INVALID");
}
public override ExpressionSyntax BackMarshalReturn(string resultVar)
{
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
}
}
class InterfaceArg : Arg
{
public string InterfaceType;
public override ExpressionSyntax Value(bool isHresultReturn) =>
ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")");
public override string ManagedType => InterfaceType;
public override StatementSyntax[] ReturnMarshalResult() => new[]
{
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
Name + ", true);")
};
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
Name + ", false)");
}
public override ExpressionSyntax BackMarshalReturn(string resultVar)
{
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
}
}
class BypassArg : Arg
{
public string Type { get; set; }
public int PointerLevel;
public override string ManagedType => Type + new string('*', PointerLevel);
public override string ReturnManagedType => Type + new string('*', PointerLevel - 1);
public override ExpressionSyntax Value(bool isHresultReturn)
{
if (isHresultReturn)
return ParseExpression("&" + Name);
return base.Value(false);
}
public override void PreMarshalForReturn(List<StatementSyntax> body)
{
if (PointerLevel == 0)
base.PreMarshalForReturn(body);
else
body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;"));
}
}
class StringArg : Arg
{
private string BName => "__bytemarshal_" + Name;
private string FName => "__fixedmarshal_" + Name;
public override void PreMarshal(List<StatementSyntax> body)
{
body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];"));
body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name}.Length, {BName}, 0);"));
}
public override StatementSyntax CreateFixed(StatementSyntax inner)
{
return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner);
}
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName);
public override string ManagedType => "string";
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression(
$"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))");
}
}
string ConvertNativeType(string type)
{
if (type == "size_t")
return "System.IntPtr";
if (type == "HRESULT")
return "int";
return type;
}
Arg ConvertArg(AstInterfaceMemberArgumentNode node)
{
var arg = ConvertArg(node.Name, node.Type);
arg.Attributes = node.Attributes.Clone();
return arg;
}
Arg ConvertArg(string name, AstTypeNode type)
{
type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel };
if (type.PointerLevel == 2)
{
if (IsInterface(type))
return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" };
}
else if (type.PointerLevel == 1)
{
if (IsInterface(type))
return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" };
if (type.Name == "char")
return new StringArg { Name = name, NativeType = "byte*" };
}
return new BypassArg
{
Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString()
};
}
void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface,
ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl,
List<StatementSyntax> vtblCtor, int num)
{
// Prepare method information
if (member.Name == "GetRenderingDevice")
Console.WriteLine();
var args = member.Select(ConvertArg).ToList();
var returnArg = ConvertArg("__result", member.ReturnType);
bool isHresult = member.ReturnType.Name == "HRESULT";
bool isHresultLastArgumentReturn = isHresult
&& args.Count > 0
&& (args.Last().Name == "ppv"
|| args.Last().Name == "retOut"
|| args.Last().Name == "ret"
|| args.Last().Attributes.HasAttribute("out")
|| args.Last().Attributes.HasAttribute("retval")
)
&& ((member.Last().Type.PointerLevel > 0
&& !IsInterface(member.Last().Type))
|| member.Last().Type.PointerLevel == 2);
bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0;
// Generate method signature
MethodDeclarationSyntax GenerateManagedSig(string returnType, string name,
IEnumerable<(string n, string t)> args)
=> MethodDeclaration(ParseTypeName(returnType), name).WithParameterList(
ParameterList(
SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t))))));
var managedSig =
isHresult ?
GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void",
member.Name,
(isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) :
GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType)));
iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon()));
// Prepare args for marshaling
var preMarshal = new List<StatementSyntax>();
if (!isVoidReturn)
preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;"));
for (var idx = 0; idx < args.Count; idx++)
{
if (isHresultLastArgumentReturn && idx == args.Count - 1)
args[idx].PreMarshalForReturn(preMarshal);
else
args[idx].PreMarshal(preMarshal);
}
// Generate call expression
ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType,
args.Select(x => x.NativeType).ToList()))
.AddArgumentListArguments(Argument(ParseExpression("PPV")))
.AddArgumentListArguments(args
.Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray())
.AddArgumentListArguments(Argument(ParseExpression("(*PPV)[base.VTableSize + " + num + "]")));
if (!isVoidReturn)
callExpr = CastExpression(ParseTypeName(returnArg.NativeType), callExpr);
// Save call result if needed
if (!isVoidReturn)
callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"),
callExpr);
// Wrap call into fixed() blocks
StatementSyntax callStatement = ExpressionStatement(callExpr);
foreach (var arg in args)
callStatement = arg.CreateFixed(callStatement);
// Build proxy body
var proxyBody = Block()
.AddStatements(preMarshal.ToArray())
.AddStatements(callStatement);
// Process return value
if (!isVoidReturn)
{
if (isHresult)
{
proxyBody = proxyBody.AddStatements(
ParseStatement(
$"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);"));
if (isHresultLastArgumentReturn)
proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult());
}
else
proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult());
}
// Add the proxy method
proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword)
.WithBody(proxyBody));
// Generate VTable method
var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name + "Delegate")
.AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr")))
.AddParameterListParameters(args.Select(x =>
Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray())
.AddAttribute("System.Runtime.InteropServices.UnmanagedFunctionPointer",
"System.Runtime.InteropServices.CallingConvention.StdCall");
var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name)
.WithParameterList(shadowDelegate.ParameterList)
.AddModifiers(Token(SyntaxKind.StaticKeyword));
var backPreMarshal = new List<StatementSyntax>();
foreach (var arg in args)
arg.BackPreMarshal(backPreMarshal);
backPreMarshal.Add(
ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);"));
var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn);
StatementSyntax backCallStatement;
var backCallExpr =
IsPropertyRewriteCandidate(managedSig) ?
ParseExpression("__target." + member.Name.Substring(3)) :
InvocationExpression(ParseExpression("__target." + member.Name))
.WithArgumentList(ArgumentList(SeparatedList(
(isHresultLastArgumentReturn ? args.SkipLast(1) : args)
.Select(a =>
Argument(a.BackMarshalValue())))));
if (isBackVoidReturn)
backCallStatement = ExpressionStatement(backCallExpr);
else
{
backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr));
if (isHresultLastArgumentReturn)
{
backCallStatement = Block(backCallStatement,
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
ParseExpression("*" + args.Last().Name),
args.Last().BackMarshalReturn("__result")
)));
}
else
backCallStatement = Block(backCallStatement,
ReturnStatement(returnArg.BackMarshalReturn("__result")));
}
BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement);
var exceptions = new List<CatchClauseSyntax>()
{
CatchClause(
CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null,
Block(
ParseStatement(
"Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"),
isHresult ? ParseStatement("return unchecked((int)0x80004005u);")
: isVoidReturn ? EmptyStatement() : ParseStatement("return default;")
))
};
if (isHresult)
exceptions.Insert(0, CatchClause(
CatchDeclaration(ParseTypeName("System.Runtime.InteropServices.COMException"),
Identifier("__com_exception__")),
null, Block(ParseStatement("return __com_exception__.ErrorCode;"))));
backBodyBlock = Block(
TryStatement(
List(exceptions))
.WithBlock(Block(backBodyBlock))
);
if (isHresult)
backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;"));
backBodyBlock = Block()
.AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;"))
.AddStatements(backBodyBlock.Statements.ToArray());
shadowMethod = shadowMethod.WithBody(backBodyBlock);
vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod);
vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" +
shadowMethod.Identifier.Text + ");"));
}
class LocalInteropHelper
{
public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop");
private HashSet<string> _existing = new HashSet<string>();
public ExpressionSyntax GetCaller(string returnType, List<string> args)
{
string ConvertType(string t) => t.EndsWith("*") ? "void*" : t;
returnType = ConvertType(returnType);
args = args.Select(ConvertType).ToList();
var name = "CalliStdCall" + returnType.Replace("*", "_ptr");
var signature = returnType + "::" + name + "::" + string.Join("::", args);
if (_existing.Add(signature))
{
Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name)
.AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword)
.AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*")))
.AddParameterListParameters(args.Select((x, i) =>
Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray())
.AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*")))
.WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null"))))));
}
return ParseExpression("LocalInterop." + name);
}
}
void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs,
AstInterfaceNode iface)
{
var guidString = iface.GetAttribute("uuid");
var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown";
var ifaceDec = InterfaceDeclaration(iface.Name)
.WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits)
.AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword));
var proxyClassName = "__MicroCom" + iface.Name + "Proxy";
var proxy = ClassDeclaration(proxyClassName)
.AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility), Token(SyntaxKind.PartialKeyword))
.WithBaseType(inheritsUnknown ?
"Avalonia.MicroCom.MicroComProxyBase" :
("__MicroCom" + iface.Inherits + "Proxy"))
.AddBaseListTypes(SimpleBaseType(ParseTypeName(iface.Name)));
// Generate vtable
var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable")
.AddModifiers(Token(SyntaxKind.UnsafeKeyword));
vtbl = vtbl.WithBaseType(inheritsUnknown ?
"Avalonia.MicroCom.MicroComVtblBase" :
"__MicroCom" + iface.Inherits + "VTable");
var vtblCtor = new List<StatementSyntax>();
for (var idx = 0; idx < iface.Count; idx++)
GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx);
vtbl = vtbl.AddMembers(
ConstructorDeclaration(vtbl.Identifier.Text)
.AddModifiers(Token(SyntaxKind.PublicKeyword))
.WithBody(Block(vtblCtor))
)
.AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
.WithExpressionBody(ArrowExpressionClause(
ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" +
iface.Name + "), new " + vtbl.Identifier.Text + "().CreateVTable())")))
.WithSemicolonToken(Semicolon()));
// Finalize proxy code
proxy = proxy.AddMembers(
MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
.WithBody(Block(
ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" +
iface.Name + "), new Guid(\"" + guidString + "\"), (p, owns) => new " +
proxyClassName + "(p, owns));")
)))
.AddMembers(ParseMemberDeclaration("public " + proxyClassName +
"(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}"))
.AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " +
iface.Count + ";"));
ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec));
implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl));
}
}
}

111
src/tools/MicroComGenerator/CSharpGen.Utils.cs

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace MicroComGenerator
{
public partial class CSharpGen
{
CompilationUnitSyntax Unit()
=> CompilationUnit().WithUsings(List(new[]
{
"System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom"
}
.Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u)))));
string Format(CompilationUnitSyntax unit)
{
var cw = new AdhocWorkspace();
return
"#pragma warning disable 108\n" +
Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true)
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers,
true)
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true)
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true)
).ToFullString();
}
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
static VariableDeclarationSyntax DeclareVar(string type, string name,
ExpressionSyntax initializer = null)
=> VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(VariableDeclarator(name)
.WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));
FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value)
=> FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(EqualsValueClause(value))
))
).WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)));
FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) =>
DeclareField(type, name, null, modifiers);
FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer,
params SyntaxKind[] modifiers) =>
FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(initializer))))
.WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(modifiers.Select(x => Token(x))));
bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method)
{
return
method.ReturnType.ToFullString() != "void"
&& method.Identifier.Text.StartsWith("Get")
&& method.ParameterList.Parameters.Count == 0;
}
TypeDeclarationSyntax RewriteMethodsToProperties<T>(T decl) where T : TypeDeclarationSyntax
{
var replace = new Dictionary<MethodDeclarationSyntax, PropertyDeclarationSyntax>();
foreach (var method in decl.Members.OfType<MethodDeclarationSyntax>().ToList())
{
if (IsPropertyRewriteCandidate(method))
{
var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration);
if (method.Body != null)
getter = getter.WithBody(method.Body);
else
getter = getter.WithSemicolonToken(Semicolon());
replace[method] = PropertyDeclaration(method.ReturnType,
method.Identifier.Text.Substring(3))
.WithModifiers(method.Modifiers).AddAccessorListAccessors(getter);
}
}
return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]);
}
bool IsInterface(string name)
{
if (name == "IUnknown")
return true;
return _idl.Interfaces.Any(i => i.Name == name);
}
private bool IsInterface(AstTypeNode type) => IsInterface(type.Name);
}
}

155
src/tools/MicroComGenerator/CSharpGen.cs

@ -1,155 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ReSharper disable CoVariantArrayConversion
namespace MicroComGenerator
{
public partial class CSharpGen
{
private readonly AstIdlNode _idl;
private List<string> _extraUsings;
private string _namespace;
private SyntaxKind _visibility;
private LocalInteropHelper _localInterop = new LocalInteropHelper();
public CSharpGen(AstIdlNode idl)
{
_idl = idl.Clone();
new AstRewriter(_idl.Attributes.Where(a => a.Name == "clr-map")
.Select(x => x.Value.Trim().Split(' '))
.ToDictionary(x => x[0], x => x[1])
).VisitAst(_idl);
_extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList();
_namespace = _idl.GetAttribute("clr-namespace");
var visibilityString = _idl.GetAttribute("clr-access");
if (visibilityString == "internal")
_visibility = SyntaxKind.InternalKeyword;
else if (visibilityString == "public")
_visibility = SyntaxKind.PublicKeyword;
else
throw new CodeGenException("Invalid clr-access attribute");
}
class AstRewriter : AstVisitor
{
private readonly Dictionary<string, string> _typeMap = new Dictionary<string, string>();
public AstRewriter(Dictionary<string, string> typeMap)
{
_typeMap = typeMap;
}
void ConvertIntPtr(AstTypeNode type)
{
if (type.Name == "void" && type.PointerLevel > 0)
{
type.Name = "IntPtr";
type.PointerLevel--;
}
}
protected override void VisitStructMember(AstStructMemberNode member)
{
if (member.HasAttribute("intptr"))
ConvertIntPtr(member.Type);
base.VisitStructMember(member);
}
protected override void VisitType(AstTypeNode type)
{
if (type.IsLink)
{
type.PointerLevel++;
type.IsLink = false;
}
if (_typeMap.TryGetValue(type.Name, out var mapped))
type.Name = mapped;
base.VisitType(type);
}
protected override void VisitArgument(AstInterfaceMemberArgumentNode argument)
{
if (argument.HasAttribute("intptr"))
{
if(argument.Name == "retOut")
Console.WriteLine();
ConvertIntPtr(argument.Type);
}
base.VisitArgument(argument);
}
protected override void VisitInterfaceMember(AstInterfaceMemberNode member)
{
if (member.HasAttribute("intptr"))
ConvertIntPtr(member.ReturnType);
if (member.HasAttribute("propget") && !member.Name.StartsWith("Get"))
member.Name = "Get" + member.Name;
if (member.HasAttribute("propput") && !member.Name.StartsWith("Set"))
member.Name = "Set" + member.Name;
base.VisitInterfaceMember(member);
}
}
public string Generate()
{
var ns = NamespaceDeclaration(ParseName(_namespace));
var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl"));
ns = GenerateEnums(ns);
ns = GenerateStructs(ns);
foreach (var i in _idl.Interfaces)
GenerateInterface(ref ns, ref implNs, i);
implNs = implNs.AddMembers(_localInterop.Class);
var unit = Unit().AddMembers(ns, implNs);
return Format(unit);
}
NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns)
{
return ns.AddMembers(_idl.Enums.Select(e =>
{
var dec = EnumDeclaration(e.Name)
.WithModifiers(TokenList(Token(_visibility)))
.WithMembers(SeparatedList(e.Select(m =>
{
var member = EnumMemberDeclaration(m.Name);
if (m.Value != null)
return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value)));
return member;
})));
if (e.HasAttribute("flags"))
dec = dec.AddAttribute("System.Flags");
return dec;
}).ToArray());
}
NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns)
{
return ns.AddMembers(_idl.Structs.Select(e =>
StructDeclaration(e.Name)
.WithModifiers(TokenList(Token(_visibility)))
.AddAttribute("System.Runtime.InteropServices.StructLayout", "System.Runtime.InteropServices.LayoutKind.Sequential")
.AddModifiers(Token(SyntaxKind.UnsafeKeyword))
.WithMembers(new SyntaxList<MemberDeclarationSyntax>(SeparatedList(e.Select(m =>
DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword)))))
).ToArray());
}
}
}

119
src/tools/MicroComGenerator/CppGen.cs

@ -1,119 +0,0 @@
using System;
using System.Linq;
using System.Text;
using MicroComGenerator.Ast;
namespace MicroComGenerator
{
public class CppGen
{
static string ConvertType(AstTypeNode type)
{
var name = type.Name;
if (name == "byte")
name = "unsigned char";
else if(name == "uint")
name = "unsigned int";
type = type.Clone();
type.Name = name;
return type.Format();
}
public static string GenerateCpp(AstIdlNode idl)
{
var sb = new StringBuilder();
var preamble = idl.GetAttributeOrDefault("cpp-preamble");
if (preamble != null)
sb.AppendLine(preamble);
foreach (var s in idl.Structs)
sb.AppendLine("struct " + s.Name + ";");
foreach (var s in idl.Interfaces)
sb.AppendLine("struct " + s.Name + ";");
foreach (var en in idl.Enums)
{
sb.Append("enum ");
if (en.Attributes.Any(a => a.Name == "class-enum"))
sb.Append("class ");
sb.AppendLine(en.Name).AppendLine("{");
foreach (var m in en)
{
sb.Append(" ").Append(m.Name);
if (m.Value != null)
sb.Append(" = ").Append(m.Value);
sb.AppendLine(",");
}
sb.AppendLine("};");
}
foreach (var s in idl.Structs)
{
sb.Append("struct ").AppendLine(s.Name).AppendLine("{");
foreach (var m in s)
sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";");
sb.AppendLine("};");
}
foreach (var i in idl.Interfaces)
{
var guidString = i.GetAttribute("uuid");
var guid = Guid.Parse(guidString).ToString().Replace("-", "");
sb.Append("COMINTERFACE(").Append(i.Name).Append(", ")
.Append(guid.Substring(0, 8)).Append(", ")
.Append(guid.Substring(8, 4)).Append(", ")
.Append(guid.Substring(12, 4));
for (var c = 0; c < 8; c++)
{
sb.Append(", ").Append(guid.Substring(16 + c * 2, 2));
}
sb.Append(") : ");
if (i.HasAttribute("cpp-virtual-inherits"))
sb.Append("virtual ");
sb.AppendLine(i.Inherits ?? "IUnknown")
.AppendLine("{");
foreach (var m in i)
{
sb.Append(" ")
.Append("virtual ")
.Append(ConvertType(m.ReturnType))
.Append(" ").Append(m.Name).Append(" (");
if (m.Count == 0)
sb.AppendLine(") = 0;");
else
{
sb.AppendLine();
for (var c = 0; c < m.Count; c++)
{
var arg = m[c];
sb.Append(" ");
if (arg.Attributes.Any(a => a.Name == "const"))
sb.Append("const ");
sb.Append(ConvertType(arg.Type))
.Append(" ")
.Append(arg.Name);
if (c != m.Count - 1)
sb.Append(", ");
sb.AppendLine();
}
sb.AppendLine(" ) = 0;");
}
}
sb.AppendLine("};");
}
return sb.ToString();
}
}
}

97
src/tools/MicroComGenerator/Extensions.cs

@ -1,97 +0,0 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace MicroComGenerator
{
public static class Extensions
{
public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static string WithLowerFirst(this string s)
{
if (string.IsNullOrEmpty(s))
return s;
return char.ToLowerInvariant(s[0]) + s.Substring(1);
}
public static ExpressionSyntax MemberAccess(params string[] identifiers)
{
if (identifiers == null || identifiers.Length == 0)
throw new ArgumentException();
var expr = (ExpressionSyntax)IdentifierName(identifiers[0]);
for (var c = 1; c < identifiers.Length; c++)
expr = MemberAccess(expr, identifiers[c]);
return expr;
}
public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers)
{
foreach (var i in identifiers)
expr = MemberAccess(expr, i);
return expr;
}
public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) =>
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier));
public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt)
{
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
}
public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt)
{
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
}
public static T AddAttribute<T>(this T member, string attribute, params string[] args) where T : MemberDeclarationSyntax
{
return (T)member.AddAttributeLists(AttributeList(SingletonSeparatedList(
Attribute(ParseName(attribute), AttributeArgumentList(
SeparatedList(args.Select(a => AttributeArgument(ParseExpression(a)))))))));
}
public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s)
? s
: s.StartsWith(prefix)
? s.Substring(prefix.Length)
: s;
}
}

10
src/tools/MicroComGenerator/MicroComGenerator.csproj

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
</ItemGroup>
</Project>

27
src/tools/MicroComGenerator/ParseException.cs

@ -1,27 +0,0 @@
using System;
namespace MicroComGenerator
{
class ParseException : Exception
{
public int Line { get; }
public int Position { get; }
public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}")
{
Line = line;
Position = position;
}
public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position)
{
}
}
class CodeGenException : Exception
{
public CodeGenException(string message) : base(message)
{
}
}
}

52
src/tools/MicroComGenerator/Program.cs

@ -1,52 +0,0 @@
using System;
using System.IO;
using System.Linq;
using CommandLine;
namespace MicroComGenerator
{
class Program
{
public class Options
{
[Option('i', "input", Required = true, HelpText = "Input IDL file")]
public string Input { get; set; }
[Option("cpp", Required = false, HelpText = "C++ output file")]
public string CppOutput { get; set; }
[Option("cs", Required = false, HelpText = "C# output file")]
public string CSharpOutput { get; set; }
}
static int Main(string[] args)
{
var p = Parser.Default.ParseArguments<Options>(args);
if (p is NotParsed<Options>)
{
return 1;
}
var opts = ((Parsed<Options>)p).Value;
var text = File.ReadAllText(opts.Input);
var ast = AstParser.Parse(text);
if (opts.CppOutput != null)
File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast));
if (opts.CSharpOutput != null)
{
File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate());
// HACK: Can't work out how to get the VS project system's fast up-to-date checks
// to ignore the generated code, so as a workaround set the write time to that of
// the input.
File.SetLastWriteTime(opts.CSharpOutput, File.GetLastWriteTime(opts.Input));
}
return 0;
}
}
}

417
src/tools/MicroComGenerator/TokenParser.cs

@ -1,417 +0,0 @@
using System;
using System.Globalization;
using System.IO;
namespace MicroComGenerator
{
internal ref struct TokenParser
{
private ReadOnlySpan<char> _s;
public int Position { get; private set; }
public int Line { get; private set; }
public TokenParser(ReadOnlySpan<char> s)
{
_s = s;
Position = 0;
Line = 0;
}
public void SkipWhitespace()
{
while (true)
{
if(_s.Length == 0)
return;
if (char.IsWhiteSpace(_s[0]))
Advance(1);
else if (_s[0] == '/' && _s.Length>1)
{
if (_s[1] == '/')
SkipOneLineComment();
else if (_s[1] == '*')
SkipMultiLineComment();
else
return;
}
else
return;
}
}
void SkipOneLineComment()
{
while (true)
{
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
Advance(1);
else
return;
}
}
void SkipMultiLineComment()
{
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("No matched */ found for /*", l, p);
if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/')
{
Advance(2);
return;
}
Advance(1);
}
}
static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z');
public void Consume(char c)
{
if (!TryConsume(c))
throw new ParseException("Expected " + c, Line, Position);
}
public bool TryConsume(char c)
{
SkipWhitespace();
if (_s.Length == 0 || _s[0] != c)
return false;
Advance(1);
return true;
}
public bool TryConsume(string s)
{
SkipWhitespace();
if (_s.Length < s.Length)
return false;
for (var c = 0; c < s.Length; c++)
{
if (_s[c] != s[c])
return false;
}
Advance(s.Length);
return true;
}
public bool TryConsumeAny(ReadOnlySpan<char> chars, out char token)
{
SkipWhitespace();
token = default;
if (_s.Length == 0)
return false;
foreach (var c in chars)
{
if (c == _s[0])
{
token = c;
Advance(1);
return true;
}
}
return false;
}
public bool TryParseKeyword(string keyword)
{
SkipWhitespace();
if (keyword.Length > _s.Length)
return false;
for(var c=0; c<keyword.Length;c++)
if (keyword[c] != _s[c])
return false;
if (_s.Length > keyword.Length && IsAlphaNumeric(_s[keyword.Length]))
return false;
Advance(keyword.Length);
return true;
}
public bool TryParseKeywordLowerCase(string keywordInLowerCase)
{
SkipWhitespace();
if (keywordInLowerCase.Length > _s.Length)
return false;
for(var c=0; c<keywordInLowerCase.Length;c++)
if (keywordInLowerCase[c] != char.ToLowerInvariant(_s[c]))
return false;
if (_s.Length > keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length]))
return false;
Advance(keywordInLowerCase.Length);
return true;
}
public void Advance(int c)
{
while (c > 0)
{
if (_s[0] == '\n')
{
Line++;
Position = 0;
}
else
Position++;
_s = _s.Slice(1);
c--;
}
}
public int Length => _s.Length;
public bool Eof
{
get
{
SkipWhitespace();
return Length == 0;
}
}
public char Peek
{
get
{
if (_s.Length == 0)
throw new ParseException("Unexpected EOF", Line, Position);
return _s[0];
}
}
public string ParseIdentifier(ReadOnlySpan<char> extraValidChars)
{
if (!TryParseIdentifier(extraValidChars, out var ident))
throw new ParseException("Identifier expected", Line, Position);
return ident.ToString();
}
public string ParseIdentifier()
{
if (!TryParseIdentifier(out var ident))
throw new ParseException("Identifier expected", Line, Position);
return ident.ToString();
}
public bool TryParseIdentifier(ReadOnlySpan<char> extraValidChars, out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch) || ch == '_')
len++;
else
{
var found = false;
foreach(var vc in extraValidChars)
if (vc == ch)
{
found = true;
break;
}
if (found)
len++;
else
break;
}
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public bool TryParseIdentifier(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch) || ch == '_')
len++;
else
break;
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public string ReadToEol()
{
var initial = _s;
var len = 0;
while (true)
{
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public string ReadTo(char c)
{
var initial = _s;
var len = 0;
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("Expected " + c + " before EOF", l, p);
if (_s[0] != c)
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public string ReadToAny(ReadOnlySpan<char> chars)
{
var initial = _s;
var len = 0;
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p);
var foundTerminator = false;
foreach (var term in chars)
{
if (_s[0] == term)
{
foundTerminator = true;
break;
}
}
if (!foundTerminator)
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public bool TryParseCall(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.')
len++;
else
break;
}
res = _s.Slice(0, len);
// Find '('
for (var c = len; c < _s.Length; c++)
{
if(char.IsWhiteSpace(_s[c]))
continue;
if(_s[c]=='(')
{
Advance(c + 1);
return true;
}
return false;
}
return false;
}
public bool TryParseFloat(out float res)
{
res = 0;
SkipWhitespace();
if (_s.Length == 0)
return false;
var len = 0;
var dotCount = 0;
for (var c = 0; c < _s.Length; c++)
{
var ch = _s[c];
if (ch >= '0' && ch <= '9')
len = c + 1;
else if (ch == '.' && dotCount == 0)
{
len = c + 1;
dotCount++;
}
else
break;
}
var span = _s.Slice(0, len);
#if NETSTANDARD2_0
if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#else
if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#endif
Advance(len);
return true;
}
public override string ToString() => _s.ToString();
}
}

54
tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
using Xunit;
namespace Avalonia.Animation.UnitTests
{
public class BrushTransitionTests
{
[Fact]
public void SolidColorBrush_Opacity_IsInteroplated()
{
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 0 });
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
Test(0.5, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
Test(0.5, new SolidColorBrush { Opacity = 0.5 }, new SolidColorBrush { Opacity = 0.5 });
Test(1, new SolidColorBrush { Opacity = 1 }, new SolidColorBrush { Opacity = 1 });
// TODO: investigate why this case fails.
//Test2(1, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
}
[Fact]
public void LinearGradientBrush_Opacity_IsInteroplated()
{
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 0 });
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 });
Test(0.5, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 });
Test(0.5, new LinearGradientBrush { Opacity = 0.5 }, new LinearGradientBrush { Opacity = 0.5 });
Test(1, new LinearGradientBrush { Opacity = 1 }, new LinearGradientBrush { Opacity = 1 });
}
private static void Test(double progress, IBrush oldBrush, IBrush newBrush)
{
var clock = new TestClock();
var border = new Border() { Background = oldBrush };
BrushTransition sut = new BrushTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.BackgroundProperty
};
sut.Apply(border, clock, oldBrush, newBrush);
clock.Pulse(TimeSpan.Zero);
clock.Pulse(sut.Duration * progress);
Assert.NotNull(border.Background);
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress, border.Background.Opacity);
}
}
}
Loading…
Cancel
Save