diff --git a/Avalonia.sln b/Avalonia.sln index 3c2fc7437b..4e7b4cc318 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader 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}" @@ -2027,30 +2025,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 @@ -2253,7 +2227,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 903146cdd7..40669f4f53 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -2,6 +2,33 @@ variables: MSBuildEnableWorkloadResolver: 'false' jobs: + +- job: GetPRNumber + pool: + vmImage: 'windows-2022' + variables: + SolutionDir: '$(Build.SourcesDirectory)' + steps: + + - task: PowerShell@2 + displayName: Get PR Number + inputs: + targetType: 'inline' + script: | + $prId = $env:System_PullRequest_PullRequestNumber + Write-Host "PR Number is:-" $env:System_PullRequest_PullRequestNumber + + if (!([string]::IsNullOrWhiteSpace($prId))) + { + Set-Content -Path $env:Build_ArtifactStagingDirectory\prId.txt -Value $prId + } + + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'PRNumber' + publishLocation: 'Container' + - job: Linux pool: vmImage: 'ubuntu-20.04' @@ -58,8 +85,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 029d7f95f5..0000000000 --- a/build/MicroCom.targets +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - false - all - true - TargetFramework=net6.0 - - - - - - - - - - - - - - - - - - <_AvaloniaPatchComInterop>true - - - diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs index 06c8acbf23..b1e546cb97 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 b28d3eb700..52b60b7d0f 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,7 @@ - + @@ -37,10 +37,6 @@ - - MicroComGenerator\%(Filename)%(Extension) - - diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index af9ed71c5f..4b28527465 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net461;netcoreapp2.0;net6.0 + net6.0;netstandard2.0;net461;netcoreapp2.0 Avalonia diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 36b6fc2dcd..816356bee9 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.Styling; using Avalonia.Styling; +using Avalonia.Themes.Fluent; using ControlCatalog.ViewModels; namespace ControlCatalog @@ -16,33 +17,17 @@ namespace ControlCatalog DataContext = new ApplicationViewModel(); } - private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") }; - private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") }; - public static Styles FluentDark = new Styles - { - new StyleInclude(new Uri("avares://ControlCatalog/Styles")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml") - }, - DataGridFluent - }; - - public static Styles FluentLight = new Styles - { - new StyleInclude(new Uri("avares://ControlCatalog/Styles")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml") - }, - DataGridFluent - }; + public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); public static Styles DefaultLight = new Styles { @@ -65,8 +50,7 @@ namespace ControlCatalog new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") - }, - DataGridDefault + } }; public static Styles DefaultDark = new Styles @@ -90,14 +74,13 @@ namespace ControlCatalog new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") - }, - DataGridDefault + } }; public override void Initialize() { - Styles.Insert(0, FluentLight); - + Styles.Insert(0, Fluent); + Styles.Insert(1, DataGridFluent); AvaloniaXamlLoader.Load(this); } @@ -111,6 +94,11 @@ namespace ControlCatalog singleViewLifetime.MainView = new MainView(); base.OnFrameworkInitializationCompleted(); + + this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + { + StartupScreenIndex = 1, + }); } } } diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs index bdf5b8fbee..a1383b9107 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml.cs +++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs @@ -11,7 +11,6 @@ namespace ControlCatalog public DecoratedWindow() { this.InitializeComponent(); - this.AttachDevTools(); } void SetupSide(string name, StandardCursorType cursor, WindowEdge edge) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index abedda3c85..0579355831 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -3,14 +3,12 @@ using System.Collections; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.MarkupExtensions; -using Avalonia.Markup.Xaml.Styling; -using Avalonia.Markup.Xaml.XamlIl; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using ControlCatalog.Pages; +using Avalonia.Themes.Fluent; using ControlCatalog.Models; +using ControlCatalog.Pages; namespace ControlCatalog { @@ -43,14 +41,36 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - Application.Current.Styles[0] = theme switch + var themeStyle = Application.Current.Styles[0]; + if (theme == CatalogTheme.FluentLight) + { + if (App.Fluent.Mode != FluentThemeMode.Light) + { + App.Fluent.Mode = FluentThemeMode.Light; + } + Application.Current.Styles[0] = App.Fluent; + Application.Current.Styles[1] = App.DataGridFluent; + } + else if (theme == CatalogTheme.FluentDark) + { + + if (App.Fluent.Mode != FluentThemeMode.Dark) + { + App.Fluent.Mode = FluentThemeMode.Dark; + } + Application.Current.Styles[0] = App.Fluent; + Application.Current.Styles[1] = App.DataGridFluent; + } + else if (theme == CatalogTheme.DefaultLight) + { + Application.Current.Styles[0] = App.DefaultLight; + Application.Current.Styles[1] = App.DataGridDefault; + } + else if (theme == CatalogTheme.DefaultDark) { - CatalogTheme.FluentLight => App.FluentLight, - CatalogTheme.FluentDark => App.FluentDark, - CatalogTheme.DefaultLight => App.DefaultLight, - CatalogTheme.DefaultDark => App.DefaultDark, - _ => Application.Current.Styles[0] - }; + Application.Current.Styles[0] = App.DefaultDark; + Application.Current.Styles[1] = App.DataGridDefault; + } } }; diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index dd62698cc7..20591103b7 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -17,10 +17,7 @@ namespace ControlCatalog public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() - { - StartupScreenIndex = 1, - }); + //Renderer.DrawFps = true; //Renderer.DrawDirtyRects = Renderer.DrawFps = true; diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 57f22e7a05..6a940a54f1 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -33,8 +33,16 @@ namespace Avalonia.Android { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; } - public Size DoubleClickSize => new Size(4, 4); - public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); + + /// + public Size TouchDoubleClickSize => new Size(4, 4); + + /// + public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200); + + public Size DoubleClickSize => TouchDoubleClickSize; + + public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); public static void Initialize(Type appType, AndroidPlatformOptions options) { diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 85938ad958..465b922b72 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4701a83175 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Base: +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post(System.Action, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 07614f31db..376075df2a 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.Base Avalonia True diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index ffc013268d..908f431776 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -81,7 +81,7 @@ namespace Avalonia.Threading _ = action ?? throw new ArgumentNullException(nameof(action)); return _jobRunner.InvokeAsync(action, priority); } - + /// public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal) { @@ -110,6 +110,13 @@ namespace Avalonia.Threading _jobRunner.Post(action, priority); } + /// + public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) + { + _ = action ?? throw new ArgumentNullException(nameof(action)); + _jobRunner.Post(action, arg, priority); + } + /// /// This is needed for platform backends that don't have internal priority system (e. g. win32) /// To ensure that there are no jobs with higher priority diff --git a/src/Avalonia.Base/Threading/IDispatcher.cs b/src/Avalonia.Base/Threading/IDispatcher.cs index 084204dfcd..cd5add70d4 100644 --- a/src/Avalonia.Base/Threading/IDispatcher.cs +++ b/src/Avalonia.Base/Threading/IDispatcher.cs @@ -26,6 +26,15 @@ namespace Avalonia.Threading /// The priority with which to invoke the method. void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); + /// + /// Posts an action that will be invoked on the dispatcher thread. + /// + /// type of argument + /// The method to call. + /// The argument of method to call. + /// The priority with which to invoke the method. + void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal); + /// /// Invokes a action on the dispatcher thread. /// diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index eb7c5e1af3..f2aef0414c 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -13,7 +13,7 @@ namespace Avalonia.Threading { private IPlatformThreadingInterface? _platform; - private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) + private readonly Queue[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1) .Select(_ => new Queue()).ToArray(); public JobRunner(IPlatformThreadingInterface? platform) @@ -59,7 +59,7 @@ namespace Avalonia.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Func function, DispatcherPriority priority) { - var job = new Job(function, priority); + var job = new JobWithResult(function, priority); AddJob(job); return job.Task; } @@ -75,6 +75,17 @@ namespace Avalonia.Threading AddJob(new Job(action, priority, true)); } + /// + /// Post action that will be invoked on main thread + /// + /// The method to call. + /// The parameter of method to call. + /// The priority with which to invoke the method. + internal void Post(Action action, T parameter, DispatcherPriority priority) + { + AddJob(new Job(action, parameter, priority, true)); + } + /// /// Allows unit tests to change the platform threading interface. /// @@ -86,7 +97,7 @@ namespace Avalonia.Threading private void AddJob(IJob job) { bool needWake; - var queue = _queues[(int) job.Priority]; + var queue = _queues[(int)job.Priority]; lock (queue) { needWake = queue.Count == 0; @@ -98,7 +109,7 @@ namespace Avalonia.Threading private IJob? GetNextJob(DispatcherPriority minimumPriority) { - for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--) + for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--) { var q = _queues[c]; lock (q) @@ -109,14 +120,14 @@ namespace Avalonia.Threading } return null; } - + private interface IJob { /// /// Gets the job priority. /// DispatcherPriority Priority { get; } - + /// /// Runs the job. /// @@ -177,11 +188,61 @@ namespace Avalonia.Threading } } } - + /// - /// A job to run. + /// A typed job to run. + /// + /// Type of job parameter + private sealed class Job : IJob + { + private readonly Action _action; + private readonly T _parameter; + private readonly TaskCompletionSource? _taskCompletionSource; + + /// + /// Initializes a new instance of the class. + /// + /// The method to call. + /// The parameter of method to call. + /// The job priority. + /// Do not wrap exception in TaskCompletionSource + + public Job(Action action, T parameter, DispatcherPriority priority, bool throwOnUiThread) + { + _action = action; + _parameter = parameter; + Priority = priority; + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + } + + /// + public DispatcherPriority Priority { get; } + + /// + void IJob.Run() + { + if (_taskCompletionSource == null) + { + _action(_parameter); + return; + } + try + { + _action(_parameter); + _taskCompletionSource.SetResult(default); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } + } + } + + /// + /// A job to run thath return value. /// - private sealed class Job : IJob + /// Type of job result + private sealed class JobWithResult : IJob { private readonly Func _function; private readonly TaskCompletionSource _taskCompletionSource; @@ -191,7 +252,7 @@ namespace Avalonia.Threading /// /// The method to call. /// The job priority. - public Job(Func function, DispatcherPriority priority) + public JobWithResult(Func function, DispatcherPriority priority) { _function = function; Priority = priority; @@ -200,7 +261,7 @@ namespace Avalonia.Threading /// public DispatcherPriority Priority { get; } - + /// /// The task. /// diff --git a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs index 66223e513d..88b1e3c807 100644 --- a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs +++ b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs @@ -180,7 +180,7 @@ namespace Avalonia.Utilities { var r = _data[c]; if (r?.TryGetTarget(out var sub) == true) - sub.OnEvent(sender, eventArgs); + sub!.OnEvent(sender, eventArgs); else needCompact = true; } diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index d50e8b1115..c89157dc0f 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.Controls.DataGrid diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 479bbb49b6..e2c6a714aa 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index 5fe2cf3704..f1c1a55a17 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -1,4 +1,4 @@ -using System; +using System; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; @@ -196,13 +196,14 @@ namespace Avalonia.Controls protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { base.OnPointerWheelChanged(e); - if (!e.Handled && AllowSpin) + + if (AllowSpin && IsKeyboardFocusWithin) { if (e.Delta.Y != 0) { var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true); OnSpin(spinnerEventArgs); - e.Handled = spinnerEventArgs.Handled; + e.Handled = true; } } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 32428bea53..20d8a94c1a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1006,13 +1006,28 @@ namespace Avalonia.Controls if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border)) { var point = e.GetPosition(_presenter); - var index = CaretIndex = _presenter.GetCaretIndex(point); + var index = _presenter.GetCaretIndex(point); + var clickToSelect = index != CaretIndex && e.KeyModifiers.HasFlag(KeyModifiers.Shift); + + if (!clickToSelect) + { + CaretIndex = index; + } + #pragma warning disable CS0618 // Type or member is obsolete switch (e.ClickCount) #pragma warning restore CS0618 // Type or member is obsolete { case 1: - SelectionStart = SelectionEnd = index; + if (clickToSelect) + { + SelectionStart = Math.Min(index, CaretIndex); + SelectionEnd = Math.Max(index, CaretIndex); + } + else + { + SelectionStart = SelectionEnd = index; + } break; case 2: if (!StringUtils.IsStartOfWord(text, index)) diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 9d8842f613..f8a7cdc690 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 + + + @@ -40,7 +54,18 @@ - + + + @@ -53,9 +78,10 @@ - + - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 6f2ac96a66..35c9e27f1a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -6,6 +6,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -15,6 +45,23 @@ IsEnabled="False" /> + + + + + + + + + + + + + @@ -73,7 +120,7 @@ Pointer Over: - + - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 8d4bead3d3..50082acca6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -19,7 +19,7 @@ namespace Avalonia.Diagnostics.Views { private readonly IDisposable? _keySubscription; private readonly Dictionary _frozenPopupStates; - private TopLevel? _root; + private AvaloniaObject? _root; public MainWindow() { @@ -50,23 +50,23 @@ namespace Avalonia.Diagnostics.Views this.Opened += lh; } - public TopLevel? Root + public AvaloniaObject? Root { get => _root; set { if (_root != value) { - if (_root != null) + if (_root is ICloseable oldClosable) { - _root.Closed -= RootClosed; + oldClosable.Closed -= RootClosed; } _root = value; - if (_root != null) + if (_root is ICloseable newClosable) { - _root.Closed += RootClosed; + newClosable.Closed += RootClosed; DataContext = new MainViewModel(_root); } else @@ -91,9 +91,9 @@ namespace Avalonia.Diagnostics.Views _frozenPopupStates.Clear(); - if (_root != null) + if (_root is ICloseable cloneable) { - _root.Closed -= RootClosed; + cloneable.Closed -= RootClosed; _root = null; } @@ -123,7 +123,7 @@ namespace Avalonia.Diagnostics.Views .FirstOrDefault(); } - private static List GetPopupRoots(IVisual root) + private static List GetPopupRoots(TopLevel root) { var popupRoots = new List(); @@ -160,7 +160,8 @@ namespace Avalonia.Diagnostics.Views return; } - var root = Root; + var root = Root as TopLevel + ?? vm.PointerOverRoot as TopLevel; if (root is null) { return; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 3543b1adea..649b65c521 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -47,7 +47,12 @@ namespace Avalonia.Diagnostics.Views return; } - var visual = (Visual)node.Visual; + var visual = node.Visual as Visual; + + if (visual is null) + { + return; + } _currentLayer = AdornerLayer.GetAdornerLayer(visual); diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs new file mode 100644 index 0000000000..a80ed2d4ef --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Media.Imaging; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics +{ + internal static class VisualExtensions + { + /// + /// Render control to the destination stream. + /// + /// Control to be rendered. + /// Destination stream. + /// Dpi quality. + public static void RenderTo(this IControl source, Stream destination, double dpi = 96) + { + if (source.TransformedBounds == null) + { + return; + } + var rect = source.TransformedBounds.Value.Clip; + var top = rect.TopLeft; + var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); + var dpiVector = new Vector(dpi, dpi); + + // get Visual root + var root = (source.VisualRoot + ?? source.GetVisualRoot()) + as IControl ?? source; + + IDisposable? clipSetter = default; + IDisposable? clipToBoundsSetter = default; + IDisposable? renderTransformOriginSetter = default; + IDisposable? renderTransformSetter = default; + try + { + // Set clip region + var clipRegion = new Media.RectangleGeometry(rect); + clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); + clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation); + + // Translate origin + renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty, + new RelativePoint(top, RelativeUnit.Absolute), + BindingPriority.Animation); + + renderTransformSetter = root.SetValue(Visual.RenderTransformProperty, + new Media.TranslateTransform(-top.X, -top.Y), + BindingPriority.Animation); + + using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) + { + bitmap.Render(root); + bitmap.Save(destination); + } + } + finally + { + // Restore values before trasformation + renderTransformSetter?.Dispose(); + renderTransformOriginSetter?.Dispose(); + clipSetter?.Dispose(); + clipToBoundsSetter?.Dispose(); + source?.InvalidateVisual(); + } + } + } +} diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index a1d249b8f7..475a3655e4 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index 897e7bf64d..bcb63783a4 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj index be91d5f056..273910eb59 100644 --- a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj +++ b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Headless/Avalonia.Headless.csproj b/src/Avalonia.Headless/Avalonia.Headless.csproj index 28666efdf2..2e33b5790a 100644 --- a/src/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Avalonia.Headless/Avalonia.Headless.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ce4c31e27e..605659d464 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -67,6 +67,10 @@ namespace Avalonia.Headless { public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + + public Size TouchDoubleClickSize => new Size(16,16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; } class HeadlessSystemDialogsStub : ISystemDialogImpl diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index a1640a62f8..e66d1d7e7c 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7532676f18..f8cedb636f 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -71,7 +71,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId();; - _trackedRootPoint = e.GetPosition(null); + _trackedRootPoint = e.GetPosition(_target); } } diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 0f832d9add..0069aa7961 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -58,20 +58,17 @@ namespace Avalonia.Input } else { - var settings = AvaloniaLocator.Current.GetService(); - if (settings == null) - { - throw new Exception("IPlatformSettings can not be null"); - } + var settings = AvaloniaLocator.Current.GetRequiredService(); + if (!_lastClickRect.Contains(args.Position) - || ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds) + || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds) { _clickCount = 0; } ++_clickCount; _lastClickTime = ev.Timestamp; _lastClickRect = new Rect(args.Position, new Size()) - .Inflate(new Thickness(16, 16)); + .Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2)); } target.RaiseEvent(new PointerPressedEventArgs(target, pointer, diff --git a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj index effb0e4c99..03a53f7d86 100644 --- a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj +++ b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Layout/Avalonia.Layout.csproj b/src/Avalonia.Layout/Avalonia.Layout.csproj index c1cd19c394..f2c4b0540b 100644 --- a/src/Avalonia.Layout/Avalonia.Layout.csproj +++ b/src/Avalonia.Layout/Avalonia.Layout.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs index 85507674d2..e0f524146a 100644 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ b/src/Avalonia.MicroCom/MicroComRuntime.cs @@ -36,7 +36,13 @@ namespace Avalonia.MicroCom 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 2f0607c0a8..7092f8131a 100644 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs @@ -21,6 +21,11 @@ namespace Avalonia.MicroCom AddMethod((AddRefDelegate)Release); } + protected void AddMethod(void* f) + { + _methods.Add(new IntPtr(f)); + } + protected void AddMethod(Delegate d) { GCHandle.Alloc(d); diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c37bef0488..9301fd4d91 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -4,8 +4,9 @@ $([MSBuild]::IsOSPlatform(OSX)) $(PackAvaloniaNative) true - netstandard2.0;net6.0 + net6.0;netstandard2.0 true + Avalonia.MicroCom @@ -20,7 +21,7 @@ - + + - diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 522db1b334..5fa50f0e7f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -26,9 +26,15 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO + /// + public Size TouchDoubleClickSize => new Size(16, 16); + + /// + public TimeSpan TouchDoubleClickTime => DoubleClickTime; + public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { - var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true)); + var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true)); result.DoInitialize(options); return result; @@ -55,14 +61,14 @@ namespace Avalonia.Native return Initialize(CreateAvaloniaNative(), options); } - public void SetupApplicationMenuExporter () + public void SetupApplicationMenuExporter() { var exporter = new AvaloniaNativeMenuExporter(_factory); } - public void SetupApplicationName () + public void SetupApplicationName() { - if(!string.IsNullOrWhiteSpace(Application.Current.Name)) + if (!string.IsNullOrWhiteSpace(Application.Current.Name)) { _factory.MacOptions.SetApplicationTitle(Application.Current.Name); } @@ -80,13 +86,13 @@ namespace Avalonia.Native GCHandle.FromIntPtr(handle).Free(); } } - + void DoInitialize(AvaloniaNativePlatformOptions options) { _options = options; - + var applicationPlatform = new AvaloniaNativeApplicationPlatform(); - + _factory.Initialize(new GCHandleDeallocator(), applicationPlatform); if (_factory.MacOptions != null) { @@ -118,7 +124,7 @@ namespace Avalonia.Native hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - + if (_options.UseGpu) { try @@ -133,7 +139,7 @@ namespace Avalonia.Native } } - public ITrayIconImpl CreateTrayIcon () + public ITrayIconImpl CreateTrayIcon() { return new TrayIconImpl(_factory); } @@ -159,8 +165,8 @@ namespace Avalonia.Native ShowInDock = true; } - public bool ShowInDock - { + public bool ShowInDock + { get => _showInDock; set { diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 5649d46102..2b32926008 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 true diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 189b156569..348f5f3a10 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.ReactiveUI false diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 63456bc13a..a475cf5eac 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -57,7 +57,13 @@ namespace Avalonia.ReactiveUI /// public static readonly StyledProperty RouterProperty = AvaloniaProperty.Register(nameof(Router)); - + + /// + /// for the property. + /// + public static readonly StyledProperty ViewContractProperty = + AvaloniaProperty.Register(nameof(ViewContract)); + /// /// Initializes a new instance of the class. /// @@ -70,15 +76,18 @@ namespace Avalonia.ReactiveUI .Where(router => router == null)! .Cast(); + var viewContract = this.WhenAnyValue(x => x.ViewContract); + this.WhenAnyValue(x => x.Router) .Where(router => router != null) .SelectMany(router => router!.CurrentViewModel) .Merge(routerRemoved) - .Subscribe(NavigateToViewModel) + .CombineLatest(viewContract) + .Subscribe(tuple => NavigateToViewModel(tuple.First, tuple.Second)) .DisposeWith(disposables); }); } - + /// /// Gets or sets the of the view model stack. /// @@ -87,17 +96,27 @@ namespace Avalonia.ReactiveUI get => GetValue(RouterProperty); set => SetValue(RouterProperty, value); } - + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => GetValue(ViewContractProperty); + set => SetValue(ViewContractProperty, value); + } + /// /// Gets or sets the ReactiveUI view locator used by this router. /// public IViewLocator? ViewLocator { get; set; } - + /// /// Invoked when ReactiveUI router navigates to a view model. /// /// ViewModel to which the user navigates. - private void NavigateToViewModel(object? viewModel) + /// The contract for view resolution. + private void NavigateToViewModel(object? viewModel, string? contract) { if (Router == null) { @@ -112,17 +131,33 @@ namespace Avalonia.ReactiveUI Content = DefaultContent; return; } - + var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel); + var viewInstance = viewLocator.ResolveView(viewModel, contract); if (viewInstance == null) { - this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + if (contract == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + } + else + { + this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content."); + } + Content = DefaultContent; return; } - - this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + + if (contract == null) + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + } + else + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'."); + } + viewInstance.ViewModel = viewModel; if (viewInstance is IDataContextProvider provider) provider.DataContext = viewModel; diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index c88323d674..16dee00ebc 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -3,7 +3,7 @@ using System.Reactive.Disposables; using ReactiveUI; using Splat; -namespace Avalonia.ReactiveUI +namespace Avalonia.ReactiveUI { /// /// This content control will automatically load the View associated with @@ -18,6 +18,12 @@ namespace Avalonia.ReactiveUI public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty.Register(nameof(ViewModel)); + /// + /// for the property. + /// + public static readonly StyledProperty ViewContractProperty = + AvaloniaProperty.Register(nameof(ViewContract)); + /// /// Initializes a new instance of the class. /// @@ -25,8 +31,8 @@ namespace Avalonia.ReactiveUI { this.WhenActivated(disposables => { - this.WhenAnyValue(x => x.ViewModel) - .Subscribe(NavigateToViewModel) + this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract) + .Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2)) .DisposeWith(disposables); }); } @@ -39,7 +45,16 @@ namespace Avalonia.ReactiveUI get => GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } - + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => GetValue(ViewContractProperty); + set => SetValue(ViewContractProperty, value); + } + /// /// Gets or sets the view locator. /// @@ -49,7 +64,8 @@ namespace Avalonia.ReactiveUI /// Invoked when ReactiveUI router navigates to a view model. /// /// ViewModel to which the user navigates. - private void NavigateToViewModel(object? viewModel) + /// The contract for view resolution. + private void NavigateToViewModel(object? viewModel, string? contract) { if (viewModel == null) { @@ -57,17 +73,33 @@ namespace Avalonia.ReactiveUI Content = DefaultContent; return; } - + var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel); + var viewInstance = viewLocator.ResolveView(viewModel, contract); if (viewInstance == null) { - this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + if (contract == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + } + else + { + this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content."); + } + Content = DefaultContent; return; } - - this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + + if (contract == null) + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + } + else + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'."); + } + viewInstance.ViewModel = viewModel; if (viewInstance is IStyledElement styled) styled.DataContext = viewModel; diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 3be0a688ab..5a8301a2e9 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) true Key.snk diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 3548749846..139ba1bd2e 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.Styling Avalonia diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index d132dc4f5d..678f75b43f 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Themes.Default/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/ButtonSpinner.xaml index ce2b85d2b5..561ca8bedd 100644 --- a/src/Avalonia.Themes.Default/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Default/ButtonSpinner.xaml @@ -5,6 +5,7 @@ +