From 6351cfcda175ea2ae5852d66c5dba7e18ea931bf Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 15 Mar 2022 15:53:30 +0200 Subject: [PATCH] merge release/0.10.13 Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 345ff4417e788fbe40cb0338f1e0307797f3a25a Author: Dan Walmsley Date: Wed Mar 2 18:31:16 2022 +0000 bump version. commit 892960c83d3c5a0722f060fe4f4bb15e7620c807 Author: Max Katz 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 d8848478eaa278b569fa86f93bf2a6f37b50f9fe Author: Dan Walmsley Date: Wed Mar 2 16:48:43 2022 +0000 Merge pull request #7723 from AvaloniaUI/fix-expander Fix expander header stretching commit 2eb51b8672114da35cd26d89900c35f077d71ab4 Author: Dan Walmsley 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 84f04f429b6f8a3c6c66ecf8c34da99b846d2240 Author: Max Katz 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 5b4f5da12725a5216f8a4649d4e4a77621d97936 Author: Max Katz 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 d5359603ace169de3f706a8fbe146ef2096b352e Author: Max Katz 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 480dfdfe0d0a5151583b8e809e695908548a07bb Author: Nikita Tsukanov 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 3f11b014a616164c291a7219e0899954392ef51e Author: Max Katz 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 870f62fb8feb245f9cb5fa4260ea81d94bbe3988 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 7d771c86e63fc95e969e6223a1dd1e7fc4dde986 Author: Nikita Tsukanov Date: Fri Jan 28 12:38:54 2022 +0300 Merge pull request #7455 from Mikolaytis/DeadlockFix [Deadlock] Fix Monitor.Enter in finally commit 859793b12241d17314006663af8ebd395fcb7bf6 Author: Max Katz 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 256bba51f7bc9e351ab3d6e5cbd9eb7dcb100c38 Author: Max Katz 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 039fa5b7add775241d9378decc17da5e0d727cab Author: Max Katz Date: Tue Feb 15 15:22:22 2022 -0500 Merge pull request #7520 from timunie/fix/ScrollViewerShiftAndPointerWheel Fix [Shift] + [PointerWheel] should scroll horizontally commit 2c53e06c2631716cb30103ffa279f71a437b2df6 Author: Olivier DALET Date: Fri Feb 4 17:55:12 2022 +0100 Fix #7519 - Reset fb and depth buffer Ids once they are deleted commit 8bc795be0411198324ffbd6de4aac0720c40aa9a Author: Andrii Kurdiumov 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 7b4462163abda85d06b21cd699bce51c26f93c3a Author: Dan Walmsley Date: Mon Feb 7 22:39:02 2022 +0000 Merge pull request #7537 from fr-Pursuit/master Minimization bugfix on Windows commit 80319bf257aedcc8baea575ea4d1a855c2cc3698 Author: Nikita Tsukanov 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 e89b6dcbc1e1ea48e85ec42d5bf9130c144c4bd2 Author: Steve 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 4dc4c993dec1d8285ef0e7167e8cc01574a2d8be Author: Dariusz Komosiński 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 03bc5a475299e0010c3b74071a0bb974f2251ec7 Author: Max Katz 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 bfff7d9e01a803ad4d970d297c9aa72e00f0566a Author: Dan Walmsley Date: Mon Feb 21 21:15:56 2022 +0000 Merge pull request #7622 from pr8x/child-window-property2 Exposing `Window.ChildWindows` collection commit da3004d1d6ed0b267c856d32ec6b905afe6b7193 Author: Dan Walmsley Date: Wed Feb 16 22:24:27 2022 +0000 Merge pull request #7628 from wieslawsoltes/UpdateNuGetPackageDescription Update PackageDescription for NuGet commit 4b6d1223db7cf4bec9962ed416386c9410443ea1 Author: Max Katz 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 0419426061a3ff72d1d1265c96783326f4ae6783 Merge: 645ce6ada a9d683bb8 Author: Dan Walmsley 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 645ce6ada399b456b06e42dfd1b43e64732477b7 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 a9d683bb8f31ffb2d81c978501c7a6213538ed2e Author: Nikita Tsukanov Date: Sat Feb 12 15:25:42 2022 +0300 Introduced RawPointerPoint for usage with IntermediatePoints (#7581) Introduced RawPointerPoint for usage with IntermediatePoints commit 8b3b65496f12a3f4b0e9d38f401b6178639586db Author: Max Katz 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 d22e62711200824587ca53b11a2965f128268c2c Author: Dan Walmsley 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 --- Avalonia.sln | 27 - azure-pipelines.yml | 6 +- build/MicroCom.targets | 34 -- build/SharedVersion.props | 4 +- build/System.Drawing.Common.props | 3 +- nukebuild/MicroComGen.cs | 8 +- nukebuild/_build.csproj | 6 +- samples/ControlCatalog/Pages/PointersPage.cs | 220 +++++++- samples/ControlCatalog/Pages/ViewboxPage.xaml | 22 +- .../ControlCatalog/Pages/ViewboxPage.xaml.cs | 19 - samples/RenderDemo/Pages/ClippingPage.xaml | 54 +- samples/RenderDemo/Pages/ClippingPage.xaml.cs | 17 - src/Avalonia.Base/Threading/Dispatcher.cs | 7 + src/Avalonia.Base/Threading/JobRunner.cs | 17 +- .../Calendar/CalendarDatePicker.cs | 4 +- .../DateTimePickers/DatePicker.cs | 18 +- .../DateTimePickers/DatePickerPresenter.cs | 3 + .../DateTimePickers/TimePicker.cs | 19 +- .../DateTimePickers/TimePickerPresenter.cs | 3 + .../TransitioningContentControl.cs | 96 ++++ src/Avalonia.Controls/Window.cs | 20 +- .../Diagnostics/ViewModels/VisualTreeNode.cs | 6 +- .../Diagnostics/Views/MainWindow.xaml.cs | 5 +- src/Avalonia.Input/MouseDevice.cs | 19 +- src/Avalonia.Input/PointerEventArgs.cs | 45 +- src/Avalonia.Input/Raw/RawInputEventArgs.cs | 2 +- src/Avalonia.Input/Raw/RawPointerEventArgs.cs | 65 ++- src/Avalonia.Input/TouchDevice.cs | 2 +- src/Avalonia.MicroCom/MicroComRuntime.cs | 6 + src/Avalonia.MicroCom/MicroComVtblBase.cs | 5 + src/Avalonia.Native/Avalonia.Native.csproj | 5 +- src/Avalonia.Native/WindowImpl.cs | 5 +- .../Controls/OpenGlControlBase.cs | 2 + src/Avalonia.OpenGL/Egl/EglContext.cs | 2 +- .../ReactiveUserControl.cs | 1 + .../TransitioningContentControl.cs | 1 + src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + src/Avalonia.Themes.Default/Expander.xaml | 59 ++- .../TransitioningContentControl.xaml | 20 + .../Controls/Expander.xaml | 8 +- .../Controls/FluentControls.xaml | 1 + .../Controls/TransitioningContentControl.xaml | 20 + src/Avalonia.Themes.Fluent/FluentTheme.cs | 48 +- .../Animators/SolidColorBrushAnimator.cs | 6 +- src/Avalonia.Visuals/Animation/PageSlide.cs | 2 + src/Avalonia.X11/Avalonia.X11.csproj | 1 + src/Avalonia.X11/NativeDialogs/Gtk.cs | 2 +- src/Avalonia.X11/SMLib.cs | 16 +- src/Avalonia.X11/X11PlatformLifetimeEvents.cs | 21 +- src/Avalonia.X11/X11Window.cs | 39 +- src/Directory.Build.props | 6 + .../Avalonia.LinuxFramebuffer.csproj | 1 + .../Input/EvDev/EvDevBackend.cs | 38 +- .../Input/LibInput/LibInputBackend.cs | 31 +- .../Input/RawEventGroupingThreadingHelper.cs | 43 ++ src/Shared/ModuleInitializer.cs | 10 + src/Shared/RawEventGrouping.cs | 133 +++++ .../Helpers/DrawingContextHelper.cs | 71 ++- .../Avalonia.Win32/Avalonia.Win32.csproj | 7 +- .../Interop/UnmanagedMethods.cs | 6 +- .../Avalonia.Win32/SystemDialogImpl.cs | 31 +- src/Windows/Avalonia.Win32/Win32Com/win32.idl | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 10 +- src/tools/MicroComGenerator/Ast.cs | 241 --------- src/tools/MicroComGenerator/AstParser.cs | 232 --------- .../CSharpGen.InterfaceGen.cs | 484 ------------------ .../MicroComGenerator/CSharpGen.Utils.cs | 111 ---- src/tools/MicroComGenerator/CSharpGen.cs | 155 ------ src/tools/MicroComGenerator/CppGen.cs | 119 ----- src/tools/MicroComGenerator/Extensions.cs | 97 ---- .../MicroComGenerator.csproj | 10 - src/tools/MicroComGenerator/ParseException.cs | 27 - src/tools/MicroComGenerator/Program.cs | 52 -- src/tools/MicroComGenerator/TokenParser.cs | 417 --------------- .../BrushTransitionTests.cs | 54 ++ 75 files changed, 1151 insertions(+), 2259 deletions(-) delete mode 100644 build/MicroCom.targets create mode 100644 src/Avalonia.Controls/TransitioningContentControl.cs create mode 100644 src/Avalonia.Themes.Default/TransitioningContentControl.xaml create mode 100644 src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/Input/RawEventGroupingThreadingHelper.cs create mode 100644 src/Shared/ModuleInitializer.cs create mode 100644 src/Shared/RawEventGrouping.cs delete mode 100644 src/tools/MicroComGenerator/Ast.cs delete mode 100644 src/tools/MicroComGenerator/AstParser.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.Utils.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.cs delete mode 100644 src/tools/MicroComGenerator/CppGen.cs delete mode 100644 src/tools/MicroComGenerator/Extensions.cs delete mode 100644 src/tools/MicroComGenerator/MicroComGenerator.csproj delete mode 100644 src/tools/MicroComGenerator/ParseException.cs delete mode 100644 src/tools/MicroComGenerator/Program.cs delete mode 100644 src/tools/MicroComGenerator/TokenParser.cs create mode 100644 tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs diff --git a/Avalonia.sln b/Avalonia.sln index 410a7171c7e..124f1e535dd 100644 --- a/Avalonia.sln +++ b/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} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 903146cdd7d..497402fe4bb 100644 --- a/azure-pipelines.yml +++ b/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: diff --git a/build/MicroCom.targets b/build/MicroCom.targets deleted file mode 100644 index 1ed388f689e..00000000000 --- a/build/MicroCom.targets +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - false - all - true - - - - - - - - - - - - - - - - - - <_AvaloniaPatchComInterop>true - - - diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 38e1ed42438..4316d91a54f 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.10.12 + 0.10.13 Copyright 2022 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ @@ -11,7 +11,7 @@ latest MIT Icon.png - 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. + 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. avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin https://github.com/AvaloniaUI/Avalonia/releases git diff --git a/build/System.Drawing.Common.props b/build/System.Drawing.Common.props index 2b3707d38a9..108a0f41e04 100644 --- a/build/System.Drawing.Common.props +++ b/build/System.Drawing.Common.props @@ -1,5 +1,6 @@  - + + diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs index 06c8acbf238..b1e546cb97b 100644 --- a/nukebuild/MicroComGen.cs +++ b/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()); }); } \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index a6cb86a9bdb..af087e3180e 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,7 @@ - + @@ -38,10 +38,6 @@ - - MicroComGenerator\%(Filename)%(Extension) - - diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs index fddc503a905..2901013cea3 100644 --- a/samples/ControlCatalog/Pages/PointersPage.cs +++ b/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 @@ class PointerInfo private Dictionary _pointers = new Dictionary(); - public PointersPage() + public PointerContactsTab() { ClipToBounds = true; } @@ -104,4 +126,196 @@ public override void Render(DrawingContext context) } } } + + 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 _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); + } + } + + } } diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml index 81e5046636b..e7e3007d355 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -1,5 +1,6 @@ @@ -12,8 +13,8 @@ + Stretch="{Binding #StretchSelector.SelectedItem, FallbackValue={x:Static Stretch.Uniform}}" + StretchDirection="{Binding #StretchDirectionSelector.SelectedItem, FallbackValue={x:Static StretchDirection.Both}}"> @@ -25,9 +26,22 @@ - + + + Uniform + UniformToFill + Fill + None + + - + + + Both + DownOnly + UpOnly + + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs index 94b3f3ea14c..12b5086e12b 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs +++ b/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 @@ public class ViewboxPage : UserControl public ViewboxPage() { InitializeComponent(); - - var stretchSelector = this.FindControl("StretchSelector"); - - stretchSelector.Items = new[] - { - Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None - }; - - stretchSelector.SelectedIndex = 0; - - var stretchDirectionSelector = this.FindControl("StretchDirectionSelector"); - - stretchDirectionSelector.Items = new[] - { - StretchDirection.Both, StretchDirection.DownOnly, StretchDirection.UpOnly - }; - - stretchDirectionSelector.SelectedIndex = 0; } private void InitializeComponent() diff --git a/samples/RenderDemo/Pages/ClippingPage.xaml b/samples/RenderDemo/Pages/ClippingPage.xaml index 10225f7c491..698e18d496f 100644 --- a/samples/RenderDemo/Pages/ClippingPage.xaml +++ b/samples/RenderDemo/Pages/ClippingPage.xaml @@ -19,30 +19,36 @@ - + + + + diff --git a/samples/RenderDemo/Pages/ClippingPage.xaml.cs b/samples/RenderDemo/Pages/ClippingPage.xaml.cs index 5357181838c..c5b669343a5 100644 --- a/samples/RenderDemo/Pages/ClippingPage.xaml.cs +++ b/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("useMask"); - var clipped = this.FindControl("clipped"); - _clip = clipped.Clip; - useMask.Click += (s, e) => clipped.Clip = clipped.Clip == null ? _clip : null; - } } } diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 949c11fbe0b..65507e5310c 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -78,6 +78,13 @@ public void RunJobs() /// /// public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority); + + /// + /// Use this method to check if there are more prioritized tasks + /// + /// + public bool HasJobsWithPriority(DispatcherPriority minimumPriority) => + _jobRunner.HasJobsWithPriority(minimumPriority); /// public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index eb7c5e1af31..99ef511a019 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -109,7 +109,22 @@ private void AddJob(IJob job) } 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 { /// diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index a856ee071ca..1d19393c774 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -185,7 +185,9 @@ public class CalendarDatePicker : TemplatedControl AvaloniaProperty.RegisterDirect( nameof(SelectedDate), o => o.SelectedDate, - (o, v) => o.SelectedDate = v); + (o, v) => o.SelectedDate = v, + enableDataValidation: true, + defaultBindingMode:BindingMode.TwoWay); public static readonly StyledProperty SelectedDateFormatProperty = AvaloniaProperty.Register( diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 5893a02b04a..771f8a176b6 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -4,6 +4,7 @@ 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 @@ private void SetSelectedDateText() 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) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index eec4615736f..91375e4eafc 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -534,6 +534,9 @@ private void OnMonthChanged(object sender, EventArgs e) internal double GetOffsetForPopup() { + if (_monthSelector is null) + return 0; + var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2); } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index 6b3f66912fd..2921984f082 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Layout; using System; using System.Globalization; @@ -254,16 +255,28 @@ protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTim 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) diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs index 920eeeb4063..ab9393ed4e7 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs @@ -253,6 +253,9 @@ private void OnSelectorButtonClick(object sender, RoutedEventArgs e) internal double GetOffsetForPopup() { + if (_hourSelector is null) + return 0; + var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2); } diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs new file mode 100644 index 00000000000..cb0d2291108 --- /dev/null +++ b/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; + +/// +/// Displays according to a . +/// Uses to move between the old and new content values. +/// +public class TransitioningContentControl : ContentControl +{ + private CancellationTokenSource? _lastTransitionCts; + private object? _currentContent; + + /// + /// Defines the property. + /// + public static readonly StyledProperty PageTransitionProperty = + AvaloniaProperty.Register(nameof(PageTransition), + new CrossFade(TimeSpan.FromSeconds(0.125))); + + /// + /// Defines the property. + /// + public static readonly DirectProperty CurrentContentProperty = + AvaloniaProperty.RegisterDirect(nameof(CurrentContent), + o => o.CurrentContent); + + /// + /// Gets or sets the animation played when content appears and disappears. + /// + public IPageTransition? PageTransition + { + get => GetValue(PageTransitionProperty); + set => SetValue(PageTransitionProperty, value); + } + + /// + /// Gets the content currently displayed on the screen. + /// + 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(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ContentProperty) + { + Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content)); + } + } + + /// + /// Updates the content with transitions. + /// + /// New content to set. + 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); + } +} diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 4c94b725ea3..dec1f4bb23c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -255,6 +255,11 @@ public Window(IWindowImpl impl) [CanBeNull] public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; + /// + /// Gets a collection of child windows owned by this window. + /// + public IReadOnlyList OwnedWindows => _children.Select(x => x.child).ToList(); + /// /// Gets or sets a value indicating how the window will size itself to fit its content. /// @@ -854,6 +859,17 @@ private void OnGotInputWhenDisabled() 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 @@ private void SetWindowStartupLocation(IWindowBaseImpl owner = null) 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 @@ private void SetWindowStartupLocation(IWindowBaseImpl owner = null) Position = screen.WorkingArea.CenterRect(rect).Position; } } - else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) + else if (startupLocation == WindowStartupLocation.CenterOwner) { if (owner != null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 6a430897bab..81a19d05500 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -77,7 +77,8 @@ public override void Dispose() 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 @@ public override void Dispose() if (ToolTip != null) return GetPopupHostObservable(ToolTip, "ToolTip"); + if (ButtonFlyout != null) + return GetPopupHostObservable(ButtonFlyout, "Flyout"); + return Observable.Return(null); }) .Switch(), diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 62bfdb5186c..4a661b9cbdc 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -147,6 +147,7 @@ void ProcessProperty(IControl control, AvaloniaProperty property) ProcessProperty(control, ContextMenuProperty); ProcessProperty(control, FlyoutBase.AttachedFlyoutProperty); ProcessProperty(control, ToolTipDiagnostics.ToolTipProperty); + ProcessProperty(control, Button.FlyoutProperty); } return popupRoots; @@ -162,7 +163,9 @@ private void RawKeyDown(RawKeyEventArgs e) 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; diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index b377df0dc47..268129c05a6 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/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 @@ private void ProcessRawEvent(RawPointerEventArgs e) 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 @@ private void ProcessRawEvent(RawPointerEventArgs e) 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 @@ private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, } private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, - KeyModifiers inputModifiers) + KeyModifiers inputModifiers, Lazy?>? intermediatePoints) { device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); @@ -283,7 +285,7 @@ private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Po 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 @@ private bool MouseWheel(IMouseDevice device, ulong timestamp, IInputRoot root, P 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); diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 8c86cd46374..0604d09dc4e 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/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 @@ public class PointerEventArgs : RoutedEventArgs private readonly IVisual? _rootVisual; private readonly Point _rootVisualPosition; private readonly PointerPointProperties _properties; + private Lazy?>? _previousPoints; public PointerEventArgs(RoutedEvent routedEvent, IInteractive? source, @@ -28,6 +30,20 @@ public PointerEventArgs(RoutedEvent routedEvent, Timestamp = timestamp; KeyModifiers = modifiers; } + + public PointerEventArgs(RoutedEvent routedEvent, + IInteractive? source, + IPointer pointer, + IVisual? rootVisual, Point rootVisualPosition, + ulong timestamp, + PointerPointProperties properties, + KeyModifiers modifiers, + Lazy?>? previousPoints) + : this(routedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) + { + _previousPoints = previousPoints; + } + class EmulatedDevice : IPointerDevice { @@ -76,14 +92,16 @@ public InputModifiers InputModifiers 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 @@ public Point GetPosition(IVisual? relativeTo) public PointerPoint GetCurrentPoint(IVisual? relativeTo) => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); + /// + /// Returns the PointerPoint associated with the current event + /// + /// The visual which coordinate system to use. Pass null for toplevel coordinate system + /// + public IReadOnlyList 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; + } + /// /// Returns the current pointer point properties /// diff --git a/src/Avalonia.Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Input/Raw/RawInputEventArgs.cs index dcc5f27a79f..3a5ae1340f2 100644 --- a/src/Avalonia.Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawInputEventArgs.cs @@ -51,6 +51,6 @@ public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) /// /// Gets the timestamp associated with the event. /// - public ulong Timestamp { get; private set; } + public ulong Timestamp { get; set; } } } diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs index 62a1dd5d846..e81f32c3aff 100644 --- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Avalonia.Input.Raw { @@ -29,6 +30,8 @@ public enum RawPointerEventType /// public class RawPointerEventArgs : RawInputEventArgs { + private RawPointerPoint _point; + /// /// Initializes a new instance of the class. /// @@ -54,11 +57,50 @@ public RawPointerEventArgs( Type = type; InputModifiers = inputModifiers; } + + /// + /// Initializes a new instance of the class. + /// + /// The associated device. + /// The event timestamp. + /// The root from which the event originates. + /// The type of the event. + /// The point properties and position, in client DIPs. + /// The input modifiers. + public RawPointerEventArgs( + IInputDevice device, + ulong timestamp, + IInputRoot root, + RawPointerEventType type, + RawPointerPoint point, + RawInputModifiers inputModifiers) + : base(device, timestamp, root) + { + Contract.Requires(device != null); + Contract.Requires(root != null); + + Point = point; + Type = type; + InputModifiers = inputModifiers; + } + + /// + /// Gets the pointer properties and position, in client DIPs. + /// + public RawPointerPoint Point + { + get => _point; + set => _point = value; + } /// /// Gets the mouse position, in client DIPs. /// - public Point Position { get; set; } + public Point Position + { + get => _point.Position; + set => _point.Position = value; + } /// /// Gets the type of the event. @@ -68,6 +110,25 @@ public RawPointerEventArgs( /// /// Gets the input modifiers. /// - public RawInputModifiers InputModifiers { get; private set; } + public RawInputModifiers InputModifiers { get; set; } + + /// + /// Points that were traversed by a pointer since the previous relevant event, + /// only valid for Move and TouchUpdate + /// + public Lazy?>? IntermediatePoints { get; set; } + } + + public struct RawPointerPoint + { + /// + /// Pointer position, in client DIPs. + /// + public Point Position { get; set; } + + public RawPointerPoint() + { + Position = default; + } } } diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index af8851a82b1..f4b7b871cba 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -107,7 +107,7 @@ public void ProcessRawEvent(RawInputEventArgs ev) 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)); } diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs index 85507674d20..e0f524146ad 100644 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ b/src/Avalonia.MicroCom/MicroComRuntime.cs @@ -36,7 +36,13 @@ public static void Register(Type t, Guid guid, Func proxyF public static T CreateProxyFor(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); public static T CreateProxyFor(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); + + public static T CreateProxyOrNullFor(void* pObject, bool ownsHandle) where T : class => + pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); + public static T CreateProxyOrNullFor(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(this T obj, bool owned = false) where T : IUnknown diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs index 2f0607c0a88..7092f8131a4 100644 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs @@ -21,6 +21,11 @@ public MicroComVtblBase() AddMethod((AddRefDelegate)Release); } + protected void AddMethod(void* f) + { + _methods.Add(new IntPtr(f)); + } + protected void AddMethod(Delegate d) { GCHandle.Alloc(d); diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 39134844315..393467a8b1c 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -6,6 +6,7 @@ true netstandard2.0 true + Avalonia.MicroCom @@ -20,7 +21,7 @@ - + + - diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index f740be44a23..865aab3fd10 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -87,7 +87,10 @@ public void SetTitleBarColor(Avalonia.Media.Color color) _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 { diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 33773ed8e27..b3469c212b5 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -89,7 +89,9 @@ void DoCleanup() 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(); diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 58137dfff06..bc517c3e273 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -82,7 +82,7 @@ public IDisposable MakeCurrent(EglSurface surface) finally { if(!success) - Monitor.Enter(_lock); + Monitor.Exit(_lock); } } diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 49873491627..21cdef2634a 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -48,6 +48,7 @@ public TViewModel? ViewModel protected override void OnDataContextChanged(EventArgs e) { + base.OnDataContextChanged(e); ViewModel = DataContext as TViewModel; } diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index c4dd79f4688..d26e90b2daa 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -10,6 +10,7 @@ namespace Avalonia.ReactiveUI /// /// A ContentControl that animates the transition when its content is changed. /// + [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")] public class TransitioningContentControl : ContentControl, IStyleable { /// diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 4ae9ea4812c..54cbd4faa1f 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -38,6 +38,7 @@ + diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml index e72ddea1632..2f18faf84a8 100644 --- a/src/Avalonia.Themes.Default/Expander.xaml +++ b/src/Avalonia.Themes.Default/Expander.xaml @@ -1,5 +1,36 @@ - + + + + + + + + + + + + Expanded content + + + + + Expanded content + + + + + Expanded content + + + + + Expanded content + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index a32e0b580ce..c5bc7b6a388 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -5,6 +5,12 @@ + + + + + + Expanded content @@ -90,7 +96,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 53be41e4d13..d1136d44a49 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -15,6 +15,12 @@ public enum FluentThemeMode Dark, } + public enum DensityStyle + { + Normal, + Compact + } + /// /// Includes the fluent theme in an application. /// @@ -24,6 +30,7 @@ public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider 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 @@ public FluentTheme(IServiceProvider serviceProvider) InitStyles(_baseUri); } - public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register(nameof(Mode)); + + public static readonly StyledProperty DensityStyleProperty = + AvaloniaProperty.Register(nameof(DensityStyle)); + /// /// Gets or sets the mode of the fluent theme (light, dark). /// @@ -58,6 +68,16 @@ public FluentThemeMode Mode get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); } + + /// + /// Gets or sets the density style of the fluent theme (normal, compact). + /// + public DensityStyle DensityStyle + { + get => GetValue(DensityStyleProperty); + set => SetValue(DensityStyleProperty, value); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -74,6 +94,18 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs (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 @@ public IStyle Loaded { _loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] }; } + + if (DensityStyle == DensityStyle.Compact) + { + (_loaded as Styles)!.Add(_densityStyles[0]); + } + _isLoading = false; } @@ -183,6 +221,14 @@ private void InitStyles(Uri baseUri) 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") + } + }; } } } diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 7c6372aae23..f0e72f0b376 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -12,6 +12,8 @@ namespace Avalonia.Animation.Animators /// public class ISolidColorBrushAnimator : Animator { + 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 @@ public class ISolidColorBrushAnimator : Animator 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 instance) diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index 7d033ccf61c..8badc018e06 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -79,6 +79,7 @@ public async Task Start(Visual from, Visual to, bool forward, CancellationToken var animation = new Animation { Easing = SlideOutEasing, + FillMode = FillMode.Forward, Children = { new KeyFrame @@ -109,6 +110,7 @@ public async Task Start(Visual from, Visual to, bool forward, CancellationToken to.IsVisible = true; var animation = new Animation { + FillMode = FillMode.Forward, Easing = SlideInEasing, Children = { diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index c160fd77261..3f5d4979691 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index 77e410162f0..872c824f743 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -63,7 +63,7 @@ public void Dispose() public static IDisposable ConnectSignal(IntPtr obj, string name, T handler) { var handle = GCHandle.Alloc(handler); - var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); + var ptr = Marshal.GetFunctionPointerForDelegate(handler); using (var utf = new Utf8Buffer(name)) { var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0); diff --git a/src/Avalonia.X11/SMLib.cs b/src/Avalonia.X11/SMLib.cs index 6ffcad60bfc..e2b39cfcffb 100644 --- a/src/Avalonia.X11/SMLib.cs +++ b/src/Avalonia.X11/SMLib.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Runtime.InteropServices; @@ -7,20 +8,19 @@ internal static unsafe class SMLib { 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( diff --git a/src/Avalonia.X11/X11PlatformLifetimeEvents.cs b/src/Avalonia.X11/X11PlatformLifetimeEvents.cs index 06e1d8879f0..1a17a018e8b 100644 --- a/src/Avalonia.X11/X11PlatformLifetimeEvents.cs +++ b/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 s_nativeToManagedMapper = new ConcurrentDictionary(); @@ -62,24 +63,24 @@ internal X11PlatformLifetimeEvents(AvaloniaX11Platform platform) 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; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 07469b73627..520b35dd033 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -50,13 +50,7 @@ unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client, private double? _scalingOverride; private bool _disabled; private TransparencyHelper _transparencyHelper; - - class InputEventContainer - { - public RawInputEventArgs Event; - } - private readonly Queue _inputQueue = new Queue(); - private InputEventContainer _lastEvent; + private RawEventGrouper _rawEventGrouper; private bool _useRenderWindow = false; public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) { @@ -180,6 +174,8 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) 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 @@ private void ScheduleInput(RawInputEventArgs args) 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 @@ public void Dispose() void Cleanup() { + if (_rawEventGrouper != null) + { + _rawEventGrouper.Dispose(); + _rawEventGrouper = null; + } + if (_transparencyHelper != null) { _transparencyHelper.Dispose(); diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a3f0c01a514..869602e4528 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,4 +2,10 @@ + + + Shared\_ModuleInitializer.cs + false + + diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index 48095a4c25b..a03a90038fe 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -7,5 +7,6 @@ + diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs index b3fc979fcac..2eb10ae6662 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs @@ -13,15 +13,16 @@ public class EvDevBackend : IInputBackend private readonly EvDevDeviceDescription[] _deviceDescriptions; private readonly List _handlers = new List(); private int _epoll; - private Queue _inputQueue = new Queue(); private bool _isQueueHandlerTriggered; private object _lock = new object(); private Action _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 @@ unsafe void InputThread() 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 onInput) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 432344955ac..15d42789d44 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -17,15 +17,15 @@ public class LibInputBackend : IInputBackend private TouchDevice _touch = new TouchDevice(); private MouseDevice _mouse = new MouseDevice(); private Point _mousePosition; - - private readonly Queue _inputQueue = new Queue(); + + private readonly RawEventGroupingThreadingHelper _inputQueue; private Action _onInput; private Dictionary _pointers = new Dictionary(); public LibInputBackend() { var ctx = libinput_path_create_context(); - + _inputQueue = new(e => _onInput?.Invoke(e)); new Thread(()=>InputThread(ctx)).Start(); } @@ -66,30 +66,7 @@ private unsafe void InputThread(IntPtr ctx) } } - 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) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/RawEventGroupingThreadingHelper.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/RawEventGroupingThreadingHelper.cs new file mode 100644 index 00000000000..f706f184612 --- /dev/null +++ b/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 _rawQueue = new(); + private readonly Action _queueHandler; + + public RawEventGroupingThreadingHelper(Action 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); +} \ No newline at end of file diff --git a/src/Shared/ModuleInitializer.cs b/src/Shared/ModuleInitializer.cs new file mode 100644 index 00000000000..c14b150c1a3 --- /dev/null +++ b/src/Shared/ModuleInitializer.cs @@ -0,0 +1,10 @@ +namespace System.Runtime.CompilerServices +{ +#if !NET5_0_OR_GREATER + internal class ModuleInitializerAttribute : Attribute + { + + } +#endif +} + diff --git a/src/Shared/RawEventGrouping.cs b/src/Shared/RawEventGrouping.cs new file mode 100644 index 00000000000..084593ffc6a --- /dev/null +++ b/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 _eventCallback; + private readonly Queue _inputQueue = new(); + private readonly Action _dispatchFromQueue; + readonly Dictionary _lastTouchPoints = new(); + RawInputEventArgs? _lastEvent; + + public RawEventGrouper(Action 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 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 GetPooledList() => new PooledList(); + private static readonly Func> s_getPooledListDelegate = GetPooledList; + + private static void MergeEvents(RawPointerEventArgs last, RawPointerEventArgs current) + { + + last.IntermediatePoints ??= new Lazy?>(s_getPooledListDelegate); + ((PooledList)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(); + } +} + diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index 72438609d51..d0b45b7c5d7 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/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 { /// /// Wrap Skia canvas in drawing context so we can use Avalonia api to render to external skia canvas @@ -27,5 +28,71 @@ public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi, IV return new DrawingContextImpl(createInfo); } + + /// + /// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext. + /// + [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); + } + + /// + /// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext. + /// + [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); + } } } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 2a2aff322a2..e970fc87ea3 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,14 +3,15 @@ netstandard2.0 true Avalonia.Win32 + Avalonia.MicroCom - - + + + - diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c74c5fbc01e..1809fcf98b6 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -862,7 +862,7 @@ public struct BITMAPINFOHEADER public void Init() { - biSize = (uint)Marshal.SizeOf(this); + biSize = (uint)sizeof(BITMAPINFOHEADER); } } @@ -1521,7 +1521,7 @@ internal struct RTL_OSVERSIONINFOEX internal static Version RtlGetVersion() { RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX(); - v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(v); + v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(); if (RtlGetVersion(ref v) == 0) { return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber); @@ -1914,7 +1914,7 @@ public static WINDOWPLACEMENT Default get { WINDOWPLACEMENT result = new WINDOWPLACEMENT(); - result.Length = Marshal.SizeOf(result); + result.Length = Marshal.SizeOf(); return result; } } diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index 29844368db6..fa53bf57fed 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -79,7 +79,16 @@ internal class SystemDialogImpl : ISystemDialogImpl } } - 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 @@ internal class SystemDialogImpl : ISystemDialogImpl } catch (COMException ex) { - if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) - { - return result; - } throw new Win32Exception(ex.HResult); } })!; @@ -151,7 +156,17 @@ internal class SystemDialogImpl : ISystemDialogImpl } } - 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 @@ internal class SystemDialogImpl : ISystemDialogImpl } catch (COMException ex) { - if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) - { - return result; - } throw new Win32Exception(ex.HResult); } }); diff --git a/src/Windows/Avalonia.Win32/Win32Com/win32.idl b/src/Windows/Avalonia.Win32/Win32Com/win32.idl index 904ac41ff24..54e9aa583f2 100644 --- a/src/Windows/Avalonia.Win32/Win32Com/win32.idl +++ b/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); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e4f52682853..d1945e6c855 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -228,7 +228,7 @@ public Size? FrameSize 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()); return new Size(rect.Width, rect.Height) / RenderScaling; } } @@ -265,7 +265,7 @@ public WindowState WindowState { 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 @@ private WindowTransparencyLevel Win7EnableBlur(WindowTransparencyLevel transpare private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel) { var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); + var accentStructSize = Marshal.SizeOf(); if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) { @@ -392,7 +392,7 @@ private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transpar bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); + var accentStructSize = Marshal.SizeOf(); if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic) { @@ -973,7 +973,7 @@ private void ShowWindow(WindowState state, bool activate) { case WindowState.Minimized: newWindowProperties.IsFullScreen = false; - command = activate ? ShowWindowCommand.Minimize : ShowWindowCommand.ShowMinNoActive; + command = ShowWindowCommand.Minimize; break; case WindowState.Maximized: newWindowProperties.IsFullScreen = false; diff --git a/src/tools/MicroComGenerator/Ast.cs b/src/tools/MicroComGenerator/Ast.cs deleted file mode 100644 index e9a55308be6..00000000000 --- a/src/tools/MicroComGenerator/Ast.cs +++ /dev/null @@ -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 - { - 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, 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, 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, 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, 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 Enums { get; set; } = new List(); - public List Structs { get; set; } = new List(); - public List Interfaces { get; set; } = new List(); - - 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() - }; - } -} diff --git a/src/tools/MicroComGenerator/AstParser.cs b/src/tools/MicroComGenerator/AstParser.cs deleted file mode 100644 index 388a8eb0186..00000000000 --- a/src/tools/MicroComGenerator/AstParser.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs deleted file mode 100644 index adb8faf9381..00000000000 --- a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs +++ /dev/null @@ -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 body) - { - } - - public virtual void PreMarshalForReturn(List 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 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 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 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 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 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(); - 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(); - 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() - { - 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 _existing = new HashSet(); - - public ExpressionSyntax GetCaller(string returnType, List 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(); - 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)); - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs deleted file mode 100644 index 28baaa65f83..00000000000 --- a/src/tools/MicroComGenerator/CSharpGen.Utils.cs +++ /dev/null @@ -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 decl) where T : TypeDeclarationSyntax - { - var replace = new Dictionary(); - foreach (var method in decl.Members.OfType().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); - - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.cs b/src/tools/MicroComGenerator/CSharpGen.cs deleted file mode 100644 index ff4c351fd92..00000000000 --- a/src/tools/MicroComGenerator/CSharpGen.cs +++ /dev/null @@ -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 _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 _typeMap = new Dictionary(); - - public AstRewriter(Dictionary 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(SeparatedList(e.Select(m => - DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword))))) - ).ToArray()); - } - - - - } -} diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs deleted file mode 100644 index b053088ca9b..00000000000 --- a/src/tools/MicroComGenerator/CppGen.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/tools/MicroComGenerator/Extensions.cs b/src/tools/MicroComGenerator/Extensions.cs deleted file mode 100644 index c8a4c8f45cd..00000000000 --- a/src/tools/MicroComGenerator/Extensions.cs +++ /dev/null @@ -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(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; - } -} diff --git a/src/tools/MicroComGenerator/MicroComGenerator.csproj b/src/tools/MicroComGenerator/MicroComGenerator.csproj deleted file mode 100644 index 68895b96ca3..00000000000 --- a/src/tools/MicroComGenerator/MicroComGenerator.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - Exe - net6.0 - - - - - - diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs deleted file mode 100644 index cb549181009..00000000000 --- a/src/tools/MicroComGenerator/ParseException.cs +++ /dev/null @@ -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) - { - } - } -} diff --git a/src/tools/MicroComGenerator/Program.cs b/src/tools/MicroComGenerator/Program.cs deleted file mode 100644 index 2468b1b5a48..00000000000 --- a/src/tools/MicroComGenerator/Program.cs +++ /dev/null @@ -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(args); - if (p is NotParsed) - { - return 1; - } - - var opts = ((Parsed)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; - } - } -} diff --git a/src/tools/MicroComGenerator/TokenParser.cs b/src/tools/MicroComGenerator/TokenParser.cs deleted file mode 100644 index ea8850b8e4e..00000000000 --- a/src/tools/MicroComGenerator/TokenParser.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Globalization; -using System.IO; - -namespace MicroComGenerator -{ - internal ref struct TokenParser - { - private ReadOnlySpan _s; - public int Position { get; private set; } - public int Line { get; private set; } - public TokenParser(ReadOnlySpan 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 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 && 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 && 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 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 extraValidChars, out ReadOnlySpan res) - { - res = ReadOnlySpan.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 res) - { - res = ReadOnlySpan.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 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 res) - { - res = ReadOnlySpan.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(); - - } -} diff --git a/tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs b/tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs new file mode 100644 index 00000000000..1986bd2ee39 --- /dev/null +++ b/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); + } + } +}