diff --git a/.ncrunch/BindingDemo.v3.ncrunchproject b/.ncrunch/BindingDemo.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/BindingDemo.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/RenderDemo.v3.ncrunchproject b/.ncrunch/RenderDemo.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/RenderDemo.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/VirtualizationDemo.v3.ncrunchproject b/.ncrunch/VirtualizationDemo.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/VirtualizationDemo.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index accad63faa..6a9f72039a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,7 @@ jobs: displayName: 'Install Nuke' inputs: script: | - dotnet tool install --global Nuke.GlobalTool --version 0.12.3 + dotnet tool install --global Nuke.GlobalTool --version 0.24.0 - task: CmdLine@2 displayName: 'Run Nuke' inputs: @@ -35,16 +35,16 @@ jobs: vmImage: 'macOS-10.14' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 3.0.x' + displayName: 'Use .NET Core SDK 3.1.x' inputs: packageType: sdk - version: 3.0.x + version: 3.1.x - task: UseDotNet@2 - displayName: 'Use .NET Core Runtime 2.1.x' + displayName: 'Use .NET Core Runtime 3.1.x' inputs: packageType: runtime - version: 2.1.x + version: 3.1.x - task: CmdLine@2 displayName: 'Install Mono 5.18' @@ -74,7 +74,7 @@ jobs: displayName: 'Install Nuke' inputs: script: | - dotnet tool install --global Nuke.GlobalTool --version 0.12.3 + dotnet tool install --global Nuke.GlobalTool --version 0.24.0 - task: CmdLine@2 displayName: 'Run Nuke' @@ -116,7 +116,7 @@ jobs: displayName: 'Install Nuke' inputs: script: | - dotnet tool install --global Nuke.GlobalTool --version 0.12.3 + dotnet tool install --global Nuke.GlobalTool --version 0.24.0 - task: CmdLine@2 displayName: 'Run Nuke' diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index 3923bdeeda..d61268173d 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -4,7 +4,6 @@ - diff --git a/global.json b/global.json index 1e599211d4..a3fdca9bed 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,7 @@ { + "sdk": { + "version": "3.1.101" + }, "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", "MSBuild.Sdk.Extras": "2.0.46", diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index e7abedae51..ed00f8c5e1 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -157,11 +157,14 @@ NSArray* _modes; -(void) perform { + ComPtr cb; @synchronized (self) { _signaled = false; - if(_parent != NULL && _parent->SignaledCallback != NULL) - _parent->SignaledCallback->Signaled(0, false); + if(_parent != NULL) + cb = _parent->SignaledCallback; } + if(cb != nullptr) + cb->Signaled(0, false); } -(void) setParent:(PlatformThreadingInterface *)parent diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 7b3b8465ce..358846a14e 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -13,6 +13,7 @@ using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.MSBuild; using Nuke.Common.Utilities; +using Nuke.Common.Utilities.Collections; using static Nuke.Common.EnvironmentInfo; using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.IO.PathConstruction; @@ -26,11 +27,13 @@ using static Nuke.Common.Tools.VSWhere.VSWhereTasks; running and debugging a particular target (optionally without deps) would be way easier ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support - + */ partial class Build : NukeBuild { + [Solution("Avalonia.sln")] readonly Solution Solution; + static Lazy MsBuildExe = new Lazy(() => { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -54,7 +57,7 @@ partial class Build : NukeBuild protected override void OnBuildInitialized() { Parameters = new BuildParameters(this); - Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.", + Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.", Parameters.Version, Parameters.Configuration, typeof(NukeBuild).Assembly.GetName().Version.ToString()); @@ -93,29 +96,24 @@ partial class Build : NukeBuild string projectFile, Configure configurator = null) { - return MSBuild(projectFile, c => - { + return MSBuild(c => c + .SetProjectFile(projectFile) // This is required for VS2019 image on Azure Pipelines - if (Parameters.IsRunningOnWindows && Parameters.IsRunningOnAzure) - { - var javaSdk = Environment.GetEnvironmentVariable("JAVA_HOME_8_X64"); - if (javaSdk != null) - c = c.AddProperty("JavaSdkDirectory", javaSdk); - } - - c = c.AddProperty("PackageVersion", Parameters.Version) - .AddProperty("iOSRoslynPathHackRequired", "true") - .SetToolPath(MsBuildExe.Value) - .SetConfiguration(Parameters.Configuration) - .SetVerbosity(MSBuildVerbosity.Minimal); - c = configurator?.Invoke(c) ?? c; - return c; - }); + .When(Parameters.IsRunningOnWindows && + Parameters.IsRunningOnAzure, c => c + .AddProperty("JavaSdkDirectory", GetVariable("JAVA_HOME_8_X64"))) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", true) + .SetToolPath(MsBuildExe.Value) + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .Apply(configurator)); } + Target Clean => _ => _.Executes(() => { - DeleteDirectories(Parameters.BuildDirs); - EnsureCleanDirectories(Parameters.BuildDirs); + Parameters.BuildDirs.ForEach(DeleteDirectory); + Parameters.BuildDirs.ForEach(EnsureCleanDirectory); EnsureCleanDirectory(Parameters.ArtifactsDir); EnsureCleanDirectory(Parameters.NugetIntermediateRoot); EnsureCleanDirectory(Parameters.NugetRoot); @@ -134,97 +132,84 @@ partial class Build : NukeBuild ); else - DotNetBuild(Parameters.MSBuildSolution, c => c + DotNetBuild(c => c + .SetProjectFile(Parameters.MSBuildSolution) .AddProperty("PackageVersion", Parameters.Version) .SetConfiguration(Parameters.Configuration) ); }); - - void RunCoreTest(string project) + + void RunCoreTest(string projectName) { - if(!project.EndsWith(".csproj")) - project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); - Information("Running tests from " + project); - XDocument xdoc; - using (var s = File.OpenRead(project)) - xdoc = XDocument.Load(s); - - List frameworks = null; - var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault(); - if (targets != null) - frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList(); - else - frameworks = new List {xdoc.Root.Descendants("TargetFramework").First().Value}; - - foreach(var fw in frameworks) + Information($"Running tests from {projectName}"); + var project = Solution.GetProject(projectName).NotNull("project != null"); + + foreach (var fw in project.GetTargetFrameworks()) { if (fw.StartsWith("net4") - && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1") { - Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969"); + Information($"Skipping {projectName} ({fw}) tests on Linux - https://github.com/mono/mono/issues/13969"); continue; } - Information("Running for " + fw); - DotNetTest(c => - { - c = c - .SetProjectFile(project) - .SetConfiguration(Parameters.Configuration) - .SetFramework(fw) - .EnableNoBuild() - .EnableNoRestore(); - // NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient - if (Parameters.PublishTestResults) - c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot); - return c; - }); + Information($"Running for {projectName} ({fw}) ..."); + + DotNetTest(c => c + .SetProjectFile(project) + .SetConfiguration(Parameters.Configuration) + .SetFramework(fw) + .EnableNoBuild() + .EnableNoRestore() + .When(Parameters.PublishTestResults, c => c + .SetLogger("trx") + .SetResultsDirectory(Parameters.TestResultsRoot))); } } Target RunCoreLibsTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests) + .OnlyWhenStatic(() => !Parameters.SkipTests) .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Animation.UnitTests"); - RunCoreTest("./tests/Avalonia.Base.UnitTests"); - RunCoreTest("./tests/Avalonia.Controls.UnitTests"); - RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests"); - RunCoreTest("./tests/Avalonia.Input.UnitTests"); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); - RunCoreTest("./tests/Avalonia.Layout.UnitTests"); - RunCoreTest("./tests/Avalonia.Markup.UnitTests"); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests"); - RunCoreTest("./tests/Avalonia.Styling.UnitTests"); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests"); - RunCoreTest("./tests/Avalonia.Skia.UnitTests"); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests"); + RunCoreTest("Avalonia.Animation.UnitTests"); + RunCoreTest("Avalonia.Base.UnitTests"); + RunCoreTest("Avalonia.Controls.UnitTests"); + RunCoreTest("Avalonia.Controls.DataGrid.UnitTests"); + RunCoreTest("Avalonia.Input.UnitTests"); + RunCoreTest("Avalonia.Interactivity.UnitTests"); + RunCoreTest("Avalonia.Layout.UnitTests"); + RunCoreTest("Avalonia.Markup.UnitTests"); + RunCoreTest("Avalonia.Markup.Xaml.UnitTests"); + RunCoreTest("Avalonia.Styling.UnitTests"); + RunCoreTest("Avalonia.Visuals.UnitTests"); + RunCoreTest("Avalonia.Skia.UnitTests"); + RunCoreTest("Avalonia.ReactiveUI.UnitTests"); }); Target RunRenderTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests) + .OnlyWhenStatic(() => !Parameters.SkipTests) .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj"); + RunCoreTest("Avalonia.Skia.RenderTests"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj"); + RunCoreTest("Avalonia.Direct2D1.RenderTests"); }); - + Target RunDesignerTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests"); + RunCoreTest("Avalonia.DesignerSupport.Tests"); }); [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; Target RunLeakTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) .DependsOn(Compile) .Executes(() => { @@ -235,7 +220,7 @@ partial class Build : NukeBuild }); Target ZipFiles => _ => _ - .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package) + .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package) .Executes(() => { var data = Parameters; @@ -259,9 +244,10 @@ partial class Build : NukeBuild MsBuildCommon(Parameters.MSBuildSolution, c => c .AddTargets("Pack")); else - DotNetPack(Parameters.MSBuildSolution, c => - c.SetConfiguration(Parameters.Configuration) - .AddProperty("PackageVersion", Parameters.Version)); + DotNetPack(c => c + .SetProject(Parameters.MSBuildSolution) + .SetConfiguration(Parameters.Configuration) + .AddProperty("PackageVersion", Parameters.Version)); }); Target CreateNugetPackages => _ => _ @@ -274,32 +260,40 @@ partial class Build : NukeBuild new NumergeNukeLogger())) throw new Exception("Package merge failed"); }); - + Target RunTests => _ => _ .DependsOn(RunCoreLibsTests) .DependsOn(RunRenderTests) .DependsOn(RunDesignerTests) .DependsOn(RunLeakTests); - + Target Package => _ => _ .DependsOn(RunTests) .DependsOn(CreateNugetPackages); - + Target CiAzureLinux => _ => _ .DependsOn(RunTests); - + Target CiAzureOSX => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); - + Target CiAzureWindows => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); - + public static int Main() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Execute(x => x.Package) : Execute(x => x.RunTests); } + +public static class ToolSettingsExtensions +{ + public static T Apply(this T settings, Configure configurator) + { + return configurator != null ? configurator(settings) : settings; + } +} diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 65ba5e9756..149716b416 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -4,24 +4,21 @@ using System.Linq; using System.Runtime.InteropServices; using System.Xml.Linq; using Nuke.Common; -using Nuke.Common.BuildServers; -using Nuke.Common.Execution; +using Nuke.Common.CI.AzurePipelines; using Nuke.Common.IO; -using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.IO.PathConstruction; -using static Nuke.Common.Tools.MSBuild.MSBuildTasks; public partial class Build { [Parameter("configuration")] public string Configuration { get; set; } - + [Parameter("skip-tests")] public bool SkipTests { get; set; } - + [Parameter("force-nuget-version")] public string ForceNugetVersion { get; set; } - + public class BuildParameters { public string Configuration { get; } @@ -79,15 +76,15 @@ public partial class Build IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - IsRunningOnAzure = Host == HostType.TeamServices || + IsRunningOnAzure = Host == HostType.AzurePipelines || Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; if (IsRunningOnAzure) { - RepositoryName = TeamServices.Instance.RepositoryUri; - RepositoryBranch = TeamServices.Instance.SourceBranch; - IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; - IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); + RepositoryName = AzurePipelines.Instance.RepositoryUri; + RepositoryBranch = AzurePipelines.Instance.SourceBranch; + IsPullRequest = AzurePipelines.Instance.PullRequestId.HasValue; + IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, AzurePipelines.Instance.RepositoryUri); } IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs index 461d617643..1ac14bf622 100644 --- a/nukebuild/Shims.cs +++ b/nukebuild/Shims.cs @@ -19,9 +19,9 @@ public partial class Build Logger.Info(info, args); } - private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable()); + private void Zip(AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable()); - private void Zip(PathConstruction.AbsolutePath target, IEnumerable paths) + private void Zip(AbsolutePath target, IEnumerable paths) { var targetPath = target.ToString(); bool finished = false, atLeastOneFileAdded = false; @@ -38,7 +38,7 @@ public partial class Build fileStream.CopyTo(entryStream); atLeastOneFileAdded = true; } - + foreach (var path in paths) { if (Directory.Exists(path)) @@ -64,7 +64,7 @@ public partial class Build finished = true; } - finally + finally { try { diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 2a736e4653..584c36d033 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.1 false False @@ -10,7 +10,7 @@ - + @@ -20,11 +20,11 @@ - + - + diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 2c5a09bee7..9f7593b702 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net461;netcoreapp2.0 + netstandard2.0;net461;netcoreapp3.1 Avalonia @@ -20,8 +20,8 @@ Platform=$(Platform)" /> - <_PackageFiles Include="$(DesignerHostAppPath)/Avalonia.Designer.HostApp/bin/$(Configuration)/netcoreapp2.0/Avalonia.Designer.HostApp.dll"> - tools/netcoreapp2.0/designer + <_PackageFiles Include="$(DesignerHostAppPath)/Avalonia.Designer.HostApp/bin/$(Configuration)/netcoreapp3.1/Avalonia.Designer.HostApp.dll"> + tools/netcoreapp3.1/designer false None diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index 6f21971d3d..99d53c1e03 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -1,6 +1,6 @@ - $(MSBuildThisFileDirectory)\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll + $(MSBuildThisFileDirectory)\..\tools\netcoreapp3.1\designer\Avalonia.Designer.HostApp.dll $(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe $(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Avalonia.Build.Tasks.dll false diff --git a/readme.md b/readme.md index 33da0e82b6..42b1e52205 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,8 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core). +If you need to develop Avalonia app with JetBrains Rider, go and *vote* on [this issue](https://youtrack.jetbrains.com/issue/RIDER-39247) in their tracker. JetBrains won't do things without their users telling them that they want the feature, so only **YOU** can make it happen. + Avalonia is delivered via NuGet package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/) Use these commands in the Package Manager console to install Avalonia manually: diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index 3005fdbc51..ce33f42143 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -1,9 +1,10 @@  Exe - netcoreapp2.0;net461 + netcoreapp3.1 + diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 7919c3ac5a..772af9eacd 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.1 true diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 4c2769bcc4..11c0a4fa68 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -21,6 +21,7 @@ + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 1c2653f73a..f3f70719e3 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -54,6 +54,7 @@ + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 248f94082d..d25de9c1f5 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -14,7 +14,7 @@ - + @@ -22,7 +22,9 @@ - + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 38cbde9d92..b40fdb4a17 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -1,13 +1,11 @@ +using System; +using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Notifications; -using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Markup.Xaml; -using Avalonia.Threading; using ControlCatalog.ViewModels; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; namespace ControlCatalog { @@ -35,6 +33,12 @@ namespace ControlCatalog mainMenu.AttachedToVisualTree += MenuAttached; } + public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit"; + + public static KeyGesture MenuQuitGesture => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + new KeyGesture(Key.Q, KeyModifiers.Meta) : + new KeyGesture(Key.F4, KeyModifiers.Alt); + public void MenuAttached(object sender, VisualTreeAttachmentEventArgs e) { if (NativeMenu.GetIsNativeMenuExported(this) && sender is Menu mainMenu) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index 868f0df6ad..de9ea34e80 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -16,13 +16,13 @@ Defined in XAML - + - + diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index 39bbf391bb..13bae59805 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -6,15 +6,19 @@ A progress bar control + - + - + diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml new file mode 100644 index 0000000000..f73ef9b4fb --- /dev/null +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -0,0 +1,134 @@ + + + TextBlock + A control that can display text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs b/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs new file mode 100644 index 0000000000..49fecbe7c5 --- /dev/null +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class TextBlockPage : UserControl + { + public TextBlockPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 2adef5f64e..86d762a5bc 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.1 diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 2cdde0c945..cd3daf61e1 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp2.0 + netcoreapp3.1 diff --git a/samples/RemoteDemo/RemoteDemo.csproj b/samples/RemoteDemo/RemoteDemo.csproj index 2d7699561a..530cad805f 100644 --- a/samples/RemoteDemo/RemoteDemo.csproj +++ b/samples/RemoteDemo/RemoteDemo.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp2.0 + netcoreapp3.1 diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj index 3005fdbc51..ce33f42143 100644 --- a/samples/RenderDemo/RenderDemo.csproj +++ b/samples/RenderDemo/RenderDemo.csproj @@ -1,9 +1,10 @@  Exe - netcoreapp2.0;net461 + netcoreapp3.1 + diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index 3005fdbc51..ce33f42143 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -1,9 +1,10 @@  Exe - netcoreapp2.0;net461 + netcoreapp3.1 + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 6a00feaf79..88b99cd99a 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -34,7 +34,6 @@ namespace Avalonia public AvaloniaObject() { VerifyAccess(); - AvaloniaPropertyRegistry.Instance.NotifyInitialized(this); } /// @@ -312,7 +311,10 @@ namespace Avalonia /// The property. /// The value. /// The priority of the value. - public void SetValue( + /// + /// An if setting the property can be undone, otherwise null. + /// + public IDisposable SetValue( StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue) @@ -336,8 +338,10 @@ namespace Avalonia } else if (!(value is DoNothingType)) { - Values.SetValue(property, value, priority); + return Values.SetValue(property, value, priority); } + + return null; } /// @@ -479,7 +483,13 @@ namespace Avalonia } } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) { } + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) + { + ((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default); + } /// /// Called for each inherited property when the changes. diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index a4c7fa95a5..0f82042dcd 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -458,7 +458,10 @@ namespace Avalonia /// The property. /// The value. /// The priority of the value. - public static void SetValue( + /// + /// An if setting the property can be undone, otherwise null. + /// + public static IDisposable SetValue( this IAvaloniaObject target, AvaloniaProperty property, object value, @@ -467,7 +470,7 @@ namespace Avalonia target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - property.RouteSetValue(target, value, priority); + return property.RouteSetValue(target, value, priority); } /// @@ -478,7 +481,10 @@ namespace Avalonia /// The property. /// The value. /// The priority of the value. - public static void SetValue( + /// + /// An if setting the property can be undone, otherwise null. + /// + public static IDisposable SetValue( this IAvaloniaObject target, AvaloniaProperty property, T value, @@ -490,11 +496,10 @@ namespace Avalonia switch (property) { case StyledPropertyBase styled: - target.SetValue(styled, value, priority); - break; + return target.SetValue(styled, value, priority); case DirectPropertyBase direct: target.SetValue(direct, value); - break; + return null; default: throw new NotSupportedException("Unsupported AvaloniaProperty type."); } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index b305a9aaa2..394e22eac1 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -20,7 +20,6 @@ namespace Avalonia public static readonly object UnsetValue = new UnsetValueType(); private static int s_nextId; - private readonly Subject _initialized; private readonly Subject _changed; private readonly PropertyMetadata _defaultMetadata; private readonly Dictionary _metadata; @@ -53,7 +52,6 @@ namespace Avalonia throw new ArgumentException("'name' may not contain periods."); } - _initialized = new Subject(); _changed = new Subject(); _metadata = new Dictionary(); @@ -81,7 +79,6 @@ namespace Avalonia Contract.Requires(source != null); Contract.Requires(ownerType != null); - _initialized = source._initialized; _changed = source._changed; _metadata = new Dictionary(); @@ -136,22 +133,6 @@ namespace Avalonia /// public virtual bool IsReadOnly => false; - /// - /// Gets an observable that is fired when this property is initialized on a - /// new instance. - /// - /// - /// This observable is fired each time a new is constructed - /// for all properties registered on the object's type. The default value of the property - /// for the object is passed in the args' NewValue (OldValue will always be - /// . - /// - /// - /// An observable that is fired when this property is initialized on a new - /// instance. - /// - public IObservable Initialized => _initialized; - /// /// Gets an observable that is fired when this property changes on any /// instance. @@ -489,24 +470,13 @@ namespace Avalonia } /// - /// True if has any observers. + /// Uses the visitor pattern to resolve an untyped property to a typed property. /// - internal bool HasNotifyInitializedObservers => _initialized.HasObservers; - - /// - /// Notifies the observable. - /// - /// The object being initialized. - internal abstract void NotifyInitialized(IAvaloniaObject o); - - /// - /// Notifies the observable. - /// - /// The observable arguments. - internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e) - { - _initialized.OnNext(e); - } + /// The type of user data passed. + /// The visitor which will accept the typed property. + /// The user data to pass. + public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) + where TData : struct; /// /// Notifies the observable. @@ -535,7 +505,10 @@ namespace Avalonia /// The object instance. /// The value. /// The priority. - internal abstract void RouteSetValue( + /// + /// An if setting the property can be undone, otherwise null. + /// + internal abstract IDisposable? RouteSetValue( IAvaloniaObject o, object value, BindingPriority priority); diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 14c8630599..29ab10278b 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -415,51 +415,6 @@ namespace Avalonia _inheritedCache.Clear(); } - internal void NotifyInitialized(AvaloniaObject o) - { - Contract.Requires(o != null); - - var type = o.GetType(); - - if (!_initializedCache.TryGetValue(type, out var initializationData)) - { - var visited = new HashSet(); - - initializationData = new List(); - - foreach (AvaloniaProperty property in GetRegistered(type)) - { - if (property.IsDirect) - { - initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property)); - } - else - { - initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); - } - - visited.Add(property); - } - - foreach (AvaloniaProperty property in GetRegisteredAttached(type)) - { - if (!visited.Contains(property)) - { - initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); - - visited.Add(property); - } - } - - _initializedCache.Add(type, initializationData); - } - - foreach (PropertyInitializationData data in initializationData) - { - data.Property.NotifyInitialized(o); - } - } - private readonly struct PropertyInitializationData { public AvaloniaProperty Property { get; } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 7a0be065eb..d3b5277c53 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Data; using Avalonia.Reactive; +using Avalonia.Utilities; #nullable enable @@ -102,18 +103,9 @@ namespace Avalonia } /// - internal override void NotifyInitialized(IAvaloniaObject o) + public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) { - if (HasNotifyInitializedObservers) - { - var e = new AvaloniaPropertyChangedEventArgs( - o, - this, - default, - InvokeGetter(o), - BindingPriority.Unset); - NotifyInitialized(e); - } + vistor.Visit(this, ref data); } /// @@ -129,7 +121,7 @@ namespace Avalonia } /// - internal override void RouteSetValue( + internal override IDisposable? RouteSetValue( IAvaloniaObject o, object value, BindingPriority priority) @@ -148,6 +140,8 @@ namespace Avalonia { throw v.Error!; } + + return null; } /// diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs index fb85ae222c..81a212b087 100644 --- a/src/Avalonia.Base/IAvaloniaObject.cs +++ b/src/Avalonia.Base/IAvaloniaObject.cs @@ -65,7 +65,7 @@ namespace Avalonia /// The property. /// The value. /// The priority of the value. - void SetValue( + IDisposable SetValue( StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue); diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index 09a0f169df..3249b31d66 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -48,10 +48,10 @@ namespace Avalonia.PropertyStore { _subscription?.Dispose(); _subscription = null; - _sink.Completed(Property, this); + _sink.Completed(Property, this, Value); } - public void OnCompleted() => _sink.Completed(Property, this); + public void OnCompleted() => _sink.Completed(Property, this, Value); public void OnError(Exception error) { diff --git a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs index f15f56e32b..aa054c46ff 100644 --- a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs @@ -10,16 +10,20 @@ namespace Avalonia.PropertyStore /// . /// /// The property type. - internal class ConstantValueEntry : IPriorityValueEntry + internal class ConstantValueEntry : IPriorityValueEntry, IDisposable { + private IValueSink _sink; + public ConstantValueEntry( StyledPropertyBase property, T value, - BindingPriority priority) + BindingPriority priority, + IValueSink sink) { Property = property; Value = value; Priority = priority; + _sink = sink; } public StyledPropertyBase Property { get; } @@ -28,6 +32,7 @@ namespace Avalonia.PropertyStore Optional IValue.Value => Value.ToObject(); BindingPriority IValue.ValuePriority => Priority; - public void Reparent(IValueSink sink) { } + public void Dispose() => _sink.Completed(Property, this, Value); + public void Reparent(IValueSink sink) => _sink = sink; } } diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs index 223b0058c1..9012a985ac 100644 --- a/src/Avalonia.Base/PropertyStore/IValueSink.cs +++ b/src/Avalonia.Base/PropertyStore/IValueSink.cs @@ -15,6 +15,9 @@ namespace Avalonia.PropertyStore Optional oldValue, BindingValue newValue); - void Completed(AvaloniaProperty property, IPriorityValueEntry entry); + void Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue); } } diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index 2785dc6840..affb20f334 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -78,8 +78,10 @@ namespace Avalonia.PropertyStore public void ClearLocalValue() => UpdateEffectiveValue(); - public void SetValue(T value, BindingPriority priority) + public IDisposable? SetValue(T value, BindingPriority priority) { + IDisposable? result = null; + if (priority == BindingPriority.LocalValue) { _localValue = value; @@ -87,10 +89,13 @@ namespace Avalonia.PropertyStore else { var insert = FindInsertPoint(priority); - _entries.Insert(insert, new ConstantValueEntry(Property, value, priority)); + var entry = new ConstantValueEntry(Property, value, priority, this); + _entries.Insert(insert, entry); + result = entry; } UpdateEffectiveValue(); + return result; } public BindingEntry AddBinding(IObservable> source, BindingPriority priority) @@ -117,7 +122,10 @@ namespace Avalonia.PropertyStore UpdateEffectiveValue(); } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) { _entries.Remove((IPriorityValueEntry)entry); UpdateEffectiveValue(); diff --git a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs index cd8ce2cd80..d3ac3dac8a 100644 --- a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs +++ b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs @@ -36,7 +36,7 @@ namespace Avalonia.Reactive return this; } - void IDisposable.Dispose() + public virtual void Dispose() { Unsubscribed(); _observer = null; diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 8c4d683ae0..fb07ef3f62 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using Avalonia.Data; using Avalonia.Reactive; +using Avalonia.Utilities; namespace Avalonia { @@ -169,6 +170,12 @@ namespace Avalonia base.OverrideMetadata(type, metadata); } + /// + public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) + { + vistor.Visit(this, ref data); + } + /// /// Gets the string representation of the property. /// @@ -181,21 +188,6 @@ namespace Avalonia /// object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); - /// - internal override void NotifyInitialized(IAvaloniaObject o) - { - if (HasNotifyInitializedObservers) - { - var e = new AvaloniaPropertyChangedEventArgs( - o, - this, - default, - o.GetValue(this), - BindingPriority.Unset); - NotifyInitialized(e); - } - } - /// internal override void RouteClearValue(IAvaloniaObject o) { @@ -209,7 +201,7 @@ namespace Avalonia } /// - internal override void RouteSetValue( + internal override IDisposable RouteSetValue( IAvaloniaObject o, object value, BindingPriority priority) @@ -218,7 +210,7 @@ namespace Avalonia if (v.HasValue) { - o.SetValue(this, (TValue)v.Value, priority); + return o.SetValue(this, (TValue)v.Value, priority); } else if (v.Type == BindingValueType.UnsetValue) { @@ -228,6 +220,8 @@ namespace Avalonia { throw v.Error; } + + return null; } /// diff --git a/src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs b/src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs new file mode 100644 index 0000000000..4b889eb129 --- /dev/null +++ b/src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs @@ -0,0 +1,34 @@ +#nullable enable + +namespace Avalonia.Utilities +{ + /// + /// A visitor to resolve an untyped to a typed property. + /// + /// The type of user data passed. + /// + /// Pass an instance that implements this interface to + /// + /// in order to resolve un untyped to a typed + /// or . + /// + public interface IAvaloniaPropertyVisitor + where TData : struct + { + /// + /// Called when the property is a styled property. + /// + /// The property value type. + /// The property. + /// The user data. + void Visit(StyledPropertyBase property, ref TData data); + + /// + /// Called when the property is a direct property. + /// + /// The property value type. + /// The property. + /// The user data. + void Visit(DirectPropertyBase property, ref TData data); + } +} diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index d1393a9c0d..8a25d29c3f 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; @@ -185,6 +186,14 @@ namespace Avalonia.Utilities } } + var typeConverter = TypeDescriptor.GetConverter(to); + + if (typeConverter.CanConvertFrom(from) == true) + { + result = typeConverter.ConvertFrom(null, culture, value); + return true; + } + var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit); if (cast != null) diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 37e25d0fac..aca08f5259 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -183,11 +183,16 @@ namespace Avalonia.Utilities for (int c = 0; c < _count; c++) { var r = _data[c]; + + TSubscriber target = null; + + r.Subscriber?.TryGetTarget(out target); + //Mark current index as first empty - if (r.Subscriber == null && empty == -1) + if (target == null && empty == -1) empty = c; //If current element isn't null and we have an empty one - if (r.Subscriber != null && empty != -1) + if (target != null && empty != -1) { _data[c] = default; _data[empty] = r; diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 58ebc48652..104c06de0f 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -70,23 +70,25 @@ namespace Avalonia return false; } - public void SetValue(StyledPropertyBase property, T value, BindingPriority priority) + public IDisposable? SetValue(StyledPropertyBase property, T value, BindingPriority priority) { if (property.ValidateValue?.Invoke(value) == false) { throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); } + IDisposable? result = null; + if (_values.TryGetValue(property, out var slot)) { - SetExisting(slot, property, value, priority); + result = SetExisting(slot, property, value, priority); } else if (property.HasCoercion) { // If the property has any coercion callbacks then always create a PriorityValue. var entry = new PriorityValue(_owner, property, this); _values.AddValue(property, entry); - entry.SetValue(value, priority); + result = entry.SetValue(value, priority); } else if (priority == BindingPriority.LocalValue) { @@ -95,10 +97,13 @@ namespace Avalonia } else { - var entry = new ConstantValueEntry(property, value, priority); + var entry = new ConstantValueEntry(property, value, priority, this); _values.AddValue(property, entry); _sink.ValueChanged(property, priority, default, value); + result = entry; } + + return result; } public IDisposable AddBinding( @@ -148,7 +153,7 @@ namespace Avalonia _values.Remove(property); _sink.ValueChanged( property, - BindingPriority.LocalValue, + BindingPriority.Unset, old, BindingValue.Unset); } @@ -173,7 +178,7 @@ namespace Avalonia { return new Diagnostics.AvaloniaPropertyValue( property, - slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue, + slot.Value.HasValue ? slot.Value.Value : AvaloniaProperty.UnsetValue, slot.ValuePriority, null); } @@ -190,32 +195,38 @@ namespace Avalonia _sink.ValueChanged(property, priority, oldValue, newValue); } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) { if (_values.TryGetValue(property, out var slot)) { if (slot == entry) { _values.Remove(property); + _sink.Completed(property, entry, oldValue); } } } - private void SetExisting( + private IDisposable? SetExisting( object slot, StyledPropertyBase property, T value, BindingPriority priority) { + IDisposable? result = null; + if (slot is IPriorityValueEntry e) { var priorityValue = new PriorityValue(_owner, property, this, e); _values.SetValue(property, priorityValue); - priorityValue.SetValue(value, priority); + result = priorityValue.SetValue(value, priority); } else if (slot is PriorityValue p) { - p.SetValue(value, priority); + result = p.SetValue(value, priority); } else if (slot is LocalValueEntry l) { @@ -228,6 +239,7 @@ namespace Avalonia else { var priorityValue = new PriorityValue(_owner, property, this, l); + result = priorityValue.SetValue(value, priority); _values.SetValue(property, priorityValue); } } @@ -235,6 +247,8 @@ namespace Avalonia { throw new NotSupportedException("Unrecognised value store slot type."); } + + return result; } private IDisposable BindExisting( diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index e16e11cdd6..844316741a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -65,7 +65,7 @@ namespace Avalonia.Controls private const double DATAGRID_minimumColumnHeaderHeight = 4; internal const double DATAGRID_maximumStarColumnWidth = 10000; internal const double DATAGRID_minimumStarColumnWidth = 0.001; - private const double DATAGRID_mouseWheelDelta = 48.0; + private const double DATAGRID_mouseWheelDelta = 72.0; private const double DATAGRID_maxHeadersThickness = 32768; private const double DATAGRID_defaultRowHeight = 22; @@ -149,6 +149,9 @@ namespace Avalonia.Controls private IEnumerable _items; + public event EventHandler HorizontalScroll; + public event EventHandler VerticalScroll; + /// /// Identifies the CanUserReorderColumns dependency property. /// @@ -373,7 +376,11 @@ namespace Avalonia.Controls public bool IsValid { get { return _isValid; } - internal set { SetAndRaise(IsValidProperty, ref _isValid, value); } + internal set + { + SetAndRaise(IsValidProperty, ref _isValid, value); + PseudoClasses.Set(":invalid", !value); + } } public static readonly StyledProperty MaxColumnWidthProperty = @@ -656,8 +663,6 @@ namespace Avalonia.Controls HorizontalScrollBarVisibilityProperty, VerticalScrollBarVisibilityProperty); - PseudoClass(IsValidProperty, x => !x, ":invalid"); - ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsPropertyChanged(e)); CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e)); ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e)); @@ -4223,6 +4228,7 @@ namespace Avalonia.Controls private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e) { ProcessHorizontalScroll(e.ScrollEventType); + HorizontalScroll?.Invoke(sender, e); } private bool IsColumnOutOfBounds(int columnIndex) @@ -5555,6 +5561,7 @@ namespace Avalonia.Controls private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e) { ProcessVerticalScroll(e.ScrollEventType); + VerticalScroll?.Invoke(sender, e); } //TODO: Ensure left button is checked for diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index bff2b500ae..31c77c595d 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls static DataGridColumnHeader() { - AreSeparatorsVisibleProperty.Changed.AddClassHandler((x,e) => x.OnAreSeparatorsVisibleChanged(e)); + AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreSeparatorsVisibleChanged(e)); } /// @@ -103,7 +103,7 @@ namespace Avalonia.Controls { get; set; - } + } internal DataGrid OwningGrid => OwningColumn?.OwningGrid; internal int ColumnIndex @@ -116,19 +116,19 @@ namespace Avalonia.Controls } return OwningColumn.Index; } - } + } internal ListSortDirection? CurrentSortingState { get; private set; - } + } private bool IsMouseOver { get; set; - } + } private bool IsPressed { @@ -158,14 +158,14 @@ namespace Avalonia.Controls && OwningGrid.DataConnection.AllowSort) { var sort = OwningColumn.GetSortDescription(); - if(sort != null) + if (sort != null) { CurrentSortingState = sort.Descending ? ListSortDirection.Descending : ListSortDirection.Ascending; } } - PseudoClasses.Set(":sortascending", + PseudoClasses.Set(":sortascending", CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Ascending); - PseudoClasses.Set(":sortdescending", + PseudoClasses.Set(":sortdescending", CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Descending); } @@ -195,7 +195,7 @@ namespace Avalonia.Controls // completed a click without dragging, so we're sorting InvokeProcessSort(keyModifiers); handled = true; - } + } internal void InvokeProcessSort(KeyModifiers keyModifiers) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls { Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers)); } - } + } //TODO GroupSorting internal void ProcessSort(KeyModifiers keyModifiers) @@ -246,45 +246,49 @@ namespace Avalonia.Controls owningGrid.DataConnection.SortDescriptions.Clear(); } - if (sort != null) + // if ctrl is held down, we only clear the sort directions + if (!ctrl) { - newSort = sort.SwitchSortDirection(); - - // changing direction should not affect sort order, so we replace this column's - // sort description instead of just adding it to the end of the collection - int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort); - if (oldIndex >= 0) + if (sort != null) { - owningGrid.DataConnection.SortDescriptions.Remove(sort); - owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort); + newSort = sort.SwitchSortDirection(); + + // changing direction should not affect sort order, so we replace this column's + // sort description instead of just adding it to the end of the collection + int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort); + if (oldIndex >= 0) + { + owningGrid.DataConnection.SortDescriptions.Remove(sort); + owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort); + } + else + { + owningGrid.DataConnection.SortDescriptions.Add(newSort); + } } else { + string propertyName = OwningColumn.GetSortPropertyName(); + // no-opt if we couldn't find a property to sort on + if (string.IsNullOrEmpty(propertyName)) + { + return; + } + + newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture); owningGrid.DataConnection.SortDescriptions.Add(newSort); } } - else - { - string propertyName = OwningColumn.GetSortPropertyName(); - // no-opt if we couldn't find a property to sort on - if (string.IsNullOrEmpty(propertyName)) - { - return; - } - - newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture); - owningGrid.DataConnection.SortDescriptions.Add(newSort); - } } } - } + } private bool CanReorderColumn(DataGridColumn column) { - return OwningGrid.CanUserReorderColumns + return OwningGrid.CanUserReorderColumns && !(column is DataGridFillerColumn) && (column.CanUserReorderInternal.HasValue && column.CanUserReorderInternal.Value || !column.CanUserReorderInternal.HasValue); - } + } /// /// Determines whether a column can be resized by dragging the border of its header. If star sizing @@ -302,7 +306,7 @@ namespace Avalonia.Controls return false; } return column.ActualCanUserResize; - } + } private static bool TrySetResizeColumn(DataGridColumn column) { @@ -316,7 +320,7 @@ namespace Avalonia.Controls return true; } return false; - } + } //TODO DragDrop @@ -371,7 +375,7 @@ namespace Avalonia.Controls { if (_dragMode == DragMode.MouseDown) { - OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled); + OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled); } else if (_dragMode == DragMode.Reorder) { @@ -449,7 +453,7 @@ namespace Avalonia.Controls OnMouseLeave(); ApplyState(); - } + } private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e) { @@ -577,7 +581,7 @@ namespace Avalonia.Controls { return OwningGrid.Columns.Count - 1; } - } + } /// /// Returns true if the mouse is @@ -723,7 +727,7 @@ namespace Avalonia.Controls Point targetPosition = new Point(0, 0); if (targetColumn == null || targetColumn == OwningGrid.ColumnsInternal.FillerColumn || targetColumn.IsFrozen != OwningColumn.IsFrozen) { - targetColumn = + targetColumn = OwningGrid.ColumnsInternal.GetLastColumn( isVisible: true, isFrozen: OwningColumn.IsFrozen, @@ -741,7 +745,7 @@ namespace Avalonia.Controls handled = true; } - } + } private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders) { @@ -764,7 +768,7 @@ namespace Avalonia.Controls handled = true; } - } + } private void SetDragCursor(Point mousePosition) { diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index 14ad9c23e6..17e7ecba43 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -202,24 +202,24 @@ - - + + - + - - + + /// Defines the property. @@ -160,7 +161,19 @@ namespace Avalonia /// /// Global styles apply to all windows in the application. /// - public Styles Styles => _styles ?? (_styles = new Styles()); + public Styles Styles + { + get + { + if (_styles == null) + { + _styles = new Styles(this); + _styles.ResourcesChanged += ThisResourcesChanged; + } + + return _styles; + } + } /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; @@ -233,9 +246,29 @@ namespace Avalonia } + private void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + if (_notifyingResourcesChanged) + { + return; + } + + try + { + _notifyingResourcesChanged = true; + (_resources as ISetResourceParent)?.ParentResourcesChanged(e); + (_styles as ISetResourceParent)?.ParentResourcesChanged(e); + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + finally + { + _notifyingResourcesChanged = false; + } + } + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { - ResourcesChanged?.Invoke(this, e); + NotifyResourcesChanged(e); } private string _name; diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 2e115463ac..0bc6394b19 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -91,7 +91,11 @@ namespace Avalonia.Controls CommandProperty.Changed.Subscribe(CommandChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsCancelProperty.Changed.Subscribe(IsCancelChanged); - PseudoClass