diff --git a/.editorconfig b/.editorconfig index ff7ac5d69e..3620896f34 100644 --- a/.editorconfig +++ b/.editorconfig @@ -169,6 +169,8 @@ dotnet_diagnostic.CA1828.severity = warning dotnet_diagnostic.CA1829.severity = warning #CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = warning +#CACA2211:Non-constant fields should not be visible +dotnet_diagnostic.CA2211.severity = error # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/Directory.Build.props b/Directory.Build.props index 42daa2df7f..c19a55e8ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,5 +7,6 @@ false false False + 11 diff --git a/NOTICE.md b/NOTICE.md index e97fc654c9..7083706c3e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -81,14 +81,14 @@ A "contributor" is any person that distributes its contribution under this licen https://github.com/wayland-project/wayland-protocols -Copyright 2008-2013 Kristian Hgsberg -Copyright 2010-2013 Intel Corporation -Copyright 2013 Rafael Antognolli -Copyright 2013 Jasper St. Pierre -Copyright 2014 Jonas dahl -Copyright 2014 Jason Ekstrand -Copyright 2014-2015 Collabora, Ltd. -Copyright 2015 Red Hat Inc. +Copyright © 2008-2013 Kristian Høgsberg +Copyright © 2010-2013 Intel Corporation +Copyright © 2013 Rafael Antognolli +Copyright © 2013 Jasper St. Pierre +Copyright © 2014 Jonas Ådahl +Copyright © 2014 Jason Ekstrand +Copyright © 2014-2015 Collabora, Ltd. +Copyright © 2015 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. https://github.com/toptensoftware/RichTextKit -Copyright 2019 Topten Software. All Rights Reserved. +Copyright © 2019 Topten Software. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in compliance with the License. You may obtain @@ -303,3 +303,62 @@ https://github.com/chromium/chromium // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Flutter + +https://github.com/flutter/flutter + +//Copyright 2014 The Flutter Authors. All rights reserved. + +//Redistribution and use in source and binary forms, with or without modification, +//are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; +//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Reactive Extensions + +https://github.com/dotnet/reactive + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml index 43253ac6be..5735da19ab 100644 --- a/azure-pipelines-integrationtests.yml +++ b/azure-pipelines-integrationtests.yml @@ -13,14 +13,14 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - script: system_profiler SPDisplaysDataType |grep Resolution @@ -51,14 +51,14 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: Windows Application Driver@0 inputs: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a3bbc33418..8bab6e68e2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,14 +30,14 @@ jobs: vmImage: 'ubuntu-20.04' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' @@ -67,14 +67,14 @@ jobs: vmImage: 'macos-12' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' @@ -138,14 +138,14 @@ jobs: SolutionDir: '$(Build.SourcesDirectory)' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' diff --git a/build/Base.props b/build/Base.props index 100c9088cd..26f19e3abc 100644 --- a/build/Base.props +++ b/build/Base.props @@ -1,6 +1,7 @@  - + + diff --git a/build/SharedVersion.props b/build/SharedVersion.props index e9c3d65b41..eca3ba37b0 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -8,7 +8,6 @@ https://github.com/AvaloniaUI/Avalonia/ true $(NoWarn);CS1591 - preview MIT Icon.png Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly. diff --git a/build/System.Memory.props b/build/System.Memory.props index b36998a780..a413e18927 100644 --- a/build/System.Memory.props +++ b/build/System.Memory.props @@ -1,5 +1,5 @@ - + diff --git a/global.json b/global.json index a9318b212f..8536535b51 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,9 @@ { "sdk": { - "version": "7.0.100", + "version": "7.0.101", "rollForward": "latestFeature" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "1.0.43", - "MSBuild.Sdk.Extras": "3.0.22", - "AggregatePackage.NuGet.Sdk" : "0.1.12" + "Microsoft.Build.Traversal": "3.2.0" } } diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 62c582610c..f6fa07dbde 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,7 +5,7 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] public class MainActivity : AvaloniaMainActivity { } diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml index 2759d2904a..49e079a719 100644 --- a/samples/ControlCatalog.Android/Resources/values/styles.xml +++ b/samples/ControlCatalog.Android/Resources/values/styles.xml @@ -14,4 +14,8 @@ @null + + diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs index 908b5f082a..a0b68b129b 100644 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ b/samples/ControlCatalog.Android/SplashActivity.cs @@ -28,6 +28,8 @@ namespace ControlCatalog.Android base.OnResume(); StartActivity(new Intent(Application.Context, typeof(MainActivity))); + + Finish(); } } } diff --git a/samples/ControlCatalog.Browser/Properties/launchSettings.json b/samples/ControlCatalog.Browser/Properties/launchSettings.json new file mode 100644 index 0000000000..76c1834e3c --- /dev/null +++ b/samples/ControlCatalog.Browser/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "ControlCatalog.Browser": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" + } + } +} diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 7b8b27fff7..4d28f15e2c 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -23,7 +23,7 @@ namespace ControlCatalog private static void ConfigureAssetAssembly(AppBuilder builder) { AvaloniaLocator.CurrentMutable - .GetService() + .GetRequiredService() .SetDefaultAssembly(typeof(App).Assembly); } } diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 9e0fb3f852..166b98436e 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -209,8 +209,7 @@ + HorizontalAlignment="Stretch"> None Transparent diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 15e666ae7b..f3c1a68e72 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.VisualTree; using ControlCatalog.Models; using ControlCatalog.Pages; @@ -59,17 +60,25 @@ namespace ControlCatalog }; var transparencyLevels = this.Get("TransparencyLevels"); - IDisposable? backgroundSetter = null, paneBackgroundSetter = null; + IDisposable? topLevelBackgroundSideSetter = null, sideBarBackgroundSetter = null, paneBackgroundSetter = null; transparencyLevels.SelectionChanged += (sender, e) => { - backgroundSetter?.Dispose(); + topLevelBackgroundSideSetter?.Dispose(); + sideBarBackgroundSetter?.Dispose(); paneBackgroundSetter?.Dispose(); - if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected - && selected != WindowTransparencyLevel.None) + if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected) { - var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5); - backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); - paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + var topLevel = (TopLevel)this.GetVisualRoot()!; + topLevel.TransparencyLevelHint = selected; + + if (selected != WindowTransparencyLevel.None) + { + var transparentBrush = new ImmutableSolidColorBrush(Colors.White, 0); + var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2); + topLevelBackgroundSideSetter = topLevel.SetValue(BackgroundProperty, transparentBrush, Avalonia.Data.BindingPriority.Style); + sideBarBackgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + } } }; } diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index cebb3e0916..442c1d37b0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -10,7 +10,6 @@ ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" ExtendClientAreaChromeHints="{Binding ChromeHints}" ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" - TransparencyLevelHint="{Binding TransparencyLevel}" x:Name="MainWindow" Background="Transparent" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs index 11f504f4db..7520dabf37 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs @@ -17,7 +17,7 @@ namespace ControlCatalog.Pages this.Get("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + - "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; + "12 - hour or 24 - hour clock and visibility of AM / PM is dynamically set based on user time settings, or can be overridden."; } diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 52c122f2bc..32cfa9b22b 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; diff --git a/samples/ControlCatalog/Pages/PointerCanvas.cs b/samples/ControlCatalog/Pages/PointerCanvas.cs index 7d26da8603..da1ff5442d 100644 --- a/samples/ControlCatalog/Pages/PointerCanvas.cs +++ b/samples/ControlCatalog/Pages/PointerCanvas.cs @@ -24,7 +24,7 @@ public class PointerCanvas : Control { struct CanvasPoint { - public IBrush Brush; + public IBrush? Brush; public Point Point; public double Radius; public double? Pressure; diff --git a/samples/ControlCatalog/Pages/PointerContactsTab.cs b/samples/ControlCatalog/Pages/PointerContactsTab.cs index b6aabebf99..1751b046c0 100644 --- a/samples/ControlCatalog/Pages/PointerContactsTab.cs +++ b/samples/ControlCatalog/Pages/PointerContactsTab.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 6af4cf353e..b5b80fb147 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -36,44 +36,44 @@ namespace ControlCatalog.Pages var drawBrush = Brushes.Black; Pen p = new Pen(drawBrush); - if (screens != null) - foreach (Screen screen in screens) + + foreach (Screen screen in screens) + { + if (screen.Bounds.X / 10f < _leftMost) { - if (screen.Bounds.X / 10f < _leftMost) - { - _leftMost = screen.Bounds.X / 10f; - Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); - return; - } + _leftMost = screen.Bounds.X / 10f; + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); + return; + } - Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f, - screen.Bounds.Height / 10f); - Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, - screen.WorkingArea.Height / 10f); - - context.DrawRectangle(p, boundsRect); - context.DrawRectangle(p, workingAreaRect); + Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f, + screen.Bounds.Height / 10f); + Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, + screen.WorkingArea.Height / 10f); + context.DrawRectangle(p, boundsRect); + context.DrawRectangle(p, workingAreaRect); - var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height)); - formattedText = - CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); + var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height)); - formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); + formattedText = + CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); + formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); + formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); - formattedText = - CreateFormattedText( - $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); - } + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); + + formattedText = + CreateFormattedText( + $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); + } context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10)); } diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index 413b6e1c75..f3f4bf6e93 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -51,7 +51,7 @@ namespace ControlCatalog.Pages private static IBitmap LoadBitmap(string uri) { - var assets = AvaloniaLocator.Current!.GetService()!; + var assets = AvaloniaLocator.Current.GetRequiredService(); return new Bitmap(assets.Open(new Uri(uri))); } } diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 6a4d9b7d0e..7408399873 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -38,7 +38,7 @@ UseFloatingWatermark="True" PasswordChar="*" Text="Password" /> - + - - None - Transparent - Blur - AcrylicBlur - Mica - diff --git a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs index bbe970afd6..d3e4ea7c31 100644 --- a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; using MiniMvvm; diff --git a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs index 4cfb00625e..f041f32b10 100644 --- a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; @@ -56,12 +55,9 @@ namespace ControlCatalog.ViewModels var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true }); - if (result != null) + foreach (var file in result) { - foreach (var file in result) - { - System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}"); - } + System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}"); } } diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs index 8a3f0ba947..f44d927801 100644 --- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels .Select(x => new StandardCursorModel(x)) .ToList(); - var loader = AvaloniaLocator.Current!.GetService()!; + var loader = AvaloniaLocator.Current.GetRequiredService(); var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); var bitmap = new Bitmap(s); CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index f89d9d1e20..7f32536b11 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; using ControlCatalog.Pages; diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index b79eff780c..3628a9b8a7 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -1,9 +1,9 @@ -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; +using Avalonia.Reactive; using System; using System.ComponentModel.DataAnnotations; using MiniMvvm; @@ -12,12 +12,9 @@ namespace ControlCatalog.ViewModels { class MainWindowViewModel : ViewModelBase { - private IManagedNotificationManager _notificationManager; - private bool _isMenuItemChecked = true; private WindowState _windowState; private WindowState[] _windowStates = Array.Empty(); - private int _transparencyLevel; private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; private bool _extendClientAreaEnabled; private bool _systemTitleBarEnabled; @@ -77,12 +74,6 @@ namespace ControlCatalog.ViewModels TitleBarHeight = -1; } - public int TransparencyLevel - { - get { return _transparencyLevel; } - set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); } - } - public ExtendClientAreaChromeHints ChromeHints { get { return _chromeHints; } diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 16051c3c05..df62ba04cb 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs index a31f164a2a..bcbcb345ef 100644 --- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs @@ -1,5 +1,4 @@ -using System.Reactive; -using Avalonia.Controls.Notifications; +using Avalonia.Controls.Notifications; using MiniMvvm; namespace ControlCatalog.ViewModels diff --git a/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs index d4b43043be..aa15d7758b 100644 --- a/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs +++ b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls.Notifications; using ControlCatalog.Pages; diff --git a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs index 93857fd899..4505c11e95 100644 --- a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels { public TransitioningContentControlPageViewModel() { - var assetLoader = AvaloniaLocator.Current?.GetService()!; + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var images = new string[] { diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs index 80d4844f7a..7c0855e0af 100644 --- a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using MiniMvvm; diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 471c42ec07..3b14f0ce12 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -2,9 +2,8 @@ false $(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll + false + 11 - - false - diff --git a/samples/MiniMvvm/MiniMvvm.csproj b/samples/MiniMvvm/MiniMvvm.csproj index 6535b2bdbd..2a9164624a 100644 --- a/samples/MiniMvvm/MiniMvvm.csproj +++ b/samples/MiniMvvm/MiniMvvm.csproj @@ -2,5 +2,7 @@ netstandard2.0 - + + + diff --git a/samples/MiniMvvm/PropertyChangedExtensions.cs b/samples/MiniMvvm/PropertyChangedExtensions.cs index f1065c7530..f51773810d 100644 --- a/samples/MiniMvvm/PropertyChangedExtensions.cs +++ b/samples/MiniMvvm/PropertyChangedExtensions.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Linq; using System.Reflection; +using Avalonia.Reactive; namespace MiniMvvm { @@ -92,11 +92,13 @@ namespace MiniMvvm Expression> v3, Func cb ) where TModel : INotifyPropertyChanged => - Observable.CombineLatest( - model.WhenAnyValue(v1), - model.WhenAnyValue(v2), - model.WhenAnyValue(v3), - cb); + model.WhenAnyValue(v1) + .CombineLatest( + model.WhenAnyValue(v2), + (l, r) => (l, r)) + .CombineLatest( + model.WhenAnyValue(v3), + (t, r) => cb(t.l, t.r, r)); public static IObservable> WhenAnyValue(this TModel model, Expression> v1, diff --git a/samples/MiniMvvm/ViewModelBase.cs b/samples/MiniMvvm/ViewModelBase.cs index 7256b05cef..8bc398607f 100644 --- a/samples/MiniMvvm/ViewModelBase.cs +++ b/samples/MiniMvvm/ViewModelBase.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.ComponentModel; -using System.Reactive.Joins; using System.Runtime.CompilerServices; namespace MiniMvvm diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 5c743aabdb..5f61a08f3c 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -11,4 +11,5 @@ + diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 2cc84168dc..76c1ba7b69 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -12,7 +12,8 @@ - + + diff --git a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj index 94ca4ee809..9650068434 100644 --- a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj +++ b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj @@ -23,6 +23,5 @@ - diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 223a53d8c5..1643ca3ee2 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -15,6 +15,4 @@ ControlCatalog - - diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index de9149e9a1..152076013f 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -1,10 +1,10 @@ using System; -using System.Reactive.Disposables; using System.Threading; using Android.OS; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Threading; using App = Android.App.Application; diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs index ec26ee5599..38038ef26c 100644 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -1,6 +1,5 @@ using Android.OS; using AndroidX.AppCompat.App; -using Avalonia.Controls; namespace Avalonia.Android { @@ -8,15 +7,22 @@ namespace Avalonia.Android { protected abstract AppBuilder CreateAppBuilder(); + private static AppBuilder s_appBuilder; + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); - var builder = CreateAppBuilder(); + if (s_appBuilder == null) + { + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); - var lifetime = new SingleViewLifetime(); + builder.SetupWithLifetime(lifetime); - builder.SetupWithLifetime(lifetime); + s_appBuilder = builder; + } } } diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 5267843bfc..f7e32f99db 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -24,6 +24,8 @@ namespace Avalonia.Android _root = new EmbeddableControlRoot(_view); _root.Prepare(); + + this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); } internal TopLevelImpl TopLevelImpl => _view; diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs index 19dc7b4ab6..3545ae8fe1 100644 --- a/src/Android/Avalonia.Android/ChoreographerTimer.cs +++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading.Tasks; using Android.OS; using Android.Views; +using Avalonia.Reactive; using Avalonia.Rendering; using Java.Lang; diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index 33501ece06..f205458f0e 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -22,6 +22,7 @@ namespace Avalonia.Android public InvalidationAwareSurfaceView(Context context) : base(context) { Holder.AddCallback(this); + Holder.SetFormat(global::Android.Graphics.Format.Transparent); _handler = new Handler(context.MainLooper); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 56dbadca03..1aac567dda 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; using AndroidRect = Android.Graphics.Rect; +using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -283,7 +284,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } - public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; + public WindowTransparencyLevel TransparencyLevel { get; private set; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); @@ -301,7 +302,87 @@ namespace Avalonia.Android.Platform.SkiaPlatform public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { - throw new NotImplementedException(); + if (TransparencyLevel != transparencyLevel) + { + bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; + bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; + if (_view.Context is AvaloniaMainActivity activity) + { + switch (transparencyLevel) + { + case WindowTransparencyLevel.AcrylicBlur: + case WindowTransparencyLevel.ForceAcrylicBlur: + case WindowTransparencyLevel.Mica: + case WindowTransparencyLevel.None: + if (!isBelowR) + { + activity.SetTranslucent(false); + } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 0; + + activity.Window.Attributes = attr; + } + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + if(transparencyLevel != WindowTransparencyLevel.None) + { + return; + } + break; + case WindowTransparencyLevel.Transparent: + if (!isBelowR) + { + activity.SetTranslucent(true); + } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 0; + + activity.Window.Attributes = attr; + } + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + break; + case WindowTransparencyLevel.Blur: + if (isAboveR) + { + activity.SetTranslucent(true); + activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 120; + + activity.Window.Attributes = attr; + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + else + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + return; + } + break; + } + TransparencyLevel = transparencyLevel; + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs index 6d0e6be0ad..3d0f3b2652 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } - var eventTime = (ulong)DateTime.Now.Millisecond; + var eventTime = (ulong)e.EventTime; var inputRoot = _view.InputRoot; var actionMasked = e.ActionMasked; var modifiers = GetModifiers(e.MetaState, e.ButtonState); diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index 06087cdd6a..d62acc0d52 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 6a6e69894b..682629c801 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -1,10 +1,8 @@ using System; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Animation.Animators; -using Avalonia.Animation.Utils; using Avalonia.Data; -using Avalonia.Reactive; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs index 3168a67d79..dcce75b31c 100644 --- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs @@ -63,7 +63,7 @@ namespace Avalonia.Animation } else { - return this.Bind(ValueProperty, ObservableEx.SingleValue(value).ToBinding(), targetControl); + return this.Bind(ValueProperty, Observable.SingleValue(value).ToBinding(), targetControl); } } diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs index b5d1feb4a7..2db890bd0a 100644 --- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Base/Animation/Animators/Animator`1.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Collections; using Avalonia.Data; diff --git a/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs b/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs index 72add21d69..7be974b9e5 100644 --- a/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs @@ -2,7 +2,7 @@ // and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp). using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Logging; using Avalonia.Media; diff --git a/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs index e12ca722f9..8040fb595b 100644 --- a/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Transformation; diff --git a/src/Avalonia.Base/Animation/Clock.cs b/src/Avalonia.Base/Animation/Clock.cs index 5afd2ae705..f2bce9d3a5 100644 --- a/src/Avalonia.Base/Animation/Clock.cs +++ b/src/Avalonia.Base/Animation/Clock.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Animation/CrossFade.cs b/src/Avalonia.Base/Animation/CrossFade.cs index a229bc7ce6..640d6456a3 100644 --- a/src/Avalonia.Base/Animation/CrossFade.cs +++ b/src/Avalonia.Base/Animation/CrossFade.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; using Avalonia.Animation.Easings; @@ -108,7 +108,7 @@ namespace Avalonia.Animation } var tasks = new List(); - using (var disposables = new CompositeDisposable()) + using (var disposables = new CompositeDisposable(1)) { if (to != null) { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index d6f1542687..cd122a8b67 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -14,7 +14,6 @@ - @@ -37,6 +36,13 @@ + + + + + + + @@ -48,8 +54,12 @@ + + + + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a3a732428e..dc94dfba40 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -621,7 +621,7 @@ namespace Avalonia /// The old property value. /// The new property value. /// The priority of the binding that produced the value. - private protected void RaisePropertyChanged( + protected void RaisePropertyChanged( DirectPropertyBase property, Optional oldValue, BindingValue newValue, diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 867d6215a5..7b17b9152d 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -1,10 +1,6 @@ using System; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Avalonia.Data; using Avalonia.Reactive; +using Avalonia.Data; namespace Avalonia { @@ -127,108 +123,6 @@ namespace Avalonia property ?? throw new ArgumentNullException(nameof(property))); } - /// - /// Gets a subject for an . - /// - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject GetSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create( - Observer.Create(x => o.SetValue(property, x, priority)), - o.GetObservable(property)); - } - - /// - /// Gets a subject for an . - /// - /// The property type. - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject GetSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create( - Observer.Create(x => o.SetValue(property, x, priority)), - o.GetObservable(property)); - } - - /// - /// Gets a subject for a . - /// - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject> GetBindingSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create>( - Observer.Create>(x => - { - if (x.HasValue) - { - o.SetValue(property, x.Value, priority); - } - }), - o.GetBindingObservable(property)); - } - - /// - /// Gets a subject for a . - /// - /// The property type. - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject> GetBindingSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create>( - Observer.Create>(x => - { - if (x.HasValue) - { - o.SetValue(property, x.Value, priority); - } - }), - o.GetBindingObservable(property)); - } - /// /// Binds an to an observable. /// @@ -407,13 +301,7 @@ namespace Avalonia Action action) where TTarget : AvaloniaObject { - return observable.Subscribe(e => - { - if (e.Sender is TTarget target) - { - action(target, e); - } - }); + return observable.Subscribe(new ClassHandlerObserver(action)); } /// @@ -431,13 +319,7 @@ namespace Avalonia this IObservable> observable, Action> action) where TTarget : AvaloniaObject { - return observable.Subscribe(e => - { - if (e.Sender is TTarget target) - { - action(target, e); - } - }); + return observable.Subscribe(new ClassHandlerObserver(action)); } private class BindingAdaptor : IBinding @@ -458,5 +340,57 @@ namespace Avalonia return InstancedBinding.OneWay(_source); } } + + private class ClassHandlerObserver : IObserver> + { + private readonly Action> _action; + + public ClassHandlerObserver(Action> action) + { + _action = action; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + if (value.Sender is TTarget target) + { + _action(target, value); + } + } + } + + private class ClassHandlerObserver : IObserver + { + private readonly Action _action; + + public ClassHandlerObserver(Action action) + { + _action = action; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + if (value.Sender is TTarget target) + { + _action(target, value); + } + } + } } } diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 53444ee475..f8c062a176 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Subjects; using Avalonia.Data; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia @@ -12,7 +12,7 @@ namespace Avalonia /// The value type of the property. public abstract class AvaloniaProperty : AvaloniaProperty { - private readonly Subject> _changed; + private readonly LightweightSubject> _changed; /// /// Initializes a new instance of the class. @@ -28,7 +28,7 @@ namespace Avalonia Action? notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { - _changed = new Subject>(); + _changed = new LightweightSubject>(); } /// diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs index 589b9b2d01..a9726cb86e 100644 --- a/src/Avalonia.Base/ClassBindingManager.cs +++ b/src/Avalonia.Base/ClassBindingManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia { diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs index c4684960d6..2178577eb7 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs @@ -3,7 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Reactive.Disposables; +using Avalonia.Reactive; namespace Avalonia.Collections { diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs index 689fcc89a4..68863ea257 100644 --- a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs +++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Specialized; -using System.Reactive.Linq; using Avalonia.Reactive; using Avalonia.Utilities; diff --git a/src/Avalonia.Base/Controls/NameScopeExtensions.cs b/src/Avalonia.Base/Controls/NameScopeExtensions.cs index 3895b6ceb9..0abd5e56f7 100644 --- a/src/Avalonia.Base/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Base/Controls/NameScopeExtensions.cs @@ -25,13 +25,18 @@ namespace Avalonia.Controls var result = nameScope.Find(name); - if (result != null && !(result is T)) + if (result == null) { - throw new InvalidOperationException( - $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); + return null; } - return (T?)result; + if (result is T typed) + { + return typed; + } + + throw new InvalidOperationException( + $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); } /// @@ -74,13 +79,13 @@ namespace Avalonia.Controls throw new KeyNotFoundException($"Could not find control '{name}'."); } - if (!(result is T)) + if (result is T typed) { - throw new InvalidOperationException( - $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); + return typed; } - return (T)result; + throw new InvalidOperationException( + $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); } /// diff --git a/src/Avalonia.Base/Controls/NameScopeLocator.cs b/src/Avalonia.Base/Controls/NameScopeLocator.cs index f0ce7f8a5b..371931f971 100644 --- a/src/Avalonia.Base/Controls/NameScopeLocator.cs +++ b/src/Avalonia.Base/Controls/NameScopeLocator.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Controls diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index ceb3f71285..0b737dd959 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -1,6 +1,5 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Data { @@ -46,15 +45,15 @@ namespace Avalonia.Data throw new InvalidOperationException("InstancedBinding does not contain an observable."); return target.Bind(property, binding.Observable, binding.Priority); case BindingMode.TwoWay: + if (binding.Observable is null) + throw new InvalidOperationException("InstancedBinding does not contain an observable."); if (binding.Subject is null) throw new InvalidOperationException("InstancedBinding does not contain a subject."); return new TwoWayBindingDisposable( - target.Bind(property, binding.Subject, binding.Priority), + target.Bind(property, binding.Observable, binding.Priority), target.GetObservable(property).Subscribe(binding.Subject)); case BindingMode.OneTime: - var source = binding.Subject ?? binding.Observable; - - if (source != null) + if (binding.Observable is {} source) { // Perf: Avoid allocating closure in the outer scope. var targetCopy = target; diff --git a/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs index 2577cac743..5084d8e822 100644 --- a/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs @@ -38,7 +38,7 @@ namespace Avalonia.Data.Converters } else if (Equals(obj, default(TIn))) { - yield return default(TIn); + yield return default; } } } diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index e340966983..536c14dcf9 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive.Linq; using Avalonia.Reactive; namespace Avalonia.Data.Core diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index f60b4722d9..55caf8070e 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -1,11 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using Avalonia.Reactive; using Avalonia.Data.Converters; using Avalonia.Logging; -using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Data.Core @@ -15,7 +13,7 @@ namespace Avalonia.Data.Core /// that are sent and received. /// [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] - public class BindingExpression : LightweightObservableBase, ISubject, IDescription + public class BindingExpression : LightweightObservableBase, IAvaloniaSubject, IDescription { private readonly ExpressionObserver _inner; private readonly Type _targetType; diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 2151c100e5..0818b5fa62 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; using Avalonia.Reactive; @@ -99,14 +97,14 @@ namespace Avalonia.Data.Core /// /// A function which gets the root object. /// The expression. - /// An observable which triggers a re-read of the getter. + /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// /// A description of the expression. /// public ExpressionObserver( Func rootGetter, ExpressionNode node, - IObservable update, + IObservable update, string? description) { Description = description; @@ -164,7 +162,7 @@ namespace Avalonia.Data.Core /// /// A function which gets the root object. /// The expression. - /// An observable which triggers a re-read of the getter. + /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. @@ -173,7 +171,7 @@ namespace Avalonia.Data.Core public static ExpressionObserver Create( Func rootGetter, Expression> expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null) { @@ -296,9 +294,10 @@ namespace Avalonia.Data.Core if (_root is IObservable observable) { _rootSubscription = observable.Subscribe( - x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), - x => PublishCompleted(), - () => PublishCompleted()); + new AnonymousObserver( + x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), + x => PublishCompleted(), + PublishCompleted)); } else { diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs index a808827896..2fad96701d 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs @@ -1,48 +1,47 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Data.Core { - public abstract class IndexerNodeBase : SettableNode + public abstract class IndexerNodeBase : SettableNode, + IWeakEventSubscriber, + IWeakEventSubscriber { - private IDisposable? _subscription; - protected override void StartListeningCore(WeakReference reference) { reference.TryGetTarget(out var target); - var incc = target as INotifyCollectionChanged; - var inpc = target as INotifyPropertyChanged; - var inputs = new List>(); - - if (incc != null) + if (target is INotifyCollectionChanged incc) { - inputs.Add(WeakObservable.FromEventPattern( - incc, WeakEvents.CollectionChanged) - .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) - .Select(_ => GetValue(target))); + WeakEvents.CollectionChanged.Subscribe(incc, this); } - if (inpc != null) + if (target is INotifyPropertyChanged inpc) { - inputs.Add(WeakObservable.FromEventPattern( - inpc, WeakEvents.PropertyChanged) - .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) - .Select(_ => GetValue(target))); + WeakEvents.PropertyChanged.Subscribe(inpc, this); } - - _subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged); + + ValueChanged(GetValue(target)); } protected override void StopListeningCore() { - _subscription?.Dispose(); + if (Target.TryGetTarget(out var target)) + { + if (target is INotifyCollectionChanged incc) + { + WeakEvents.CollectionChanged.Unsubscribe(incc, this); + } + + if (target is INotifyPropertyChanged inpc) + { + WeakEvents.PropertyChanged.Unsubscribe(inpc, this); + } + } } protected abstract object? GetValue(object? target); @@ -83,5 +82,21 @@ namespace Avalonia.Data.Core } protected abstract bool ShouldUpdate(object? sender, PropertyChangedEventArgs e); + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs e) + { + if (ShouldUpdate(sender, e)) + { + ValueChanged(GetValue(sender)); + } + } + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e) + { + if (ShouldUpdate(sender, e)) + { + ValueChanged(GetValue(sender)); + } + } } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index ebee4586db..b40628fd35 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Reflection; namespace Avalonia.Data.Core.Plugins @@ -12,8 +12,15 @@ namespace Avalonia.Data.Core.Plugins [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class ObservableStreamPlugin : IStreamPlugin { - static MethodInfo? observableSelect; + private static MethodInfo? s_observableGeneric; + private static MethodInfo? s_observableSelect; + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] + public ObservableStreamPlugin() + { + + } + /// /// Checks whether this plugin handles the specified value. /// @@ -54,56 +61,32 @@ namespace Avalonia.Data.Core.Plugins x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; - // Get the Observable.Select method. - var select = GetObservableSelect(sourceType); - - // Make a Box<> delegate of the correct type. - var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object)); - var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)! - .MakeGenericMethod(sourceType) - .CreateDelegate(funcType); + // Get the BoxObservable method. + var select = GetBoxObservable(sourceType); - // Call Observable.Select(target, box); + // Call BoxObservable(target); return (IObservable)select.Invoke( null, - new object[] { target, box })!; + new[] { target })!; } [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] - private static MethodInfo GetObservableSelect(Type source) + private static MethodInfo GetBoxObservable(Type source) { - return GetObservableSelect().MakeGenericMethod(source, typeof(object)); + return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); } - private static MethodInfo GetObservableSelect() + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] + private static MethodInfo GetBoxObservable() { - if (observableSelect == null) - { - observableSelect = typeof(Observable).GetRuntimeMethods().First(x => - { - if (x.Name == nameof(Observable.Select) && - x.ContainsGenericParameters && - x.GetGenericArguments().Length == 2) - { - var parameters = x.GetParameters(); - - if (parameters.Length == 2 && - parameters[0].ParameterType.IsConstructedGenericType && - parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) && - parameters[1].ParameterType.IsConstructedGenericType && - parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)) - { - return true; - } - } - - return false; - }); - } - - return observableSelect; + return s_observableSelect + ??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("BoxObservable method was not found."); } - private static object? Box(T value) => (object?)value; + private static IObservable BoxObservable(IObservable source) + { + return source.Select(v => (object?)v); + } } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 5203aa9f57..715f4604cf 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs @@ -1,9 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Reflection; using System.Threading.Tasks; +using Avalonia.Reactive; namespace Avalonia.Data.Core.Plugins { @@ -50,7 +49,7 @@ namespace Avalonia.Data.Core.Plugins case TaskStatus.Faulted: return HandleCompleted(task); default: - var subject = new Subject(); + var subject = new LightweightSubject(); task.ContinueWith( x => HandleCompleted(task).Subscribe(subject), TaskScheduler.FromCurrentSynchronizationContext()) diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 6dc6d07184..ba18a2173b 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Data.Core { diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index fcd179b9b2..83ef8f76b4 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Data +using Avalonia.Reactive; + +namespace Avalonia.Data { public class IndexerBinding : IBinding { @@ -22,7 +24,10 @@ object? anchor = null, bool enableDataValidation = false) { - return new InstancedBinding(Source.GetSubject(Property), Mode, BindingPriority.LocalValue); + var subject = new CombinedSubject( + new AnonymousObserver(x => Source.SetValue(Property, x, BindingPriority.LocalValue)), + Source.GetObservable(Property)); + return new InstancedBinding(subject, Mode, BindingPriority.LocalValue); } } } diff --git a/src/Avalonia.Base/Data/IndexerDescriptor.cs b/src/Avalonia.Base/Data/IndexerDescriptor.cs index c823630d3c..3eb5a6ef12 100644 --- a/src/Avalonia.Base/Data/IndexerDescriptor.cs +++ b/src/Avalonia.Base/Data/IndexerDescriptor.cs @@ -1,12 +1,11 @@ using System; -using System.Reactive; namespace Avalonia.Data { /// /// Holds a description of a binding for 's [] operator. /// - public class IndexerDescriptor : ObservableBase, IDescription + public class IndexerDescriptor : IObservable, IDescription { /// /// Gets or sets the binding mode. @@ -104,7 +103,7 @@ namespace Avalonia.Data } /// - protected override IDisposable SubscribeCore(IObserver observer) + public IDisposable Subscribe(IObserver observer) { if (SourceObservable is null && Source is null) throw new InvalidOperationException("Cannot subscribe to IndexerDescriptor."); diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index 4a1e2660de..a60c1d72ec 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Data { @@ -14,26 +14,7 @@ namespace Avalonia.Data /// public class InstancedBinding { - /// - /// Initializes a new instance of the class. - /// - /// The binding source. - /// The binding mode. - /// The priority of the binding. - /// - /// This constructor can be used to create any type of binding and as such requires an - /// as the binding source because this is the only binding - /// source which can be used for all binding modes. If you wish to create an instance with - /// something other than a subject, use one of the static creation methods on this class. - /// - public InstancedBinding(ISubject subject, BindingMode mode, BindingPriority priority) - { - Mode = mode; - Priority = priority; - Value = subject ?? throw new ArgumentNullException(nameof(subject)); - } - - private InstancedBinding(object? value, BindingMode mode, BindingPriority priority) + internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority) { Mode = mode; Priority = priority; @@ -61,9 +42,14 @@ namespace Avalonia.Data public IObservable? Observable => Value as IObservable; /// - /// Gets the as a subject. + /// Gets the as an observer. + /// + public IObserver? Observer => Value as IObserver; + + /// + /// Gets the as an subject. /// - public ISubject? Subject => Value as ISubject; + internal IAvaloniaSubject? Subject => Value as IAvaloniaSubject; /// /// Creates a new one-time binding with a fixed value. @@ -111,30 +97,34 @@ namespace Avalonia.Data /// /// Creates a new one-way to source binding. /// - /// The binding source. + /// The binding source. /// The priority of the binding. /// An instance. public static InstancedBinding OneWayToSource( - ISubject subject, + IObserver observer, BindingPriority priority = BindingPriority.LocalValue) { - _ = subject ?? throw new ArgumentNullException(nameof(subject)); + _ = observer ?? throw new ArgumentNullException(nameof(observer)); - return new InstancedBinding(subject, BindingMode.OneWayToSource, priority); + return new InstancedBinding(observer, BindingMode.OneWayToSource, priority); } /// /// Creates a new two-way binding. /// - /// The binding source. + /// The binding source. + /// The binding source. /// The priority of the binding. /// An instance. public static InstancedBinding TwoWay( - ISubject subject, + IObservable observable, + IObserver observer, BindingPriority priority = BindingPriority.LocalValue) { - _ = subject ?? throw new ArgumentNullException(nameof(subject)); + _ = observable ?? throw new ArgumentNullException(nameof(observable)); + _ = observer ?? throw new ArgumentNullException(nameof(observer)); + var subject = new CombinedSubject(observer, observable); return new InstancedBinding(subject, BindingMode.TwoWay, priority); } diff --git a/src/Avalonia.Base/Input/Cursor.cs b/src/Avalonia.Base/Input/Cursor.cs index 8e79206f93..c555087879 100644 --- a/src/Avalonia.Base/Input/Cursor.cs +++ b/src/Avalonia.Base/Input/Cursor.cs @@ -71,8 +71,7 @@ namespace Avalonia.Input private static ICursorFactory GetCursorFactory() { - return AvaloniaLocator.Current.GetService() ?? - throw new Exception("Could not create Cursor: ICursorFactory not registered."); + return AvaloniaLocator.Current.GetRequiredService(); } } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 64fe275547..cec98ec66b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -17,7 +17,9 @@ namespace Avalonia.Input.GestureRecognizers private bool _canVerticallyScroll; private int _gestureId; private int _scrollStartDistance = 30; - + private Point _pointerPressedPoint; + private VelocityTracker? _velocityTracker; + // Movement per second private Vector _inertia; private ulong? _lastMoveTimestamp; @@ -91,7 +93,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = e.GetPosition((Visual?)_target); + _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); } } @@ -111,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { + _velocityTracker = new VelocityTracker(); + + // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance + _trackedRootPoint = new Point( + _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance), + _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance)); + _actions!.Capture(e.Pointer, this); } } @@ -118,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers if (_scrolling) { var vector = _trackedRootPoint - rootPoint; - var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ? - TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) : - TimeSpan.Zero; - + + _velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint); + _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; - if (elapsed.TotalSeconds > 0) - _inertia = vector / elapsed.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } @@ -150,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers } } - - + + public void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { + _inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero; + e.Handled = true; if (_inertia == default || e.Timestamp == 0 @@ -183,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers var distance = speed * elapsedSinceLastTick.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); - - - if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling + if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd) { EndGesture(); return false; diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs new file mode 100644 index 0000000000..ce41aa6308 --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -0,0 +1,375 @@ +// Code in this file is derived from +// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart + +using System; +using System.Diagnostics; +using Avalonia.Utilities; + +namespace Avalonia.Input.GestureRecognizers +{ + // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? + + internal readonly record struct Velocity(Vector PixelsPerSecond) + { + public Velocity ClampMagnitude(double minValue, double maxValue) + { + Debug.Assert(minValue >= 0.0); + Debug.Assert(maxValue >= 0.0 && maxValue >= minValue); + double valueSquared = PixelsPerSecond.SquaredLength; + if (valueSquared > maxValue * maxValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero); + // preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a + // ScrollGestureEventArgs it results in runtime errors. + } + if (valueSquared < minValue * minValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero); + } + return this; + } + } + + /// A two dimensional velocity estimate. + /// + /// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An + /// estimate's [confidence] measures how well the velocity tracker's position + /// data fit a straight line, [duration] is the time that elapsed between the + /// first and last position sample used to compute the velocity, and [offset] + /// is similarly the difference between the first and last positions. + /// + /// See also: + /// + /// * [VelocityTracker], which computes [VelocityEstimate]s. + /// * [Velocity], which encapsulates (just) a velocity vector and provides some + /// useful velocity operations. + internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); + + internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time); + + /// Computes a pointer's velocity based on data from [PointerMoveEvent]s. + /// + /// The input data is provided by calling [addPosition]. Adding data is cheap. + /// + /// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will + /// compute the velocity based on the data added so far. Only call these when + /// you need to use the velocity, as they are comparatively expensive. + /// + /// The quality of the velocity estimation will be better if more data points + /// have been received. + internal class VelocityTracker + { + private const int AssumePointerMoveStoppedMilliseconds = 40; + private const int HistorySize = 20; + private const int HorizonMilliseconds = 100; + private const int MinSampleSize = 3; + private const double MinFlingVelocity = 50.0; // Logical pixels / second + private const double MaxFlingVelocity = 8000.0; + + private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; + private int _index = 0; + + /// + /// Adds a position as the given time to the tracker. + /// + /// + /// + public void AddPosition(TimeSpan time, Vector position) + { + _index++; + if (_index == HistorySize) + { + _index = 0; + } + _samples[_index] = new PointAtTime(true, position, time); + } + + /// Returns an estimate of the velocity of the object being tracked by the + /// tracker given the current information available to the tracker. + /// + /// Information is added using [addPosition]. + /// + /// Returns null if there is no data on which to base an estimate. + protected virtual VelocityEstimate? GetVelocityEstimate() + { + Span x = stackalloc double[HistorySize]; + Span y = stackalloc double[HistorySize]; + Span w = stackalloc double[HistorySize]; + Span time = stackalloc double[HistorySize]; + int sampleCount = 0; + int index = _index; + + var newestSample = _samples[index]; + if (!newestSample.Valid) + { + return null; + } + + var previousSample = newestSample; + var oldestSample = newestSample; + + // Starting with the most recent PointAtTime sample, iterate backwards while + // the samples represent continuous motion. + do + { + var sample = _samples[index]; + if (!sample.Valid) + { + break; + } + + double age = (newestSample.Time - sample.Time).TotalMilliseconds; + double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds); + previousSample = sample; + if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds) + { + break; + } + + oldestSample = sample; + var position = sample.Point; + x[sampleCount] = position.X; + y[sampleCount] = position.Y; + w[sampleCount] = 1.0; + time[sampleCount] = -age; + index = (index == 0 ? HistorySize : index) - 1; + + sampleCount++; + } while (sampleCount < HistorySize); + + if (sampleCount >= MinSampleSize) + { + var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount)); + if (xFit != null) + { + var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount)); + if (yFit != null) + { + return new VelocityEstimate( // convert from pixels/ms to pixels/s + PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000), + Confidence: xFit.Confidence * yFit.Confidence, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + } + } + + // We're unable to make a velocity estimate but we did have at least one + // valid pointer position. + return new VelocityEstimate( + PixelsPerSecond: Vector.Zero, + Confidence: 1.0, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + + /// + /// Computes the velocity of the pointer at the time of the last + /// provided data point. + /// + /// This can be expensive. Only call this when you need the velocity. + /// + /// Returns [Velocity.zero] if there is no data from which to compute an + /// estimate or if the estimated velocity is zero./// + /// + /// + internal Velocity GetVelocity() + { + var estimate = GetVelocityEstimate(); + if (estimate == null || estimate.PixelsPerSecond.IsDefault) + { + return new Velocity(Vector.Zero); + } + return new Velocity(estimate.PixelsPerSecond); + } + + internal virtual Velocity GetFlingVelocity() + { + return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity); + } + } + + /// An nth degree polynomial fit to a dataset. + internal class PolynomialFit + { + /// Creates a polynomial fit of the given degree. + /// + /// There are n + 1 coefficients in a fit of degree n. + internal PolynomialFit(int degree) + { + Coefficients = new double[degree + 1]; + } + + /// The polynomial coefficients of the fit. + public double[] Coefficients { get; } + + /// An indicator of the quality of the fit. + /// + /// Larger values indicate greater quality. + public double Confidence { get; set; } + } + + internal class LeastSquaresSolver + { + private const double PrecisionErrorTolerance = 1e-10; + + /// + /// Fits a polynomial of the given degree to the data points. + /// When there is not enough data to fit a curve null is returned. + /// + public static PolynomialFit? Solve(int degree, ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan w) + { + if (degree > x.Length) + { + // Not enough data to fit a curve. + return null; + } + + PolynomialFit result = new PolynomialFit(degree); + + // Shorthands for the purpose of notation equivalence to original C++ code. + int m = x.Length; + int n = degree + 1; + + // Expand the X vector to a matrix A, pre-multiplied by the weights. + _Matrix a = new _Matrix(m, stackalloc double[n * m]); + for (int h = 0; h < m; h += 1) + { + a[0, h] = w[h]; + for (int i = 1; i < n; i += 1) + { + a[i, h] = a[i - 1, h] * x[h]; + } + } + + // Apply the Gram-Schmidt process to A to obtain its QR decomposition. + + // Orthonormal basis, column-major order Vector. + _Matrix q = new _Matrix(m, stackalloc double[n * m]); + // Upper triangular matrix, row-major order. + _Matrix r = new _Matrix(n, stackalloc double[n * n]); + for (int j = 0; j < n; j += 1) + { + for (int h = 0; h < m; h += 1) + { + q[j, h] = a[j, h]; + } + for (int i = 0; i < j; i += 1) + { + double dot = Multiply(q.GetRow(j), q.GetRow(i)); + for (int h = 0; h < m; h += 1) + { + q[j, h] = q[j, h] - dot * q[i, h]; + } + } + + double norm = Norm(q.GetRow(j)); + if (norm < PrecisionErrorTolerance) + { + // Vectors are linearly dependent or zero so no solution. + return null; + } + + double inverseNorm = 1.0 / norm; + for (int h = 0; h < m; h += 1) + { + q[j, h] = q[j, h] * inverseNorm; + } + for (int i = 0; i < n; i += 1) + { + r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i)); + } + } + + // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. + // We just work from bottom-right to top-left calculating B's coefficients. + // "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe. + Span wy = stackalloc double[m]; + for (int h = 0; h < m; h += 1) + { + wy[h] = y[h] * w[h]; + } + for (int i = n - 1; i >= 0; i -= 1) + { + result.Coefficients[i] = Multiply(q.GetRow(i), wy); + for (int j = n - 1; j > i; j -= 1) + { + result.Coefficients[i] -= r[i, j] * result.Coefficients[j]; + } + result.Coefficients[i] /= r[i, i]; + } + + // Calculate the coefficient of determination (confidence) as: + // 1 - (sumSquaredError / sumSquaredTotal) + // ...where sumSquaredError is the residual sum of squares (variance of the + // error), and sumSquaredTotal is the total sum of squares (variance of the + // data) where each has been weighted. + double yMean = 0.0; + for (int h = 0; h < m; h += 1) + { + yMean += y[h]; + } + yMean /= m; + + double sumSquaredError = 0.0; + double sumSquaredTotal = 0.0; + for (int h = 0; h < m; h += 1) + { + double term = 1.0; + double err = y[h] - result.Coefficients[0]; + for (int i = 1; i < n; i += 1) + { + term *= x[h]; + err -= term * result.Coefficients[i]; + } + sumSquaredError += w[h] * w[h] * err * err; + double v = y[h] - yMean; + sumSquaredTotal += w[h] * w[h] * v * v; + } + + result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 : + 1.0 - (sumSquaredError / sumSquaredTotal); + + return result; + } + + private static double Multiply(Span v1, Span v2) + { + double result = 0.0; + for (int i = 0; i < v1.Length; i += 1) + { + result += v1[i] * v2[i]; + } + return result; + } + + private static double Norm(Span v) + { + return Math.Sqrt(Multiply(v, v)); + } + + private readonly ref struct _Matrix + { + private readonly int _columns; + private readonly Span _elements; + + internal _Matrix(int cols, Span elements) + { + _columns = cols; + _elements = elements; + } + + public double this[int row, int col] + { + get => _elements[row * _columns + col]; + set => _elements[row * _columns + col] = value; + } + + public Span GetRow(int row) => _elements.Slice(row * _columns, _columns); + } + } +} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 54e61d89b2..a9e42c2374 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -3,6 +3,7 @@ using System.Threading; using Avalonia.Interactivity; using Avalonia.Platform; using Avalonia.Threading; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Input diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index f233fdce51..962c7aa334 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -7,6 +7,7 @@ using Avalonia.Data; using Avalonia.Input.GestureRecognizers; using Avalonia.Input.TextInput; using Avalonia.Interactivity; +using Avalonia.Reactive; using Avalonia.VisualTree; #nullable enable diff --git a/src/Avalonia.Base/Input/InputManager.cs b/src/Avalonia.Base/Input/InputManager.cs index f604ff8e74..e87665fd54 100644 --- a/src/Avalonia.Base/Input/InputManager.cs +++ b/src/Avalonia.Base/Input/InputManager.cs @@ -1,6 +1,6 @@ using System; -using System.Reactive.Subjects; using Avalonia.Input.Raw; +using Avalonia.Reactive; namespace Avalonia.Input { @@ -10,9 +10,9 @@ namespace Avalonia.Input /// public class InputManager : IInputManager { - private readonly Subject _preProcess = new Subject(); - private readonly Subject _process = new Subject(); - private readonly Subject _postProcess = new Subject(); + private readonly LightweightSubject _preProcess = new(); + private readonly LightweightSubject _process = new(); + private readonly LightweightSubject _postProcess = new(); /// /// Gets the global instance of the input manager. diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 9b17e336d3..e1c42c4ead 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Reactive; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Utilities; diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 8a3f5b968e..9b5668bf98 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -1,5 +1,5 @@ using System; -using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Input.TextInput { diff --git a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs index b12cf64fdf..5264b96679 100644 --- a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs +++ b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs @@ -1,6 +1,5 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Interactivity { diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs index 7046871538..79a4dc60e4 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Interactivity { @@ -13,8 +13,8 @@ namespace Avalonia.Interactivity public class RoutedEvent { - private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>(); - private readonly Subject _routeFinished = new Subject(); + private readonly LightweightSubject<(object, RoutedEventArgs)> _raised = new(); + private readonly LightweightSubject _routeFinished = new(); public RoutedEvent( string name, diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index d185148894..775b8adddd 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,6 +1,6 @@ using System; using Avalonia.Logging; -using Avalonia.Styling; +using Avalonia.Reactive; using Avalonia.VisualTree; #nullable enable @@ -470,14 +470,12 @@ namespace Avalonia.Layout protected static void AffectsMeasure(params AvaloniaProperty[] properties) where T : Layoutable { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateMeasure(); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.InvalidateMeasure()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidateObserver); } } @@ -493,14 +491,12 @@ namespace Avalonia.Layout protected static void AffectsArrange(params AvaloniaProperty[] properties) where T : Layoutable { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateArrange(); - } + var invalidate = new AnonymousObserver( + static e => (e.Sender as T)?.InvalidateArrange()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidate); } } diff --git a/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs b/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs index 91fa459acb..fa298b53a3 100644 --- a/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs +++ b/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout { internal struct UvMeasure { - internal static readonly UvMeasure Zero = default(UvMeasure); + internal static readonly UvMeasure Zero = default; internal double U { get; set; } diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 4138f1c891..b9a560ad8f 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using Avalonia.Animation; using Avalonia.Animation.Animators; using Avalonia.Media.Immutable; +using Avalonia.Reactive; namespace Avalonia.Media { @@ -103,14 +104,12 @@ namespace Avalonia.Media protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Brush { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty)); foreach (var property in properties) { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index 9c30b6f872..4749bfa401 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using Avalonia.Animation; using Avalonia.Collections; using Avalonia.Media.Immutable; +using Avalonia.Reactive; #nullable enable @@ -51,13 +52,11 @@ namespace Avalonia.Media static DashStyle() { - void RaiseInvalidated(AvaloniaPropertyChangedEventArgs e) - { - ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty)); - DashesProperty.Changed.Subscribe(RaiseInvalidated); - OffsetProperty.Changed.Subscribe(RaiseInvalidated); + DashesProperty.Changed.Subscribe(invalidateObserver); + OffsetProperty.Changed.Subscribe(invalidateObserver); } /// diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index eabd7c8274..077816c645 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -279,7 +279,7 @@ namespace Avalonia.Media OpacityMask, } - public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default) { if (context._states is null) throw new ObjectDisposedException(nameof(DrawingContext)); diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 4a46d89153..7d3b4c056e 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -76,8 +76,8 @@ namespace Avalonia.Media { using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushOpacity(Opacity)) - using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState)) - using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState)) + using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default) + using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default) { foreach (var drawing in Children) { diff --git a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs index 0e485d0db8..22d5a29870 100644 --- a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; namespace Avalonia.Media { @@ -274,14 +275,12 @@ namespace Avalonia.Media protected static void AffectsRender(params AvaloniaProperty[] properties) where T : ExperimentalAcrylicMaterial { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty)); foreach (var property in properties) { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index d92d003c2a..e82d5b7ba5 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -47,10 +47,7 @@ namespace Avalonia.Media return current; } - var fontManagerImpl = AvaloniaLocator.Current.GetService(); - - if (fontManagerImpl == null) - throw new InvalidOperationException("No font manager implementation was registered."); + var fontManagerImpl = AvaloniaLocator.Current.GetRequiredService(); current = new FontManager(fontManagerImpl); diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index e62f819b74..ab1bb3b9fa 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -2,6 +2,8 @@ using System; using Avalonia.Platform; using System.ComponentModel; using System.Globalization; +using Avalonia.Reactive; + namespace Avalonia.Media { @@ -120,9 +122,10 @@ namespace Avalonia.Media /// protected static void AffectsGeometry(params AvaloniaProperty[] properties) { + var invalidateObserver = new AnonymousObserver(AffectsGeometryInvalidate); foreach (var property in properties) { - property.Changed.Subscribe(AffectsGeometryInvalidate); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/GradientBrush.cs b/src/Avalonia.Base/Media/GradientBrush.cs index 83cdaa1694..e1654a01b2 100644 --- a/src/Avalonia.Base/Media/GradientBrush.cs +++ b/src/Avalonia.Base/Media/GradientBrush.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using Avalonia.Animation.Animators; using Avalonia.Collections; using Avalonia.Metadata; +using Avalonia.Reactive; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index 1e1a73437d..eb6f105680 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -218,7 +218,7 @@ namespace Avalonia.Media OpacityMask, } - internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default) { if (context._states is null) throw new ObjectDisposedException(nameof(ImmediateDrawingContext)); diff --git a/src/Avalonia.Base/Media/MatrixTransform.cs b/src/Avalonia.Base/Media/MatrixTransform.cs index c61acb730c..22f39b1ee2 100644 --- a/src/Avalonia.Base/Media/MatrixTransform.cs +++ b/src/Avalonia.Base/Media/MatrixTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/RotateTransform.cs b/src/Avalonia.Base/Media/RotateTransform.cs index 3bd409149c..ab3ea6770f 100644 --- a/src/Avalonia.Base/Media/RotateTransform.cs +++ b/src/Avalonia.Base/Media/RotateTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/ScaleTransform.cs b/src/Avalonia.Base/Media/ScaleTransform.cs index d4c1a7f993..fca2f4bf2a 100644 --- a/src/Avalonia.Base/Media/ScaleTransform.cs +++ b/src/Avalonia.Base/Media/ScaleTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media @@ -25,8 +26,6 @@ namespace Avalonia.Media /// public ScaleTransform() { - this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged()); } /// @@ -63,5 +62,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ScaleXProperty || change.Property == ScaleYProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Base/Media/SkewTransform.cs b/src/Avalonia.Base/Media/SkewTransform.cs index 066f5371c3..d268bee778 100644 --- a/src/Avalonia.Base/Media/SkewTransform.cs +++ b/src/Avalonia.Base/Media/SkewTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media @@ -25,8 +26,6 @@ namespace Avalonia.Media /// public SkewTransform() { - this.GetObservable(AngleXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(AngleYProperty).Subscribe(_ => RaiseChanged()); } /// @@ -62,5 +61,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateSkew(Matrix.ToRadians(AngleX), Matrix.ToRadians(AngleY)); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == AngleXProperty || change.Property == AngleYProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs index d76f212f26..499026e8b3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs @@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting throw new ArgumentOutOfRangeException(nameof(index)); } #endif - return Span[index]; + return CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; } } @@ -157,33 +157,23 @@ namespace Avalonia.Media.TextFormatting /// /// Gets a span from the character buffer range /// - public ReadOnlySpan Span => - CharacterBufferReference.CharacterBuffer.Span.Slice(CharacterBufferReference.OffsetToFirstChar, Length); + public ReadOnlySpan Span => CharacterBuffer.Span.Slice(OffsetToFirstChar, Length); /// /// Gets the character memory buffer /// - internal ReadOnlyMemory CharacterBuffer - { - get { return CharacterBufferReference.CharacterBuffer; } - } + internal ReadOnlyMemory CharacterBuffer => CharacterBufferReference.CharacterBuffer; /// /// Gets the character offset relative to the beginning of buffer to /// the first character of the run /// - internal int OffsetToFirstChar - { - get { return CharacterBufferReference.OffsetToFirstChar; } - } + internal int OffsetToFirstChar => CharacterBufferReference.OffsetToFirstChar; /// /// Indicate whether the character buffer range is empty /// - internal bool IsEmpty - { - get { return CharacterBufferReference.CharacterBuffer.Length == 0 || Length <= 0; } - } + internal bool IsEmpty => CharacterBuffer.Length == 0 || Length <= 0; internal CharacterBufferRange Take(int length) { @@ -217,9 +207,7 @@ namespace Avalonia.Media.TextFormatting return new CharacterBufferRange(new CharacterBufferReference(), 0); } - var characterBufferReference = new CharacterBufferReference( - CharacterBufferReference.CharacterBuffer, - CharacterBufferReference.OffsetToFirstChar + length); + var characterBufferReference = new CharacterBufferReference(CharacterBuffer, OffsetToFirstChar + length); return new CharacterBufferRange(characterBufferReference, Length - length); } @@ -280,14 +268,8 @@ namespace Avalonia.Media.TextFormatting int IReadOnlyCollection.Count => Length; - public IEnumerator GetEnumerator() - { - return new ImmutableReadOnlyListStructEnumerator(this); - } + public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 4472ba87eb..49d94b511d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -132,7 +132,7 @@ namespace Avalonia.Media.TextFormatting { var grapheme = graphemeEnumerator.Current; - finalLength += grapheme.Text.Length; + finalLength += grapheme.Length; if (finalLength >= length) { diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs index 3c3a46c209..21e8ce089a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -91,7 +91,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (textRun is ShapedTextCharacters shapedText) + if (textRun is ShapedTextRun shapedText) { var glyphRun = shapedText.GlyphRun; var shapedBuffer = shapedText.ShapedBuffer; diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs similarity index 92% rename from src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs rename to src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 3035eb7b18..3149bc2cda 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -6,11 +6,11 @@ namespace Avalonia.Media.TextFormatting /// /// A text run that holds shaped characters. /// - public sealed class ShapedTextCharacters : DrawableTextRun + public sealed class ShapedTextRun : DrawableTextRun { private GlyphRun? _glyphRun; - public ShapedTextCharacters(ShapedBuffer shapedBuffer, TextRunProperties properties) + public ShapedTextRun(ShapedBuffer shapedBuffer, TextRunProperties properties) { ShapedBuffer = shapedBuffer; CharacterBufferReference = shapedBuffer.CharacterBufferRange.CharacterBufferReference; @@ -155,7 +155,7 @@ namespace Avalonia.Media.TextFormatting return length > 0; } - internal SplitResult Split(int length) + internal SplitResult Split(int length) { if (IsReversed) { @@ -171,7 +171,7 @@ namespace Avalonia.Media.TextFormatting var splitBuffer = ShapedBuffer.Split(length); - var first = new ShapedTextCharacters(splitBuffer.First, Properties); + var first = new ShapedTextRun(splitBuffer.First, Properties); #if DEBUG @@ -182,9 +182,9 @@ namespace Avalonia.Media.TextFormatting #endif - var second = new ShapedTextCharacters(splitBuffer.Second!, Properties); + var second = new ShapedTextRun(splitBuffer.Second!, Properties); - return new SplitResult(first, second); + return new SplitResult(first, second); } internal GlyphRun CreateGlyphRun() diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 0be753bd04..1a48151834 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -91,12 +91,12 @@ namespace Avalonia.Media.TextFormatting public override TextRunProperties Properties { get; } /// - /// Gets a list of . + /// Gets a list of . /// /// The shapeable text characters. - internal IReadOnlyList GetShapeableCharacters(CharacterBufferRange characterBufferRange, sbyte biDiLevel, ref TextRunProperties? previousProperties) + internal IReadOnlyList GetShapeableCharacters(CharacterBufferRange characterBufferRange, sbyte biDiLevel, ref TextRunProperties? previousProperties) { - var shapeableCharacters = new List(2); + var shapeableCharacters = new List(2); while (characterBufferRange.Length > 0) { @@ -120,7 +120,7 @@ namespace Avalonia.Media.TextFormatting /// The bidi level of the run. /// /// A list of shapeable text runs. - private static ShapeableTextCharacters CreateShapeableRun(CharacterBufferRange characterBufferRange, + private static UnshapedTextRun CreateShapeableRun(CharacterBufferRange characterBufferRange, TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var defaultTypeface = defaultProperties.Typeface; @@ -133,12 +133,12 @@ namespace Avalonia.Media.TextFormatting { if (TryGetShapeableLength(characterBufferRange, previousTypeface.Value, null, out var fallbackCount, out _)) { - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, fallbackCount, + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, fallbackCount, defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); } } - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -146,7 +146,7 @@ namespace Avalonia.Media.TextFormatting { if (TryGetShapeableLength(characterBufferRange, previousTypeface.Value, defaultTypeface, out count, out _)) { - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); } } @@ -176,7 +176,7 @@ namespace Avalonia.Media.TextFormatting if (matchFound && TryGetShapeableLength(characterBufferRange, currentTypeface, defaultTypeface, out count, out _)) { //Fallback found - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -196,10 +196,10 @@ namespace Avalonia.Media.TextFormatting break; } - count += grapheme.Text.Length; + count += grapheme.Length; } - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties, biDiLevel); + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties, biDiLevel); } /// @@ -264,7 +264,7 @@ namespace Avalonia.Media.TextFormatting } } - length += currentGrapheme.Text.Length; + length += currentGrapheme.Length; } return length > 0; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs index f677617b14..01804e1ce3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs @@ -21,6 +21,6 @@ namespace Avalonia.Media.TextFormatting /// Collapses given text line. /// /// Text line to collapse. - public abstract List? Collapse(TextLine textLine); + public abstract List? Collapse(TextLine textLine); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index a1b8985b43..9c201bda22 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -5,9 +5,11 @@ namespace Avalonia.Media.TextFormatting { internal static class TextEllipsisHelper { - public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) + public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) { - if (textLine.TextRuns is not List textRuns || textRuns.Count == 0) + var textRuns = textLine.TextRuns; + + if (textRuns == null || textRuns.Count == 0) { return null; } @@ -20,7 +22,7 @@ namespace Avalonia.Media.TextFormatting if (properties.Width < shapedSymbol.GlyphRun.Size.Width) { //Not enough space to fit in the symbol - return new List(0); + return new List(0); } var availableWidth = properties.Width - shapedSymbol.Size.Width; @@ -31,7 +33,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { currentWidth += shapedRun.Size.Width; @@ -70,11 +72,11 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - var collapsedRuns = new List(textRuns.Count); + var collapsedRuns = new List(textRuns.Count); if (collapsedLength > 0) { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength); + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); collapsedRuns.AddRange(splitResult.First); } @@ -84,22 +86,21 @@ namespace Avalonia.Media.TextFormatting return collapsedRuns; } - availableWidth -= currentRun.Size.Width; - + availableWidth -= shapedRun.Size.Width; break; } - case { } drawableRun: + case DrawableTextRun drawableRun: { //The whole run needs to fit into available space if (currentWidth + drawableRun.Size.Width > availableWidth) { - var collapsedRuns = new List(textRuns.Count); + var collapsedRuns = new List(textRuns.Count); if (collapsedLength > 0) { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength); + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); collapsedRuns.AddRange(splitResult.First); } @@ -109,6 +110,8 @@ namespace Avalonia.Media.TextFormatting return collapsedRuns; } + availableWidth -= drawableRun.Size.Width; + break; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 93eb4811b9..989bf7749d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -17,20 +16,20 @@ namespace Avalonia.Media.TextFormatting var textWrapping = paragraphProperties.TextWrapping; FlowDirection resolvedFlowDirection; TextLineBreak? nextLineBreak = null; - List drawableTextRuns; + IReadOnlyList textRuns; - var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, + var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, out var textEndOfLine, out var textSourceLength); if (previousLineBreak?.RemainingRuns != null) { resolvedFlowDirection = previousLineBreak.FlowDirection; - drawableTextRuns = previousLineBreak.RemainingRuns.ToList(); + textRuns = previousLineBreak.RemainingRuns; nextLineBreak = previousLineBreak; } else { - drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection); + textRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, out resolvedFlowDirection); if (nextLineBreak == null && textEndOfLine != null) { @@ -44,7 +43,7 @@ namespace Avalonia.Media.TextFormatting { case TextWrapping.NoWrap: { - textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength, + textLine = new TextLineImpl(textRuns, firstTextSourceIndex, textSourceLength, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); @@ -54,7 +53,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.WrapWithOverflow: case TextWrapping.Wrap: { - textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, + textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); break; } @@ -71,7 +70,7 @@ namespace Avalonia.Media.TextFormatting /// The text run's. /// The length to split at. /// The split text runs. - internal static SplitResult> SplitDrawableRuns(List textRuns, int length) + internal static SplitResult> SplitTextRuns(IReadOnlyList textRuns, int length) { var currentLength = 0; @@ -88,7 +87,7 @@ namespace Avalonia.Media.TextFormatting var firstCount = currentRun.Length >= 1 ? i + 1 : i; - var first = new List(firstCount); + var first = new List(firstCount); if (firstCount > 1) { @@ -102,7 +101,7 @@ namespace Avalonia.Media.TextFormatting if (currentLength + currentRun.Length == length) { - var second = secondCount > 0 ? new List(secondCount) : null; + var second = secondCount > 0 ? new List(secondCount) : null; if (second != null) { @@ -116,33 +115,33 @@ namespace Avalonia.Media.TextFormatting first.Add(currentRun); - return new SplitResult>(first, second); + return new SplitResult>(first, second); } else { secondCount++; - var second = new List(secondCount); + var second = new List(secondCount); - if (currentRun is ShapedTextCharacters shapedTextCharacters) + if (currentRun is ShapedTextRun shapedTextCharacters) { var split = shapedTextCharacters.Split(length - currentLength); first.Add(split.First); second.Add(split.Second!); - } + } for (var j = 1; j < secondCount; j++) { second.Add(textRuns[i + j]); } - return new SplitResult>(first, second); + return new SplitResult>(first, second); } } - return new SplitResult>(textRuns, null); + return new SplitResult>(textRuns, null); } /// @@ -154,11 +153,11 @@ namespace Avalonia.Media.TextFormatting /// /// A list of shaped text characters. /// - private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties, + private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties, out FlowDirection resolvedFlowDirection) { var flowDirection = paragraphProperties.FlowDirection; - var drawableTextRuns = new List(); + var shapedRuns = new List(); var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) @@ -199,23 +198,16 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case DrawableTextRun drawableRun: + case UnshapedTextRun shapeableRun: { - drawableTextRuns.Add(drawableRun); - - break; - } - - case ShapeableTextCharacters shapeableRun: - { - var groupedRuns = new List(2) { shapeableRun }; + var groupedRuns = new List(2) { shapeableRun }; var characterBufferReference = currentRun.CharacterBufferReference; var length = currentRun.Length; var offsetToFirstCharacter = characterBufferReference.OffsetToFirstChar; while (index + 1 < processedRuns.Count) { - if (processedRuns[index + 1] is not ShapeableTextCharacters nextRun) + if (processedRuns[index + 1] is not UnshapedTextRun nextRun) { break; } @@ -245,23 +237,29 @@ namespace Avalonia.Media.TextFormatting var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, - shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, + shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); - drawableTextRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions)); + shapedRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions)); + + break; + } + default: + { + shapedRuns.Add(currentRun); break; } } } - return drawableTextRuns; + return shapedRuns; } - private static IReadOnlyList ShapeTogether( - IReadOnlyList textRuns, CharacterBufferReference text, int length, TextShaperOptions options) + private static IReadOnlyList ShapeTogether( + IReadOnlyList textRuns, CharacterBufferReference text, int length, TextShaperOptions options) { - var shapedRuns = new List(textRuns.Count); + var shapedRuns = new List(textRuns.Count); var shapedBuffer = TextShaper.Current.ShapeText(text, length, options); @@ -271,7 +269,7 @@ namespace Avalonia.Media.TextFormatting var splitResult = shapedBuffer.Split(currentRun.Length); - shapedRuns.Add(new ShapedTextCharacters(splitResult.First, currentRun.Properties)); + shapedRuns.Add(new ShapedTextRun(splitResult.First, currentRun.Properties)); shapedBuffer = splitResult.Second!; } @@ -280,9 +278,9 @@ namespace Avalonia.Media.TextFormatting } /// - /// Coalesces ranges of the same bidi level to form + /// Coalesces ranges of the same bidi level to form /// - /// The text characters to form from. + /// The text characters to form from. /// The bidi levels. /// private static IEnumerable> CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels) @@ -390,6 +388,10 @@ namespace Avalonia.Media.TextFormatting if (textRun == null) { + textRuns.Add(new TextEndOfParagraph()); + + textSourceLength += TextRun.DefaultTextSourceLength; + break; } @@ -465,7 +467,7 @@ namespace Avalonia.Media.TextFormatting return false; } - private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength) + private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength) { measuredLength = 0; var currentWidth = 0.0; @@ -474,9 +476,9 @@ namespace Avalonia.Media.TextFormatting { switch (currentRun) { - case ShapedTextCharacters shapedTextCharacters: + case ShapedTextRun shapedTextCharacters: { - if(shapedTextCharacters.ShapedBuffer.Length > 0) + if (shapedTextCharacters.ShapedBuffer.Length > 0) { var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphInfos[0].GlyphCluster; var lastCluster = firstCluster; @@ -497,12 +499,12 @@ namespace Avalonia.Media.TextFormatting } measuredLength += currentRun.Length; - } + } break; } - case { } drawableTextRun: + case DrawableTextRun drawableTextRun: { if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth) { @@ -510,14 +512,20 @@ namespace Avalonia.Media.TextFormatting } measuredLength += currentRun.Length; - currentWidth += currentRun.Size.Width; + currentWidth += drawableTextRun.Size.Width; + + break; + } + default: + { + measuredLength += currentRun.Length; break; } } } - found: + found: return measuredLength != 0; } @@ -538,7 +546,7 @@ namespace Avalonia.Media.TextFormatting var shapedBuffer = new ShapedBuffer(characterBufferRange, glyphInfos, glyphTypeface, properties.FontRenderingEmSize, (sbyte)flowDirection); - var textRuns = new List { new ShapedTextCharacters(shapedBuffer, properties) }; + var textRuns = new List { new ShapedTextRun(shapedBuffer, properties) }; return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine(); } @@ -553,13 +561,13 @@ namespace Avalonia.Media.TextFormatting /// /// The current line break if the line was explicitly broken. /// The wrapped text line. - private static TextLineImpl PerformTextWrapping(List textRuns, int firstTextSourceIndex, + private static TextLineImpl PerformTextWrapping(IReadOnlyList textRuns, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak) { - if(textRuns.Count == 0) + if (textRuns.Count == 0) { - return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties); + return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties); } if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength)) @@ -575,46 +583,24 @@ namespace Avalonia.Media.TextFormatting for (var index = 0; index < textRuns.Count; index++) { - var currentRun = textRuns[index]; - - var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); - - var lineBreaker = new LineBreakEnumerator(runText); - var breakFound = false; - while (lineBreaker.MoveNext()) - { - if (lineBreaker.Current.Required && - currentLength + lineBreaker.Current.PositionMeasure <= measuredLength) - { - //Explicit break found - breakFound = true; - - currentPosition = currentLength + lineBreaker.Current.PositionWrap; - - break; - } + var currentRun = textRuns[index]; - if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength) - { - if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) + switch (currentRun) + { + case ShapedTextRun: { - if (lastWrapPosition > 0) - { - currentPosition = lastWrapPosition; + var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); - breakFound = true; + var lineBreaker = new LineBreakEnumerator(runText); - break; - } - - //Find next possible wrap position (overflow) - if (index < textRuns.Count - 1) + while (lineBreaker.MoveNext()) { - if (lineBreaker.Current.PositionWrap != currentRun.Length) + if (lineBreaker.Current.Required && + currentLength + lineBreaker.Current.PositionMeasure <= measuredLength) { - //We already found the next possible wrap position. + //Explicit break found breakFound = true; currentPosition = currentLength + lineBreaker.Current.PositionWrap; @@ -622,51 +608,81 @@ namespace Avalonia.Media.TextFormatting break; } - while (lineBreaker.MoveNext() && index < textRuns.Count) + if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength) { - currentPosition += lineBreaker.Current.PositionWrap; - - if (lineBreaker.Current.PositionWrap != currentRun.Length) + if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) { - break; - } + if (lastWrapPosition > 0) + { + currentPosition = lastWrapPosition; - index++; + breakFound = true; + + break; + } + + //Find next possible wrap position (overflow) + if (index < textRuns.Count - 1) + { + if (lineBreaker.Current.PositionWrap != currentRun.Length) + { + //We already found the next possible wrap position. + breakFound = true; + + currentPosition = currentLength + lineBreaker.Current.PositionWrap; + + break; + } + + while (lineBreaker.MoveNext() && index < textRuns.Count) + { + currentPosition += lineBreaker.Current.PositionWrap; + + if (lineBreaker.Current.PositionWrap != currentRun.Length) + { + break; + } + + index++; + + if (index >= textRuns.Count) + { + break; + } + + currentRun = textRuns[index]; + + runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); + + lineBreaker = new LineBreakEnumerator(runText); + } + } + else + { + currentPosition = currentLength + lineBreaker.Current.PositionWrap; + } + + breakFound = true; - if (index >= textRuns.Count) - { break; } - currentRun = textRuns[index]; + //We overflowed so we use the last available wrap position. + currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition; - runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); + breakFound = true; - lineBreaker = new LineBreakEnumerator(runText); + break; } - } - else - { - currentPosition = currentLength + lineBreaker.Current.PositionWrap; - } - breakFound = true; + if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap) + { + lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap; + } + } break; } - - //We overflowed so we use the last available wrap position. - currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition; - - breakFound = true; - - break; - } - - if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap) - { - lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap; - } } if (!breakFound) @@ -681,12 +697,12 @@ namespace Avalonia.Media.TextFormatting break; } - var splitResult = SplitDrawableRuns(textRuns, measuredLength); + var splitResult = SplitTextRuns(textRuns, measuredLength); var remainingCharacters = splitResult.Second; var lineBreak = remainingCharacters?.Count > 0 ? - new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) : + new TextLineBreak(null, resolvedFlowDirection, remainingCharacters) : null; if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) @@ -744,7 +760,7 @@ namespace Avalonia.Media.TextFormatting /// /// The shaped symbol. /// - internal static ShapedTextCharacters CreateSymbol(TextRun textRun, FlowDirection flowDirection) + internal static ShapedTextRun CreateSymbol(TextRun textRun, FlowDirection flowDirection) { var textShaper = TextShaper.Current; @@ -760,7 +776,7 @@ namespace Avalonia.Media.TextFormatting var shapedBuffer = textShaper.ShapeText(characterBuffer, textRun.Length, shaperOptions); - return new ShapedTextCharacters(shapedBuffer, textRun.Properties); + return new ShapedTextRun(shapedBuffer, textRun.Properties); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f803001481..ef0c726793 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -448,7 +448,7 @@ namespace Avalonia.Media.TextFormatting var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + if(textLine == null || textLine.Length == 0) { if (previousLine != null && previousLine.NewLineLength > 0) { @@ -501,6 +501,11 @@ namespace Avalonia.Media.TextFormatting break; } + + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + { + break; + } } //Make sure the TextLayout always contains at least on empty line diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 2752af8f0c..e30a0fe9f4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -39,9 +39,11 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { - if (textLine.TextRuns is not List textRuns || textRuns.Count == 0) + var textRuns = textLine.TextRuns; + + if (textRuns == null || textRuns.Count == 0) { return null; } @@ -52,7 +54,7 @@ namespace Avalonia.Media.TextFormatting if (Width < shapedSymbol.GlyphRun.Size.Width) { - return new List(0); + return new List(0); } // Overview of ellipsis structure @@ -65,93 +67,102 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: - { - currentWidth += currentRun.Size.Width; - - if (currentWidth > availableWidth) + case ShapedTextRun shapedRun: { - shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength); - - var collapsedRuns = new List(textRuns.Count); + currentWidth += shapedRun.Size.Width; - if (measuredLength > 0) + if (currentWidth > availableWidth) { - List? preSplitRuns = null; - List? postSplitRuns; - - if (_prefixLength > 0) - { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, - Math.Min(_prefixLength, measuredLength)); + shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength); - collapsedRuns.AddRange(splitResult.First); + var collapsedRuns = new List(textRuns.Count); - preSplitRuns = splitResult.First; - postSplitRuns = splitResult.Second; - } - else + if (measuredLength > 0) { - postSplitRuns = textRuns; - } + IReadOnlyList? preSplitRuns = null; + IReadOnlyList? postSplitRuns; - collapsedRuns.Add(shapedSymbol); + if (_prefixLength > 0) + { + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, + Math.Min(_prefixLength, measuredLength)); - if (measuredLength <= _prefixLength || postSplitRuns is null) - { - return collapsedRuns; - } + collapsedRuns.AddRange(splitResult.First); - var availableSuffixWidth = availableWidth; + preSplitRuns = splitResult.First; + postSplitRuns = splitResult.Second; + } + else + { + postSplitRuns = textRuns; + } - if (preSplitRuns is not null) - { - foreach (var run in preSplitRuns) + collapsedRuns.Add(shapedSymbol); + + if (measuredLength <= _prefixLength || postSplitRuns is null) { - availableSuffixWidth -= run.Size.Width; + return collapsedRuns; } - } - for (var i = postSplitRuns.Count - 1; i >= 0; i--) - { - var run = postSplitRuns[i]; + var availableSuffixWidth = availableWidth; - switch (run) + if (preSplitRuns is not null) { - case ShapedTextCharacters endShapedRun: + foreach (var run in preSplitRuns) { - if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, - out var suffixCount, out var suffixWidth)) + if (run is DrawableTextRun drawableTextRun) { - availableSuffixWidth -= suffixWidth; + availableSuffixWidth -= drawableTextRun.Size.Width; + } + } + } - if (suffixCount > 0) + for (var i = postSplitRuns.Count - 1; i >= 0; i--) + { + var run = postSplitRuns[i]; + + switch (run) + { + case ShapedTextRun endShapedRun: { - var splitSuffix = - endShapedRun.Split(run.Length - suffixCount); + if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, + out var suffixCount, out var suffixWidth)) + { + availableSuffixWidth -= suffixWidth; - collapsedRuns.Add(splitSuffix.Second!); - } - } + if (suffixCount > 0) + { + var splitSuffix = + endShapedRun.Split(run.Length - suffixCount); + + collapsedRuns.Add(splitSuffix.Second!); + } + } - break; + break; + } } } } - } - else - { - collapsedRuns.Add(shapedSymbol); + else + { + collapsedRuns.Add(shapedSymbol); + } + + return collapsedRuns; } - return collapsedRuns; - } + availableWidth -= shapedRun.Size.Width; - break; - } - } + break; + } + case DrawableTextRun drawableTextRun: + { + availableWidth -= drawableTextRun.Size.Width; - availableWidth -= currentRun.Size.Width; + break; + } + } runIndex++; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs index ce35e47fbd..bf26ac5df4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting public class TextLineBreak { public TextLineBreak(TextEndOfLine? textEndOfLine = null, FlowDirection flowDirection = FlowDirection.LeftToRight, - IReadOnlyList? remainingRuns = null) + IReadOnlyList? remainingRuns = null) { TextEndOfLine = textEndOfLine; FlowDirection = flowDirection; @@ -25,6 +25,6 @@ namespace Avalonia.Media.TextFormatting /// /// Get the remaining runs that were split up by the during the formatting process. /// - public IReadOnlyList? RemainingRuns { get; } + public IReadOnlyList? RemainingRuns { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index d893468052..a1f93bcd07 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -6,13 +6,13 @@ namespace Avalonia.Media.TextFormatting { internal class TextLineImpl : TextLine { - private readonly List _textRuns; + private IReadOnlyList _textRuns; private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; private readonly FlowDirection _resolvedFlowDirection; - public TextLineImpl(List textRuns, int firstTextSourceIndex, int length, double paragraphWidth, + public TextLineImpl(IReadOnlyList textRuns, int firstTextSourceIndex, int length, double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { @@ -86,11 +86,14 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - var offsetY = GetBaselineOffset(this, textRun); + if (textRun is DrawableTextRun drawable) + { + var offsetY = GetBaselineOffset(this, drawable); - textRun.Draw(drawingContext, new Point(currentX, currentY + offsetY)); + drawable.Draw(drawingContext, new Point(currentX, currentY + offsetY)); - currentX += textRun.Size.Width; + currentX += drawable.Size.Width; + } } } @@ -180,7 +183,14 @@ namespace Avalonia.Media.TextFormatting { var lastRun = _textRuns[_textRuns.Count - 1]; - return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, lastRun.Size.Width); + var size = 0.0; + + if (lastRun is DrawableTextRun drawableTextRun) + { + size = drawableTextRun.Size.Width; + } + + return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, size); } // process hit that happens within the line @@ -192,14 +202,14 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[i]; - if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var rightToLeftIndex = i; currentPosition += currentRun.Length; while (rightToLeftIndex + 1 <= _textRuns.Count - 1) { - var nextShaped = _textRuns[++rightToLeftIndex] as ShapedTextCharacters; + var nextShaped = _textRuns[++rightToLeftIndex] as ShapedTextRun; if (nextShaped == null || nextShaped.ShapedBuffer.IsLeftToRight) { @@ -220,9 +230,16 @@ namespace Avalonia.Media.TextFormatting currentRun = _textRuns[j]; - if (currentDistance + currentRun.Size.Width <= distance) + if(currentRun is not ShapedTextRun) + { + continue; + } + + shapedRun = (ShapedTextRun)currentRun; + + if (currentDistance + shapedRun.Size.Width <= distance) { - currentDistance += currentRun.Size.Width; + currentDistance += shapedRun.Size.Width; currentPosition -= currentRun.Length; continue; @@ -234,12 +251,19 @@ namespace Avalonia.Media.TextFormatting characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); - if (i < _textRuns.Count - 1 && currentDistance + currentRun.Size.Width < distance) + if (currentRun is DrawableTextRun drawableTextRun) { - currentDistance += currentRun.Size.Width; + if (i < _textRuns.Count - 1 && currentDistance + drawableTextRun.Size.Width < distance) + { + currentDistance += drawableTextRun.Size.Width; - currentPosition += currentRun.Length; + currentPosition += currentRun.Length; + continue; + } + } + else + { continue; } @@ -249,13 +273,13 @@ namespace Avalonia.Media.TextFormatting return characterHit; } - private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance) + private static CharacterHit GetRunCharacterHit(TextRun run, int currentPosition, double distance) { CharacterHit characterHit; switch (run) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); @@ -270,9 +294,9 @@ namespace Avalonia.Media.TextFormatting break; } - default: + case DrawableTextRun drawableTextRun: { - if (distance < run.Size.Width / 2) + if (distance < drawableTextRun.Size.Width / 2) { characterHit = new CharacterHit(currentPosition); } @@ -282,6 +306,10 @@ namespace Avalonia.Media.TextFormatting } break; } + default: + characterHit = new CharacterHit(currentPosition, run.Length); + + break; } return characterHit; @@ -303,21 +331,21 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[index]; - if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var i = index; - var rightToLeftWidth = currentRun.Size.Width; + var rightToLeftWidth = shapedRun.Size.Width; while (i + 1 <= _textRuns.Count - 1) { var nextRun = _textRuns[i + 1]; - if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) + if (nextRun is ShapedTextRun nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) { i++; - rightToLeftWidth += nextRun.Size.Width; + rightToLeftWidth += nextShapedRun.Size.Width; continue; } @@ -331,7 +359,10 @@ namespace Avalonia.Media.TextFormatting { currentRun = _textRuns[i]; - rightToLeftWidth -= currentRun.Size.Width; + if (currentRun is DrawableTextRun drawable) + { + rightToLeftWidth -= drawable.Size.Width; + } if (currentPosition + currentRun.Length >= characterIndex) { @@ -355,8 +386,13 @@ namespace Avalonia.Media.TextFormatting return Math.Max(0, currentDistance + distance); } + if (currentRun is DrawableTextRun drawableTextRun) + { + currentDistance += drawableTextRun.Size.Width; + } + //No hit hit found so we add the full width - currentDistance += currentRun.Size.Width; + currentPosition += currentRun.Length; remainingLength -= currentRun.Length; } @@ -380,8 +416,12 @@ namespace Avalonia.Media.TextFormatting return Math.Max(0, currentDistance - distance); } + if (currentRun is DrawableTextRun drawableTextRun) + { + currentDistance -= drawableTextRun.Size.Width; + } + //No hit hit found so we add the full width - currentDistance -= currentRun.Size.Width; currentPosition += currentRun.Length; remainingLength -= currentRun.Length; } @@ -391,7 +431,7 @@ namespace Avalonia.Media.TextFormatting } private static bool TryGetDistanceFromCharacterHit( - DrawableTextRun currentRun, + TextRun currentRun, CharacterHit characterHit, int currentPosition, int remainingLength, @@ -407,7 +447,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedTextCharacters: + case ShapedTextRun shapedTextCharacters: { currentGlyphRun = shapedTextCharacters.GlyphRun; @@ -432,7 +472,7 @@ namespace Avalonia.Media.TextFormatting break; } - default: + case DrawableTextRun drawableTextRun: { if (characterIndex == currentPosition) { @@ -441,7 +481,7 @@ namespace Avalonia.Media.TextFormatting if (characterIndex == currentPosition + currentRun.Length) { - distance = currentRun.Size.Width; + distance = drawableTextRun.Size.Width; return true; @@ -449,6 +489,10 @@ namespace Avalonia.Media.TextFormatting break; } + default: + { + return false; + } } return false; @@ -476,7 +520,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit); break; @@ -550,7 +594,7 @@ namespace Avalonia.Media.TextFormatting double combinedWidth; - if (currentRun is ShapedTextCharacters currentShapedRun) + if (currentRun is ShapedTextRun currentShapedRun) { var firstCluster = currentShapedRun.GlyphRun.Metrics.FirstCluster; @@ -592,7 +636,7 @@ namespace Avalonia.Media.TextFormatting var rightToLeftIndex = index; var rightToLeftWidth = currentShapedRun.Size.Width; - while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextCharacters nextShapedRun) + while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextRun nextShapedRun) { if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) { @@ -624,12 +668,12 @@ namespace Avalonia.Media.TextFormatting for (int i = rightToLeftIndex - 1; i >= index; i--) { - if (TextRuns[i] is not ShapedTextCharacters) + if (TextRuns[i] is not ShapedTextRun) { continue; } - currentShapedRun = (ShapedTextCharacters)TextRuns[i]; + currentShapedRun = (ShapedTextRun)TextRuns[i]; currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength); @@ -769,7 +813,7 @@ namespace Avalonia.Media.TextFormatting var characterLength = 0; var endX = startX; - if (currentRun is ShapedTextCharacters currentShapedRun) + if (currentRun is ShapedTextRun currentShapedRun) { var offset = Math.Max(0, firstTextSourceIndex - currentPosition); @@ -883,7 +927,7 @@ namespace Avalonia.Media.TextFormatting return result; } - private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextCharacters currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength) + private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextRun currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength) { var startX = endX; @@ -943,9 +987,9 @@ namespace Avalonia.Media.TextFormatting return this; } - private static sbyte GetRunBidiLevel(DrawableTextRun run, FlowDirection flowDirection) + private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection) { - if (run is ShapedTextCharacters shapedTextCharacters) + if (run is ShapedTextRun shapedTextCharacters) { return shapedTextCharacters.BidiLevel; } @@ -1027,7 +1071,7 @@ namespace Avalonia.Media.TextFormatting { if (current.Level >= minLevelToReverse && current.Level % 2 != 0) { - if (current.Run is ShapedTextCharacters { IsReversed: false } shapedTextCharacters) + if (current.Run is ShapedTextRun { IsReversed: false } shapedTextCharacters) { shapedTextCharacters.Reverse(); } @@ -1039,16 +1083,18 @@ namespace Avalonia.Media.TextFormatting minLevelToReverse--; } - _textRuns.Clear(); + var textRuns = new List(_textRuns.Count); current = orderedRun; while (current != null) { - _textRuns.Add(current.Run); + textRuns.Add(current.Run); current = current.Next; } + + _textRuns = textRuns; } /// @@ -1145,7 +1191,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); @@ -1230,7 +1276,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); @@ -1286,7 +1332,7 @@ namespace Avalonia.Media.TextFormatting { var runIndex = 0; textPosition = FirstTextSourceIndex; - DrawableTextRun? previousRun = null; + TextRun? previousRun = null; while (runIndex < _textRuns.Count) { @@ -1294,7 +1340,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster; @@ -1303,7 +1349,7 @@ namespace Avalonia.Media.TextFormatting break; } - if (previousRun is ShapedTextCharacters previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight) + if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight) { if (shapedRun.ShapedBuffer.IsLeftToRight) { @@ -1346,7 +1392,6 @@ namespace Avalonia.Media.TextFormatting break; } - default: { if (codepointIndex == textPosition) @@ -1363,6 +1408,7 @@ namespace Avalonia.Media.TextFormatting break; } + } runIndex++; @@ -1394,7 +1440,7 @@ namespace Avalonia.Media.TextFormatting { switch (_textRuns[index]) { - case ShapedTextCharacters textRun: + case ShapedTextRun textRun: { var textMetrics = new TextMetrics(textRun.Properties.Typeface.GlyphTypeface, textRun.Properties.FontRenderingEmSize); @@ -1436,7 +1482,7 @@ namespace Avalonia.Media.TextFormatting break; } - case { } drawableTextRun: + case DrawableTextRun drawableTextRun: { widthIncludingWhitespace += drawableTextRun.Size.Width; @@ -1558,7 +1604,7 @@ namespace Avalonia.Media.TextFormatting private sealed class OrderedBidiRun { - public OrderedBidiRun(DrawableTextRun run, sbyte level) + public OrderedBidiRun(TextRun run, sbyte level) { Run = run; Level = level; @@ -1566,7 +1612,7 @@ namespace Avalonia.Media.TextFormatting public sbyte Level { get; } - public DrawableTextRun Run { get; } + public TextRun Run { get; } public OrderedBidiRun? Next { get; set; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs index 5691dd8ad0..27515738a4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs @@ -63,14 +63,11 @@ { get { return 0; } } - + /// /// Gets the default incremental tab width. /// - public virtual double DefaultIncrementalTab - { - get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; } - } + public virtual double DefaultIncrementalTab => 0; /// /// Gets the letter spacing. diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs index 0306054767..56232ec9c8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs @@ -40,11 +40,11 @@ namespace Avalonia.Media.TextFormatting { unsafe { - var characterBuffer = _textRun.CharacterBufferReference.CharacterBuffer; + var characterBuffer = new CharacterBufferRange(_textRun.CharacterBufferReference, _textRun.Length); fixed (char* charsPtr = characterBuffer.Span) { - return new string(charsPtr, 0, characterBuffer.Span.Length); + return new string(charsPtr, 0, _textRun.Length); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs index 4aacec7c48..c161b08d20 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs @@ -29,10 +29,7 @@ namespace Avalonia.Media.TextFormatting return current; } - var textShaperImpl = AvaloniaLocator.Current.GetService(); - - if (textShaperImpl == null) - throw new InvalidOperationException("No text shaper implementation was registered."); + var textShaperImpl = AvaloniaLocator.Current.GetRequiredService(); current = new TextShaper(textShaperImpl); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 1de04ad061..deecbbe476 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, false); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs index 7c94715aa4..c291e1dfb9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, true); } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index e6408bcfa6..8b9d1c1d02 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode @@ -222,6 +223,71 @@ namespace Avalonia.Media.TextFormatting.Unicode return new Codepoint(code); } + + /// + /// Reads the at specified position. + /// + /// The buffer to read from. + /// The index to read at. + /// The count of character that were read. + /// + public static Codepoint ReadAt(CharacterBufferRange text, int index, out int count) + { + count = 1; + + if (index >= text.Length) + { + return ReplacementCodepoint; + } + + var code = text[index]; + + ushort hi, low; + + //# High surrogate + if (0xD800 <= code && code <= 0xDBFF) + { + hi = code; + + if (index + 1 == text.Length) + { + return ReplacementCodepoint; + } + + low = text[index + 1]; + + if (0xDC00 <= low && low <= 0xDFFF) + { + count = 2; + return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); + } + + return ReplacementCodepoint; + } + + //# Low surrogate + if (0xDC00 <= code && code <= 0xDFFF) + { + if (index == 0) + { + return ReplacementCodepoint; + } + + hi = text[index - 1]; + + low = code; + + if (0xD800 <= hi && hi <= 0xDBFF) + { + count = 2; + return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); + } + + return ReplacementCodepoint; + } + + return new Codepoint(code); + } /// /// Returns if is between diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs index 69015fb17d..f75168083c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs @@ -7,10 +7,11 @@ namespace Avalonia.Media.TextFormatting.Unicode /// public readonly ref struct Grapheme { - public Grapheme(Codepoint firstCodepoint, ReadOnlySpan text) + public Grapheme(Codepoint firstCodepoint, int offset, int length) { FirstCodepoint = firstCodepoint; - Text = text; + Offset = offset; + Length = length; } /// @@ -19,8 +20,13 @@ namespace Avalonia.Media.TextFormatting.Unicode public Codepoint FirstCodepoint { get; } /// - /// The text that is representing the . + /// The Offset to the FirstCodepoint /// - public ReadOnlySpan Text { get; } + public int Offset { get; } + + /// + /// The length of the grapheme cluster + /// + public int Length { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs index 5ca120c856..dc21e06813 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs @@ -185,9 +185,7 @@ namespace Avalonia.Media.TextFormatting.Unicode Return: - var text = _text.Take(processor.CurrentCodeUnitOffset); - - Current = new Grapheme(firstCodepoint, text.Span); + Current = new Grapheme(firstCodepoint, _text.OffsetToFirstChar, processor.CurrentCodeUnitOffset); _text = _text.Skip(processor.CurrentCodeUnitOffset); diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs similarity index 61% rename from src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs rename to src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs index 0e8d6e3e4a..817086db88 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs @@ -5,9 +5,9 @@ namespace Avalonia.Media.TextFormatting /// /// A group of characters that can be shaped. /// - public sealed class ShapeableTextCharacters : TextRun + public sealed class UnshapedTextRun : TextRun { - public ShapeableTextCharacters(CharacterBufferReference characterBufferReference, int length, + public UnshapedTextRun(CharacterBufferReference characterBufferReference, int length, TextRunProperties properties, sbyte biDiLevel) { CharacterBufferReference = characterBufferReference; @@ -24,30 +24,30 @@ namespace Avalonia.Media.TextFormatting public sbyte BidiLevel { get; } - public bool CanShapeTogether(ShapeableTextCharacters shapeableTextCharacters) + public bool CanShapeTogether(UnshapedTextRun unshapedTextRun) { - if (!CharacterBufferReference.Equals(shapeableTextCharacters.CharacterBufferReference)) + if (!CharacterBufferReference.Equals(unshapedTextRun.CharacterBufferReference)) { return false; } - if (BidiLevel != shapeableTextCharacters.BidiLevel) + if (BidiLevel != unshapedTextRun.BidiLevel) { return false; } if (!MathUtilities.AreClose(Properties.FontRenderingEmSize, - shapeableTextCharacters.Properties.FontRenderingEmSize)) + unshapedTextRun.Properties.FontRenderingEmSize)) { return false; } - if (Properties.Typeface != shapeableTextCharacters.Properties.Typeface) + if (Properties.Typeface != unshapedTextRun.Properties.Typeface) { return false; } - if (Properties.BaselineAlignment != shapeableTextCharacters.Properties.BaselineAlignment) + if (Properties.BaselineAlignment != unshapedTextRun.Properties.BaselineAlignment) { return false; } diff --git a/src/Avalonia.Base/Media/TranslateTransform.cs b/src/Avalonia.Base/Media/TranslateTransform.cs index 0f910f3600..1d0169a62c 100644 --- a/src/Avalonia.Base/Media/TranslateTransform.cs +++ b/src/Avalonia.Base/Media/TranslateTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media @@ -25,8 +26,6 @@ namespace Avalonia.Media /// public TranslateTransform() { - this.GetObservable(XProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(YProperty).Subscribe(_ => RaiseChanged()); } /// @@ -63,5 +62,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateTranslation(X, Y); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == XProperty || change.Property == YProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index b5ac5c817d..11dc80ef8f 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Data; using Avalonia.Threading; diff --git a/src/Avalonia.Base/Reactive/AnonymousObserver.cs b/src/Avalonia.Base/Reactive/AnonymousObserver.cs new file mode 100644 index 0000000000..c2e02ae879 --- /dev/null +++ b/src/Avalonia.Base/Reactive/AnonymousObserver.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; + +namespace Avalonia.Reactive; + +internal class AnonymousObserver : IObserver +{ + private static readonly Action ThrowsOnError = ex => throw ex; + private static readonly Action NoOpCompleted = () => { }; + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + public AnonymousObserver(TaskCompletionSource tcs) + { + if (tcs is null) + { + throw new ArgumentNullException(nameof(tcs)); + } + + _onNext = tcs.SetResult; + _onError = tcs.SetException; + _onCompleted = NoOpCompleted; + } + + public AnonymousObserver(Action onNext, Action onError, Action onCompleted) + { + _onNext = onNext ?? throw new ArgumentNullException(nameof(onNext)); + _onError = onError ?? throw new ArgumentNullException(nameof(onError)); + _onCompleted = onCompleted ?? throw new ArgumentNullException(nameof(onCompleted)); + } + + public AnonymousObserver(Action onNext) + : this(onNext, ThrowsOnError, NoOpCompleted) + { + } + + public AnonymousObserver(Action onNext, Action onError) + : this(onNext, onError, NoOpCompleted) + { + } + + public AnonymousObserver(Action onNext, Action onCompleted) + : this(onNext, ThrowsOnError, onCompleted) + { + } + + public void OnCompleted() + { + _onCompleted.Invoke(); + } + + public void OnError(Exception error) + { + _onError.Invoke(error); + } + + public void OnNext(T value) + { + _onNext.Invoke(value); + } +} diff --git a/src/Avalonia.Base/Reactive/CombinedSubject.cs b/src/Avalonia.Base/Reactive/CombinedSubject.cs new file mode 100644 index 0000000000..dc6153133d --- /dev/null +++ b/src/Avalonia.Base/Reactive/CombinedSubject.cs @@ -0,0 +1,23 @@ +using System; + +namespace Avalonia.Reactive; + +internal class CombinedSubject : IAvaloniaSubject +{ + private readonly IObserver _observer; + private readonly IObservable _observable; + + public CombinedSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() => _observer.OnCompleted(); + + public void OnError(Exception error) => _observer.OnError(error); + + public void OnNext(T value) => _observer.OnNext(value); + + public IDisposable Subscribe(IObserver observer) => _observable.Subscribe(observer); +} diff --git a/src/Avalonia.Base/Reactive/CompositeDisposable.cs b/src/Avalonia.Base/Reactive/CompositeDisposable.cs new file mode 100644 index 0000000000..4952686ad8 --- /dev/null +++ b/src/Avalonia.Base/Reactive/CompositeDisposable.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +namespace Avalonia.Reactive; + +internal sealed class CompositeDisposable : ICollection, IDisposable +{ + private readonly object _gate = new object(); + private bool _disposed; + private List _disposables; + private int _count; + private const int ShrinkThreshold = 64; + + /// + /// Initializes a new instance of the class with the specified number of disposables. + /// + /// The number of disposables that the new CompositeDisposable can initially store. + /// is less than zero. + public CompositeDisposable(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + _disposables = new List(capacity); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(params IDisposable[] disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(IList disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + private static List ToList(IEnumerable disposables) + { + var capacity = disposables switch + { + IDisposable[] a => a.Length, + ICollection c => c.Count, + _ => 12 + }; + + var list = new List(capacity); + + // do the copy and null-check in one step to avoid a + // second loop for just checking for null items + foreach (var d in disposables) + { + if (d == null) + { + throw new ArgumentException("Disposables can't contain null", nameof(disposables)); + } + + list.Add(d); + } + + return list; + } + + /// + /// Gets the number of disposables contained in the . + /// + public int Count => Volatile.Read(ref _count); + + /// + /// Adds a disposable to the or disposes the disposable if the is disposed. + /// + /// Disposable to add. + /// is null. + public void Add(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (!_disposed) + { + _disposables.Add(item); + + // If read atomically outside the lock, it should be written atomically inside + // the plain read on _count is fine here because manipulation always happens + // from inside a lock. + Volatile.Write(ref _count, _count + 1); + return; + } + } + + item.Dispose(); + } + + /// + /// Removes and disposes the first occurrence of a disposable from the . + /// + /// Disposable to remove. + /// true if found; false otherwise. + /// is null. + public bool Remove(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + // this composite was already disposed and if the item was in there + // it has been already removed/disposed + if (_disposed) + { + return false; + } + + // + // List doesn't shrink the size of the underlying array but does collapse the array + // by copying the tail one position to the left of the removal index. We don't need + // index-based lookup but only ordering for sequential disposal. So, instead of spending + // cycles on the Array.Copy imposed by Remove, we use a null sentinel value. We also + // do manual Swiss cheese detection to shrink the list if there's a lot of holes in it. + // + + // read fields as infrequently as possible + var current = _disposables; + + var i = current.IndexOf(item); + if (i < 0) + { + // not found, just return + return false; + } + + current[i] = null; + + if (current.Capacity > ShrinkThreshold && _count < current.Capacity / 2) + { + var fresh = new List(current.Capacity / 2); + + foreach (var d in current) + { + if (d != null) + { + fresh.Add(d); + } + } + + _disposables = fresh; + } + + // make sure the Count property sees an atomic update + Volatile.Write(ref _count, _count - 1); + } + + // if we get here, the item was found and removed from the list + // just dispose it and report success + + item.Dispose(); + + return true; + } + + /// + /// Disposes all disposables in the group and removes them from the group. + /// + public void Dispose() + { + List? currentDisposables = null; + + lock (_gate) + { + if (!_disposed) + { + currentDisposables = _disposables; + + // nulling out the reference is faster no risk to + // future Add/Remove because _disposed will be true + // and thus _disposables won't be touched again. + _disposables = null!; // NB: All accesses are guarded by _disposed checks. + + Volatile.Write(ref _count, 0); + Volatile.Write(ref _disposed, true); + } + } + + if (currentDisposables != null) + { + foreach (var d in currentDisposables) + { + d?.Dispose(); + } + } + } + + /// + /// Removes and disposes all disposables from the , but does not dispose the . + /// + public void Clear() + { + IDisposable?[] previousDisposables; + + lock (_gate) + { + // disposed composites are always clear + if (_disposed) + { + return; + } + + var current = _disposables; + + previousDisposables = current.ToArray(); + current.Clear(); + + Volatile.Write(ref _count, 0); + } + + foreach (var d in previousDisposables) + { + d?.Dispose(); + } + } + + /// + /// Determines whether the contains a specific disposable. + /// + /// Disposable to search for. + /// true if the disposable was found; otherwise, false. + /// is null. + public bool Contains(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (_disposed) + { + return false; + } + + return _disposables.Contains(item); + } + } + + /// + /// Copies the disposables contained in the to an array, starting at a particular array index. + /// + /// Array to copy the contained disposables to. + /// Target index at which to copy the first disposable of the group. + /// is null. + /// is less than zero. -or - is larger than or equal to the array length. + public void CopyTo(IDisposable[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex >= array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + lock (_gate) + { + // disposed composites are always empty + if (_disposed) + { + return; + } + + if (arrayIndex + _count > array.Length) + { + // there is not enough space beyond arrayIndex + // to accommodate all _count disposables in this composite + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + var i = arrayIndex; + + foreach (var d in _disposables) + { + if (d != null) + { + array[i++] = d; + } + } + } + } + + /// + /// Always returns false. + /// + public bool IsReadOnly => false; + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + public IEnumerator GetEnumerator() + { + lock (_gate) + { + if (_disposed || _count == 0) + { + return EmptyEnumerator; + } + + // the copy is unavoidable but the creation + // of an outer IEnumerable is avoidable + return new CompositeEnumerator(_disposables.ToArray()); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => Volatile.Read(ref _disposed); + + /// + /// An empty enumerator for the + /// method to avoid allocation on disposed or empty composites. + /// + private static readonly CompositeEnumerator EmptyEnumerator = + new CompositeEnumerator(Array.Empty()); + + /// + /// An enumerator for an array of disposables. + /// + private sealed class CompositeEnumerator : IEnumerator + { + private readonly IDisposable?[] _disposables; + private int _index; + + public CompositeEnumerator(IDisposable?[] disposables) + { + _disposables = disposables; + _index = -1; + } + + public IDisposable Current => _disposables[_index]!; // NB: _index is only advanced to non-null positions. + + object IEnumerator.Current => _disposables[_index]!; + + public void Dispose() + { + // Avoid retention of the referenced disposables + // beyond the lifecycle of the enumerator. + // Not sure if this happens by default to + // generic array enumerators though. + var disposables = _disposables; + Array.Clear(disposables, 0, disposables.Length); + } + + public bool MoveNext() + { + var disposables = _disposables; + + for (;;) + { + var idx = ++_index; + + if (idx >= disposables.Length) + { + return false; + } + + // inlined that filter for null elements + if (disposables[idx] != null) + { + return true; + } + } + } + + public void Reset() + { + _index = -1; + } + } +} diff --git a/src/Avalonia.Base/Reactive/Disposable.cs b/src/Avalonia.Base/Reactive/Disposable.cs new file mode 100644 index 0000000000..17b0d01db5 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Disposable.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive; + +/// +/// Provides a set of static methods for creating objects. +/// +internal static class Disposable +{ + /// + /// Represents a disposable that does nothing on disposal. + /// + private sealed class EmptyDisposable : IDisposable + { + public static readonly EmptyDisposable Instance = new(); + + private EmptyDisposable() + { + } + + public void Dispose() + { + // no op + } + } + + internal sealed class AnonymousDisposable : IDisposable + { + private volatile Action? _dispose; + public AnonymousDisposable(Action dispose) + { + _dispose = dispose; + } + public bool IsDisposed => _dispose == null; + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(); + } + } + + internal sealed class AnonymousDisposable : IDisposable + { + private TState _state; + private volatile Action? _dispose; + + public AnonymousDisposable(TState state, Action dispose) + { + _state = state; + _dispose = dispose; + } + + public bool IsDisposed => _dispose == null; + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(_state); + _state = default!; + } + } + + /// + /// Gets the disposable that does nothing when disposed. + /// + public static IDisposable Empty => EmptyDisposable.Instance; + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(dispose); + } + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// The state to be passed to the action. + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(TState state, Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(state, dispose); + } +} diff --git a/src/Avalonia.Base/Reactive/DisposableMixin.cs b/src/Avalonia.Base/Reactive/DisposableMixin.cs new file mode 100644 index 0000000000..478312fa47 --- /dev/null +++ b/src/Avalonia.Base/Reactive/DisposableMixin.cs @@ -0,0 +1,37 @@ +using System; +using Avalonia.Reactive; + +namespace Avalonia.Reactive; + +/// +/// Extension methods associated with the IDisposable interface. +/// +internal static class DisposableMixin +{ + /// + /// Ensures the provided disposable is disposed with the specified . + /// + /// + /// The type of the disposable. + /// + /// + /// The disposable we are going to want to be disposed by the CompositeDisposable. + /// + /// + /// The to which will be added. + /// + /// + /// The disposable. + /// + public static T DisposeWith(this T item, CompositeDisposable compositeDisposable) + where T : IDisposable + { + if (compositeDisposable is null) + { + throw new ArgumentNullException(nameof(compositeDisposable)); + } + + compositeDisposable.Add(item); + return item; + } +} diff --git a/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs b/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs new file mode 100644 index 0000000000..272e2190b8 --- /dev/null +++ b/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs @@ -0,0 +1,8 @@ +using System; + +namespace Avalonia.Reactive; + +internal interface IAvaloniaSubject : IObserver, IObservable /*, ISubject */ +{ + +} diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs index 9a3ab89b62..263109972f 100644 --- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Disposables; using System.Threading; using Avalonia.Threading; @@ -12,7 +10,7 @@ namespace Avalonia.Reactive /// /// The observable type. /// - /// is rather heavyweight in terms of allocations and memory + /// ObservableBase{T} is rather heavyweight in terms of allocations and memory /// usage. This class provides a more lightweight base for some internal observable types /// in the Avalonia framework. /// @@ -21,11 +19,13 @@ namespace Avalonia.Reactive private Exception? _error; private List>? _observers = new List>(); + public bool HasObservers => _observers?.Count > 0; + public IDisposable Subscribe(IObserver observer) { _ = observer ?? throw new ArgumentNullException(nameof(observer)); - Dispatcher.UIThread.VerifyAccess(); + //Dispatcher.UIThread.VerifyAccess(); var first = false; diff --git a/src/Avalonia.Base/Reactive/LightweightSubject.cs b/src/Avalonia.Base/Reactive/LightweightSubject.cs new file mode 100644 index 0000000000..5663fd66d3 --- /dev/null +++ b/src/Avalonia.Base/Reactive/LightweightSubject.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Threading; + +namespace Avalonia.Reactive; + +internal class LightweightSubject : LightweightObservableBase, IAvaloniaSubject +{ + public void OnCompleted() + { + PublishCompleted(); + } + + public void OnError(Exception error) + { + PublishError(error); + } + + public void OnNext(T value) + { + PublishNext(value); + } + + protected override void Initialize() { } + + protected override void Deinitialize() { } +} diff --git a/src/Avalonia.Base/Reactive/Observable.cs b/src/Avalonia.Base/Reactive/Observable.cs new file mode 100644 index 0000000000..1009829429 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Observable.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Reactive.Operators; +using Avalonia.Threading; + +namespace Avalonia.Reactive; + +/// +/// Provides common observable methods as a replacement for the Rx framework. +/// +internal static class Observable +{ + public static IObservable Create(Func, IDisposable> subscribe) + { + return new CreateWithDisposableObservable(subscribe); + } + + public static IDisposable Subscribe(this IObservable source, Action action) + { + return source.Subscribe(new AnonymousObserver(action)); + } + + public static IObservable Select(this IObservable source, Func selector) + { + return Create(obs => + { + return source.Subscribe(new AnonymousObserver( + input => + { + TResult value; + try + { + value = selector(input); + } + catch (Exception ex) + { + obs.OnError(ex); + return; + } + + obs.OnNext(value); + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable StartWith(this IObservable source, TSource value) + { + return Create(obs => + { + obs.OnNext(value); + return source.Subscribe(obs); + }); + } + + public static IObservable Where(this IObservable source, Func predicate) + { + return Create(obs => + { + return source.Subscribe(new AnonymousObserver( + input => + { + bool shouldRun; + try + { + shouldRun = predicate(input); + } + catch (Exception ex) + { + obs.OnError(ex); + return; + } + if (shouldRun) + { + obs.OnNext(input); + } + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable Switch( + this IObservable> sources) + { + return new Switch(sources); + } + + public static IObservable CombineLatest( + this IObservable first, IObservable second, + Func resultSelector) + { + return new CombineLatest(first, second, resultSelector); + } + + public static IObservable CombineLatest( + this IEnumerable> inputs) + { + return new CombineLatest(inputs, items => items); + } + + public static IObservable Skip(this IObservable source, int skipCount) + { + if (skipCount <= 0) + { + throw new ArgumentException("Skip count must be bigger than zero", nameof(skipCount)); + } + + return Create(obs => + { + var remaining = skipCount; + return source.Subscribe(new AnonymousObserver( + input => + { + if (remaining <= 0) + { + obs.OnNext(input); + } + else + { + remaining--; + } + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable Take(this IObservable source, int takeCount) + { + if (takeCount <= 0) + { + return Empty(); + } + + return Create(obs => + { + var remaining = takeCount; + IDisposable? sub = null; + sub = source.Subscribe(new AnonymousObserver( + input => + { + if (remaining > 0) + { + --remaining; + obs.OnNext(input); + + if (remaining == 0) + { + sub?.Dispose(); + obs.OnCompleted(); + } + } + }, obs.OnError, obs.OnCompleted)); + return sub; + }); + } + + public static IObservable FromEventPattern(Action addHandler, Action removeHandler) + { + return Create(observer => + { + var handler = new Action(observer.OnNext); + var converted = new EventHandler((_, args) => handler(args)); + addHandler(converted); + + return Disposable.Create(() => removeHandler(converted)); + }); + } + + public static IObservable Return(T value) + { + return new ReturnImpl(value); + } + + public static IObservable Empty() + { + return EmptyImpl.Instance; + } + + /// + /// Returns an observable that fires once with the specified value and never completes. + /// + /// The type of the value. + /// The value. + /// The observable. + public static IObservable SingleValue(T value) + { + return new SingleValueImpl(value); + } + + private sealed class SingleValueImpl : IObservable + { + private readonly T _value; + + public SingleValueImpl(T value) + { + _value = value; + } + public IDisposable Subscribe(IObserver observer) + { + observer.OnNext(_value); + return Disposable.Empty; + } + } + + private sealed class ReturnImpl : IObservable + { + private readonly T _value; + + public ReturnImpl(T value) + { + _value = value; + } + public IDisposable Subscribe(IObserver observer) + { + observer.OnNext(_value); + observer.OnCompleted(); + return Disposable.Empty; + } + } + + internal sealed class EmptyImpl : IObservable + { + internal static readonly IObservable Instance = new EmptyImpl(); + + private EmptyImpl() { } + + public IDisposable Subscribe(IObserver observer) + { + observer.OnCompleted(); + return Disposable.Empty; + } + } + + private sealed class CreateWithDisposableObservable : IObservable + { + private readonly Func, IDisposable> _subscribe; + + public CreateWithDisposableObservable(Func, IDisposable> subscribe) + { + _subscribe = subscribe; + } + + public IDisposable Subscribe(IObserver observer) + { + return _subscribe(observer); + } + } +} diff --git a/src/Avalonia.Base/Reactive/ObservableEx.cs b/src/Avalonia.Base/Reactive/ObservableEx.cs deleted file mode 100644 index 1cea568c88..0000000000 --- a/src/Avalonia.Base/Reactive/ObservableEx.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Reactive.Disposables; - -namespace Avalonia.Reactive -{ - /// - /// Provides common observable methods not found in standard Rx framework. - /// - public static class ObservableEx - { - /// - /// Returns an observable that fires once with the specified value and never completes. - /// - /// The type of the value. - /// The value. - /// The observable. - public static IObservable SingleValue(T value) - { - return new SingleValueImpl(value); - } - - private class SingleValueImpl : IObservable - { - private T _value; - - public SingleValueImpl(T value) - { - _value = value; - } - public IDisposable Subscribe(IObserver observer) - { - observer.OnNext(_value); - return Disposable.Empty; - } - } - } -} diff --git a/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs b/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs new file mode 100644 index 0000000000..aa95ceec35 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Avalonia.Reactive.Operators; + +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Linq/Observable/CombineLatest.cs + +internal sealed class CombineLatest : IObservable +{ + private readonly IObservable _first; + private readonly IObservable _second; + private readonly Func _resultSelector; + + public CombineLatest(IObservable first, IObservable second, + Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + public IDisposable Subscribe(IObserver observer) + { + var sink = new _(_resultSelector, observer); + sink.Run(_first, _second); + return sink; + } + + internal sealed class _ : IdentitySink + { + private readonly Func _resultSelector; + private readonly object _gate = new object(); + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + _firstDisposable = null!; + _secondDisposable = null!; + } + + private IDisposable _firstDisposable; + private IDisposable _secondDisposable; + + public void Run(IObservable first, IObservable second) + { + var fstO = new FirstObserver(this); + var sndO = new SecondObserver(this); + + fstO.SetOther(sndO); + sndO.SetOther(fstO); + + _firstDisposable = first.Subscribe(fstO); + _secondDisposable = second.Subscribe(sndO); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _firstDisposable.Dispose(); + _secondDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver + { + private readonly _ _parent; + private SecondObserver _other; + + public FirstObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(SecondObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TFirst? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TFirst value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(value, _other.Value!); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._firstDisposable.Dispose(); + } + } + } + } + + private sealed class SecondObserver : IObserver + { + private readonly _ _parent; + private FirstObserver _other; + + public SecondObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(FirstObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TSecond? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TSecond value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(_other.Value!, value); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._secondDisposable.Dispose(); + } + } + } + } + } +} + +internal sealed class CombineLatest : IObservable +{ + private readonly IEnumerable> _sources; + private readonly Func _resultSelector; + + public CombineLatest(IEnumerable> sources, Func resultSelector) + { + _sources = sources; + _resultSelector = resultSelector; + } + + public IDisposable Subscribe(IObserver observer) + { + var sink = new _(_resultSelector, observer); + sink.Run(_sources); + return sink; + } + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new object(); + private readonly Func _resultSelector; + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + + // NB: These will be set in Run before getting used. + _hasValue = null!; + _values = null!; + _isDone = null!; + _subscriptions = null!; + } + + private bool[] _hasValue; + private bool _hasValueAll; + private TSource[] _values; + private bool[] _isDone; + private IDisposable[] _subscriptions; + + public void Run(IEnumerable> sources) + { + var srcs = sources.ToArray(); + + var N = srcs.Length; + + _hasValue = new bool[N]; + _hasValueAll = false; + + _values = new TSource[N]; + + _isDone = new bool[N]; + + _subscriptions = new IDisposable[N]; + + for (var i = 0; i < N; i++) + { + var j = i; + + var o = new SourceObserver(this, j); + _subscriptions[j] = o; + + o.Disposable = srcs[j].Subscribe(o); + } + + SetUpstream(new CompositeDisposable(_subscriptions)); + } + + private void OnNext(int index, TSource value) + { + lock (_gate) + { + _values[index] = value; + + _hasValue[index] = true; + + if (_hasValueAll || (_hasValueAll = _hasValue.All(v => v))) + { + TResult res; + try + { + res = _resultSelector(_values); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(res); + } + else if (_isDone.Where((_, i) => i != index).All(d => d)) + { + ForwardOnCompleted(); + } + } + } + + private new void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + private void OnCompleted(int index) + { + lock (_gate) + { + _isDone[index] = true; + + if (_isDone.All(d => d)) + { + ForwardOnCompleted(); + } + else + { + _subscriptions[index].Dispose(); + } + } + } + + private sealed class SourceObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly int _index; + + public SourceObserver(_ parent, int index) + { + _parent = parent; + _index = index; + } + + public IDisposable? Disposable { get; set; } + + public void OnNext(TSource value) + { + _parent.OnNext(_index, value); + } + + public void OnError(Exception error) + { + _parent.OnError(error); + } + + public void OnCompleted() + { + _parent.OnCompleted(_index); + } + + public void Dispose() + { + Disposable?.Dispose(); + } + } + } +} diff --git a/src/Avalonia.Base/Reactive/Operators/Sink.cs b/src/Avalonia.Base/Reactive/Operators/Sink.cs new file mode 100644 index 0000000000..0fef350acc --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/Sink.cs @@ -0,0 +1,111 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive.Operators; + +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs + +internal abstract class Sink : IDisposable +{ + private IDisposable? _upstream; + private volatile IObserver _observer; + + protected Sink(IObserver observer) + { + _observer = observer; + } + + public void Dispose() + { + Dispose(true); + } + + /// + /// Override this method to dispose additional resources. + /// The method is guaranteed to be called at most once. + /// + /// If true, the method was called from . + protected virtual void Dispose(bool disposing) + { + //Calling base.Dispose(true) is not a proper disposal, so we can omit the assignment here. + //Sink is internal so this can pretty much be enforced. + //_observer = NopObserver.Instance; + + _upstream?.Dispose(); + } + + public void ForwardOnNext(TTarget value) + { + _observer.OnNext(value); + } + + public void ForwardOnCompleted() + { + _observer.OnCompleted(); + Dispose(); + } + + public void ForwardOnError(Exception error) + { + _observer.OnError(error); + Dispose(); + } + + protected void SetUpstream(IDisposable upstream) + { + _upstream = upstream; + } + + protected void DisposeUpstream() + { + _upstream?.Dispose(); + } +} + +internal abstract class Sink : Sink, IObserver +{ + protected Sink(IObserver observer) : base(observer) + { + } + + public virtual void Run(IObservable source) + { + SetUpstream(source.Subscribe(this)); + } + + public abstract void OnNext(TSource value); + + public virtual void OnError(Exception error) => ForwardOnError(error); + + public virtual void OnCompleted() => ForwardOnCompleted(); + + public IObserver GetForwarder() => new _(this); + + private sealed class _ : IObserver + { + private readonly Sink _forward; + + public _(Sink forward) + { + _forward = forward; + } + + public void OnNext(TTarget value) => _forward.ForwardOnNext(value); + + public void OnError(Exception error) => _forward.ForwardOnError(error); + + public void OnCompleted() => _forward.ForwardOnCompleted(); + } +} + +internal abstract class IdentitySink : Sink +{ + protected IdentitySink(IObserver observer) : base(observer) + { + } + + public override void OnNext(T value) + { + ForwardOnNext(value); + } +} diff --git a/src/Avalonia.Base/Reactive/Operators/Switch.cs b/src/Avalonia.Base/Reactive/Operators/Switch.cs new file mode 100644 index 0000000000..bc849c499c --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/Switch.cs @@ -0,0 +1,144 @@ +using System; + +namespace Avalonia.Reactive.Operators; + +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Linq/Observable/Switch.cs + +internal sealed class Switch : IObservable +{ + private readonly IObservable> _sources; + + public Switch(IObservable> sources) + { + _sources = sources; + } + + public IDisposable Subscribe(IObserver observer) + { + return _sources.Subscribe(new _(observer)); + } + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new object(); + + public _(IObserver observer) + : base(observer) + { + } + + private IDisposable? _innerSerialDisposable; + private bool _isStopped; + private ulong _latest; + private bool _hasLatest; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _innerSerialDisposable?.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(IObservable value) + { + ulong id; + + lock (_gate) + { + id = unchecked(++_latest); + _hasLatest = true; + } + + var innerObserver = new InnerObserver(this, id); + + _innerSerialDisposable = innerObserver; + innerObserver.Disposable = value.Subscribe(innerObserver); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + DisposeUpstream(); + + _isStopped = true; + if (!_hasLatest) + { + ForwardOnCompleted(); + } + } + } + + private sealed class InnerObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly ulong _id; + + public InnerObserver(_ parent, ulong id) + { + _parent = parent; + _id = id; + } + + public IDisposable? Disposable { get; set; } + + public void OnNext(TSource value) + { + lock (_parent._gate) + { + if (_parent._latest == _id) + { + _parent.ForwardOnNext(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent.ForwardOnError(error); + } + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent._hasLatest = false; + + if (_parent._isStopped) + { + _parent.ForwardOnCompleted(); + } + } + } + } + + public void Dispose() + { + Disposable?.Dispose(); + } + } + } +} diff --git a/src/Avalonia.Base/Reactive/SerialDisposableValue.cs b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs new file mode 100644 index 0000000000..9eaf6343bf --- /dev/null +++ b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive; + +/// +/// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. +/// +internal sealed class SerialDisposableValue : IDisposable +{ + private IDisposable? _current; + private bool _disposed; + + public IDisposable? Disposable + { + get => _current; + set + { + _current?.Dispose(); + _current = value; + + if (_disposed) + { + _current?.Dispose(); + _current = null; + } + } + } + + public void Dispose() + { + _disposed = true; + _current?.Dispose(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index b4817bfe9a..7fc5487171 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -111,7 +111,7 @@ namespace Avalonia.Rendering.Composition } } - batch.CommitedAt = Server.Clock.Elapsed; + batch.CommittedAt = Server.Clock.Elapsed; _server.EnqueueBatch(batch); lock (_pendingBatchLock) diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 6e53a138cd..21f14283b5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -654,7 +654,7 @@ namespace Avalonia.Rendering.Composition.Expressions } } - res = default(T); + res = default; return false; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 8dc088fed1..aebe3a5cdd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -48,7 +48,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua } } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (reader.Read() == 1) { @@ -56,7 +56,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua _renderCommands = reader.ReadObject(); _contentBounds = null; } - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); } protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0e15cbd54b..e33dc999dc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -42,11 +42,18 @@ namespace Avalonia.Rendering.Composition.Server Root!.RenderedVisuals++; + if (Opacity != 1) + canvas.PushOpacity(Opacity); + if (AdornedVisual != null) + { + canvas.PostTransform = Matrix.Identity; + canvas.Transform = Matrix.Identity; + canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); + } var transform = GlobalTransformMatrix; canvas.PostTransform = MatrixUtils.ToMatrix(transform); canvas.Transform = Matrix.Identity; - if (Opacity != 1) - canvas.PushOpacity(Opacity); + var boundsRect = new Rect(new Size(Size.X, Size.Y)); if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); @@ -67,6 +74,8 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopGeometryClip(); if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); + if (AdornedVisual != null) + canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } @@ -155,15 +164,25 @@ namespace Avalonia.Rendering.Composition.Server _clipSizeDirty = false; } + + _combinedTransformedClipBounds = + AdornedVisual?._combinedTransformedClipBounds + ?? Parent?._combinedTransformedClipBounds + ?? new Rect(Root!.Size); - _combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root!.Size); if (_transformedClipBounds != null) _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value); EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); - IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface && - !_combinedTransformedClipBounds.IsDefault; + IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false + && Visible + && !_isBackface + && !_combinedTransformedClipBounds.IsDefault; + + IsVisibleInFrame = IsHitTestVisibleInFrame + && _parent?.IsVisibleInFrame != false + && EffectiveOpacity > 0.04; if (wasVisible != IsVisibleInFrame || positionChanged) { @@ -187,7 +206,7 @@ namespace Avalonia.Rendering.Composition.Server readback.Revision = root.Revision; readback.Matrix = GlobalTransformMatrix; readback.TargetId = Root.Id; - readback.Visible = IsVisibleInFrame; + readback.Visible = IsHitTestVisibleInFrame; } void AddDirtyRect(Rect rc) @@ -248,6 +267,7 @@ namespace Avalonia.Rendering.Composition.Server } public bool IsVisibleInFrame { get; set; } + public bool IsHitTestVisibleInFrame { get; set; } public double EffectiveOpacity { get; set; } public Rect TransformedOwnContentBounds { get; set; } public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs index 227df87a87..74889c9bfe 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -16,9 +16,9 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, _handler.Attach(this); } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); var count = reader.Read(); for (var c = 0; c < count; c++) { @@ -79,4 +79,4 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs index 08a3fdce22..2500358866 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering.Composition.Server { public List List { get; } = new List(); - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (reader.Read() == 1) { @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Server for (var c = 0; c < count; c++) List.Add(reader.ReadObject()); } - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); } public override long LastChangedBy diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index 257d3b29a2..31cb16ec20 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -104,13 +104,13 @@ namespace Avalonia.Rendering.Composition.Server } protected void SetAnimatedValue(CompositionProperty prop, ref T field, - TimeSpan commitedAt, IAnimationInstance animation) where T : struct + TimeSpan committedAt, IAnimationInstance animation) where T : struct { if (IsActive && _animations.TryGetValue(prop, out var oldAnimation)) oldAnimation.Deactivate(); _animations[prop] = animation; - animation.Initialize(commitedAt, ExpressionVariant.Create(field), prop); + animation.Initialize(committedAt, ExpressionVariant.Create(field), prop); if(IsActive) animation.Activate(); @@ -165,7 +165,7 @@ namespace Avalonia.Rendering.Composition.Server public virtual CompositionProperty? GetCompositionProperty(string fieldName) => null; - protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (this is IDisposable disp && reader.Read() == 1) @@ -174,7 +174,7 @@ namespace Avalonia.Rendering.Composition.Server public void DeserializeChanges(BatchStreamReader reader, Batch batch) { - DeserializeChangesCore(reader, batch.CommitedAt); + DeserializeChangesCore(reader, batch.CommittedAt); ValuesInvalidated(); ItselfLastChangedBy = batch.SequenceId; } diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index d3e3664f84..803ea809c0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -29,7 +29,7 @@ namespace Avalonia.Rendering.Composition.Transport public BatchStreamData Changes { get; private set; } - public TimeSpan CommitedAt { get; set; } + public TimeSpan CommittedAt { get; set; } public void Complete() { diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index a3cad3cebd..5a88f0e91d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; @@ -27,6 +28,37 @@ public record struct BatchStreamSegment public int ElementCount { get; set; } } + +// Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm, +// see https://github.com/dotnet/runtime/issues/80068 +static unsafe class UnalignedMemoryHelper +{ + public static T ReadUnaligned(byte* src) where T : unmanaged + { +#if NET6_0_OR_GREATER + Unsafe.SkipInit(out var rv); +#else + T rv; +#endif + UnalignedMemcpy((byte*)&rv, src, Unsafe.SizeOf()); + return rv; + } + + public static void WriteUnaligned(byte* dst, T value) where T : unmanaged + { + UnalignedMemcpy(dst, (byte*)&value, Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static unsafe void UnalignedMemcpy(byte* dst, byte* src, int count) + { + for (var c = 0; c < count; c++) + { + dst[c] = src[c]; + } + } +} + internal class BatchStreamWriter : IDisposable { private readonly BatchStreamData _output; @@ -74,7 +106,15 @@ internal class BatchStreamWriter : IDisposable var size = Unsafe.SizeOf(); if (_currentDataSegment.Data == IntPtr.Zero || _currentDataSegment.ElementCount + size > _memoryPool.BufferSize) NextDataSegment(); - Unsafe.WriteUnaligned((byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount, item); + var ptr = (byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + UnalignedMemoryHelper.WriteUnaligned(ptr, item); + else + Unsafe.WriteUnaligned(ptr, item); + _currentDataSegment.ElementCount += size; } @@ -125,7 +165,16 @@ internal class BatchStreamReader : IDisposable if (_memoryOffset + size > _currentDataSegment.ElementCount) throw new InvalidOperationException("Attempted to read more memory then left in the current segment"); - var rv = Unsafe.ReadUnaligned((byte*)_currentDataSegment.Data + _memoryOffset); + var ptr = (byte*)_currentDataSegment.Data + _memoryOffset; + T rv; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + rv = UnalignedMemoryHelper.ReadUnaligned(ptr); + else + rv = Unsafe.ReadUnaligned(ptr); + _memoryOffset += size; if (_memoryOffset == _currentDataSegment.ElementCount) { diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 4909c78ed1..60b144e806 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -330,11 +330,11 @@ namespace Avalonia.Rendering ? visual is IVisualWithRoundRectClip roundClipVisual ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) : context.PushClip(bounds) - : default(DrawingContext.PushedState)) + : default) #pragma warning restore CS0618 // Type or member is obsolete - using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) - using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) + using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) + using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) using (context.PushTransformContainer()) { visual.Render(context); diff --git a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs index 198b36564a..261a39fa09 100644 --- a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs +++ b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.Reactive; namespace Avalonia.Rendering; diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index 5a08bfc6a1..1f58ca3827 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -50,8 +50,7 @@ namespace Avalonia.Rendering { get { - return _timer ??= AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Cannot locate IRenderTimer."); + return _timer ??= AvaloniaLocator.Current.GetRequiredService(); } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs index a991f2f657..b9491e6cbd 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; @@ -303,7 +303,7 @@ namespace Avalonia.Rendering.SceneGraph if (ClipToBounds) { context.Transform = Matrix.Identity; - if (ClipToBoundsRadius.IsEmpty) + if (ClipToBoundsRadius.IsDefault) context.PushClip(ClipBounds); else context.PushClip(new RoundedRect(ClipBounds, ClipToBoundsRadius)); diff --git a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs index dd6cf7ad15..1bbf804b5f 100644 --- a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Threading; namespace Avalonia.Rendering diff --git a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs index af2c7f71dc..6b34b44337 100644 --- a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs @@ -109,7 +109,7 @@ namespace Avalonia.Rendering.Utilities { if (IntermediateTransform != Matrix.Identity) return true; - if (SourceRect.Position != default(Point)) + if (SourceRect.Position != default) return true; if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) return false; diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index ca602167c0..61cb31c6d0 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Animation; using Avalonia.Data; using Avalonia.PropertyStore; +using Avalonia.Reactive; using Avalonia.Styling.Activators; namespace Avalonia.Styling @@ -24,7 +24,7 @@ namespace Avalonia.Styling private bool _isActive; private List? _setters; private List? _animations; - private Subject? _animationTrigger; + private LightweightSubject? _animationTrigger; public StyleInstance( IStyle style, @@ -67,7 +67,7 @@ namespace Avalonia.Styling { if (_animations is not null && control is Animatable animatable) { - _animationTrigger ??= new Subject(); + _animationTrigger ??= new LightweightSubject(); foreach (var animation in _animations) animation.Apply(animatable, null, _animationTrigger); diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 0c25d89722..f81229eb48 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Platform; namespace Avalonia.Threading @@ -176,13 +176,7 @@ namespace Avalonia.Threading { if (!IsEnabled) { - var threading = AvaloniaLocator.Current.GetService(); - - if (threading == null) - { - throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); - } - + var threading = AvaloniaLocator.Current.GetRequiredService(); _timer = threading.StartTimer(_priority, Interval, InternalTick); } } diff --git a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs b/src/Avalonia.Base/Utilities/IWeakSubscriber.cs deleted file mode 100644 index 2a5b8d39c5..0000000000 --- a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Avalonia.Utilities -{ - /// - /// Defines a listener to a event subscribed vis the . - /// - /// The type of the event arguments. - public interface IWeakSubscriber where T : EventArgs - { - /// - /// Invoked when the subscribed event is raised. - /// - /// The event sender. - /// The event arguments. - void OnEvent(object? sender, T e); - } -} diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs index 00cc5864f5..0eb7ae2e31 100644 --- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs +++ b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs @@ -42,7 +42,7 @@ namespace Avalonia.Utilities { if (!_singleValue.HasValue || !EqualityComparer.Default.Equals(_singleValue.Value.Key, key)) { - value = default(TValue); + value = default; return false; } else diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs index 726c9735ef..aad742e02b 100644 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs @@ -63,7 +63,7 @@ namespace Avalonia.Utilities } else { - result = default(Int32); + result = default; return false; } } @@ -87,7 +87,7 @@ namespace Avalonia.Utilities } else { - result = default(double); + result = default; return false; } } diff --git a/src/Avalonia.Base/Utilities/WeakObservable.cs b/src/Avalonia.Base/Utilities/WeakObservable.cs deleted file mode 100644 index e1c350d539..0000000000 --- a/src/Avalonia.Base/Utilities/WeakObservable.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; - -namespace Avalonia.Utilities -{ - /// - /// Provides extension methods for working with weak event handlers. - /// - public static class WeakObservable - { - - private class Handler - : IWeakSubscriber, - IWeakEventSubscriber where TEventArgs : EventArgs - { - private IObserver> _observer; - - public Handler(IObserver> observer) - { - _observer = observer; - } - - public void OnEvent(object? sender, TEventArgs e) - { - _observer.OnNext(new EventPattern(sender, e)); - } - - public void OnEvent(object? sender, WeakEvent ev, TEventArgs e) - { - _observer.OnNext(new EventPattern(sender, e)); - } - } - - /// - /// Converts a WeakEvent conforming to the standard .NET event pattern into an observable - /// sequence, subscribing weakly. - /// - /// The type of target. - /// The type of the event args. - /// Object instance that exposes the event to convert. - /// The weak event to convert. - /// - public static IObservable> FromEventPattern( - TTarget target, WeakEvent ev) - where TEventArgs : EventArgs where TTarget : class - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - _ = ev ?? throw new ArgumentNullException(nameof(ev)); - - return Observable.Create>(observer => - { - var handler = new Handler(observer); - ev.Subscribe(target, handler); - return () => ev.Unsubscribe(target, handler); - }).Publish().RefCount(); - } - - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 5930df5483..7fcb53bcea 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -11,6 +11,7 @@ using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Server; @@ -387,52 +388,55 @@ namespace Avalonia protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Visual { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) + var invalidateObserver = new AnonymousObserver( + static e => { - sender.InvalidateVisual(); - } - } - - static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) + if (e.Sender is T sender) + { + sender.InvalidateVisual(); + } + }); + + + var invalidateAndSubscribeObserver = new AnonymousObserver( + static e => { - if (e.OldValue is IAffectsRender oldValue) + if (e.Sender is T sender) { - if (sender._affectsRenderWeakSubscriber != null) + if (e.OldValue is IAffectsRender oldValue) { - InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber); + if (sender._affectsRenderWeakSubscriber != null) + { + InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber); + } } - } - if (e.NewValue is IAffectsRender newValue) - { - if (sender._affectsRenderWeakSubscriber == null) + if (e.NewValue is IAffectsRender newValue) { - sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber( - sender, static (target, _, _, _) => - { - target.InvalidateVisual(); - }); + if (sender._affectsRenderWeakSubscriber == null) + { + sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber( + sender, static (target, _, _, _) => + { + target.InvalidateVisual(); + }); + } + InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber); } - InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber); - } - sender.InvalidateVisual(); - } - } + sender.InvalidateVisual(); + } + }); foreach (var property in properties) { if (property.CanValueAffectRender()) { - property.Changed.Subscribe(e => InvalidateAndSubscribe(e)); + property.Changed.Subscribe(invalidateAndSubscribeObserver); } else { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } } @@ -480,6 +484,7 @@ namespace Avalonia { AttachToCompositor(compositingRenderer.Compositor); } + InvalidateMirrorTransform(); OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); @@ -619,23 +624,22 @@ namespace Avalonia /// Called when a visual's changes. /// /// The event args. - private static void RenderTransformChanged(AvaloniaPropertyChangedEventArgs e) + private static void RenderTransformChanged(AvaloniaPropertyChangedEventArgs e) { var sender = e.Sender as Visual; if (sender?.VisualRoot != null) { - var oldValue = e.OldValue as Transform; - var newValue = e.NewValue as Transform; + var (oldValue, newValue) = e.GetOldAndNewValue(); - if (oldValue != null) + if (oldValue is Transform oldTransform) { - oldValue.Changed -= sender.RenderTransformChanged; + oldTransform.Changed -= sender.RenderTransformChanged; } - if (newValue != null) + if (newValue is Transform newTransform) { - newValue.Changed += sender.RenderTransformChanged; + newTransform.Changed += sender.RenderTransformChanged; } sender.InvalidateVisual(); diff --git a/src/Avalonia.Build.Tasks/ComInteropHelper.cs b/src/Avalonia.Build.Tasks/ComInteropHelper.cs index 007231417d..c990741e1e 100644 --- a/src/Avalonia.Build.Tasks/ComInteropHelper.cs +++ b/src/Avalonia.Build.Tasks/ComInteropHelper.cs @@ -65,10 +65,8 @@ namespace Avalonia.Build.Tasks { Instruction instruction = instructions[i]; - if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference) + if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference methodDescription) { - var methodDescription = (MethodReference)instruction.Operand; - if (methodDescription.Name.StartsWith("Calli") && methodDescription.DeclaringType.Name == "LocalInterop") { var callSite = new CallSite(methodDescription.ReturnType) { CallingConvention = MethodCallingConvention.StdCall }; diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index 97f9efe8fa..5a31053bdc 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index 2cc5b99b2e..aaf272c6d2 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -289,8 +289,8 @@ namespace Avalonia.Controls }; // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png - protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs index 2758124fae..4a30fb6cb4 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs @@ -10,8 +10,8 @@ namespace Avalonia.Controls /// public class FlatHalfColorPalette : IColorPalette { - protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index d009926bc5..d4b904163c 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -344,8 +344,8 @@ namespace Avalonia.Controls // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors // This is a reduced palette for uniformity - protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs index 01d44aa65d..4432642675 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs @@ -10,8 +10,8 @@ namespace Avalonia.Controls /// public class MaterialHalfColorPalette : IColorPalette { - protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 2df46889a9..6f4c0003a8 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -15,6 +15,7 @@ using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Threading; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index efa38e49a7..6556ce721e 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5dcad83601..f35124ee0a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -27,6 +27,7 @@ using Avalonia.Layout; using Avalonia.Controls.Metadata; using Avalonia.Input.GestureRecognizers; using Avalonia.Styling; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -51,15 +52,10 @@ namespace Avalonia.Controls private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader"; private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader"; private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner"; - private const string DATAGRID_elementValidationSummary = "PART_ValidationSummary"; private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar"; - - private const bool DATAGRID_defaultAutoGenerateColumns = true; internal const bool DATAGRID_defaultCanUserReorderColumns = true; internal const bool DATAGRID_defaultCanUserResizeColumns = true; internal const bool DATAGRID_defaultCanUserSortColumns = true; - private const DataGridRowDetailsVisibilityMode DATAGRID_defaultRowDetailsVisibility = DataGridRowDetailsVisibilityMode.VisibleWhenSelected; - private const DataGridSelectionMode DATAGRID_defaultSelectionMode = DataGridSelectionMode.Extended; /// /// The default order to use for columns when there is no @@ -1123,7 +1119,7 @@ namespace Avalonia.Controls EnsureColumnHeadersVisibility(); if (!newValueCols) { - _columnHeadersPresenter.Measure(default(Size)); + _columnHeadersPresenter.Measure(default); } else { @@ -1164,7 +1160,7 @@ namespace Avalonia.Controls _topLeftCornerHeader.IsVisible = newValueRows && newValueCols; if (_topLeftCornerHeader.IsVisible) { - _topLeftCornerHeader.Measure(default(Size)); + _topLeftCornerHeader.Measure(default); } } @@ -3299,7 +3295,7 @@ namespace Avalonia.Controls newCell.IsVisible = column.IsVisible; if (row.OwningGrid.CellTheme is {} cellTheme) { - newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template); } } row.Cells.Insert(column.Index, newCell); @@ -4157,6 +4153,7 @@ namespace Avalonia.Controls if (exitEditingMode) { + CurrentColumn.EndCellEditInternal(); _editingColumnIndex = -1; editingCell.UpdatePseudoClasses(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index ce74604f70..e859a6e725 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -4,12 +4,7 @@ // All other rights reserved. using Avalonia.Data; -using Avalonia.Utilities; using System; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using Avalonia.Reactive; -using System.Diagnostics; using Avalonia.Controls.Utils; using Avalonia.Markup.Xaml.MarkupExtensions; diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index c308312398..39c1e4c118 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static StyledProperty IsThreeStateProperty = + public static readonly StyledProperty IsThreeStateProperty = CheckBox.IsThreeStateProperty.AddOwner(); /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 75101dc487..d1e1efdd85 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -185,7 +185,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static StyledProperty IsVisibleProperty = + public static readonly StyledProperty IsVisibleProperty = Control.IsVisibleProperty.AddOwner(); /// @@ -800,11 +800,22 @@ namespace Avalonia.Controls protected internal virtual void RefreshCellContent(Control element, string propertyName) { } + /// + /// When overridden in a derived class, called when a cell in the column exits editing mode. + /// + protected virtual void EndCellEdit() + { } + internal void CancelCellEditInternal(Control editingElement, object uneditedValue) { CancelCellEdit(editingElement, uneditedValue); } + internal void EndCellEditInternal() + { + EndCellEdit(); + } + /// /// Coerces a DataGridLength to a valid value. If any value components are double.NaN, this method /// coerces them to a proper initial value. For star columns, the desired width is calculated based @@ -897,7 +908,7 @@ namespace Avalonia.Controls result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; if (OwningGrid.ColumnHeaderTheme is {} columnTheme) { - result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.TemplatedParent); + result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.Template); } return result; diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 252868847a..b3e106a7bf 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -673,7 +673,7 @@ namespace Avalonia.Controls }; if (OwningGrid.ColumnHeaderTheme is {} columnHeaderTheme) { - dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.TemplatedParent); + dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.Template); } dragIndicator.PseudoClasses.Add(":dragIndicator"); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index dfa6ea2e46..ea9b2fe972 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -15,6 +15,7 @@ using Avalonia.Utilities; using Avalonia.VisualTree; using System; using System.Diagnostics; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -253,7 +254,7 @@ namespace Avalonia.Controls }; if (OwningGrid.CellTheme is {} cellTheme) { - _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template); } if (_cellsElement != null) { @@ -1021,11 +1022,11 @@ namespace Avalonia.Controls { layoutableContent.LayoutUpdated += DetailsContent_LayoutUpdated; - _detailsContentSizeSubscription = - System.Reactive.Disposables.StableCompositeDisposable.Create( - System.Reactive.Disposables.Disposable.Create(() => layoutableContent.LayoutUpdated -= DetailsContent_LayoutUpdated), - _detailsContent.GetObservable(MarginProperty) - .Subscribe(DetailsContent_MarginChanged)); + _detailsContentSizeSubscription = new CompositeDisposable(2) + { + Disposable.Create(() => layoutableContent.LayoutUpdated -= DetailsContent_LayoutUpdated), + _detailsContent.GetObservable(MarginProperty).Subscribe(DetailsContent_MarginChanged) + }; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index c746b19cc7..10efded58a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -10,7 +10,7 @@ using Avalonia.Input; using Avalonia.Media; using System; using System.Diagnostics; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 4d3bccee70..00e035270c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -1029,7 +1029,7 @@ namespace Avalonia.Controls dataGridRow.DataContext = dataContext; if (RowTheme is {} rowTheme) { - dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.TemplatedParent); + dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.Template); } CompleteCellsCollection(dataGridRow); @@ -2743,7 +2743,7 @@ namespace Avalonia.Controls groupHeader.Level = rowGroupInfo.Level; if (RowGroupTheme is {} rowGroupTheme) { - groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.TemplatedParent); + groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.Template); } // Set the RowGroupHeader's PropertyName. Unfortunately, CollectionViewGroup doesn't have this diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 24ae358dcc..516e9cf6c2 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -55,17 +55,25 @@ namespace Avalonia.Controls get => _cellEditingCellTemplate; set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); } - - private static void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) + + private bool _forceGenerateCellFromTemplate; + + protected override void EndCellEdit() { - var oldValue = (IDataTemplate)e.OldValue; - var value = (IDataTemplate)e.NewValue; + //the next call to generate element should not resuse the current content as we need to exit edit mode + _forceGenerateCellFromTemplate = true; + base.EndCellEdit(); } protected override Control GenerateElement(DataGridCell cell, object dataItem) { if (CellTemplate != null) { + if (_forceGenerateCellFromTemplate) + { + _forceGenerateCellFromTemplate = false; + return CellTemplate.Build(dataItem); + } return (CellTemplate is IRecyclingDataTemplate recyclingDataTemplate) ? recyclingDataTemplate.Build(dataItem, cell.Content as Control) : CellTemplate.Build(dataItem); diff --git a/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs b/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs index 65c3af344c..68a2fc562f 100644 --- a/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs +++ b/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs @@ -422,7 +422,7 @@ namespace Avalonia.Controls else { found = false; - return default(T); + return default; } } diff --git a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs index 1d1a595ccf..d6c46bb1e0 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs @@ -2,7 +2,7 @@ using Avalonia.Reactive; using System; using System.Collections.Generic; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Controls.Utils { @@ -16,16 +16,16 @@ namespace Avalonia.Controls.Utils internal class CellEditBinding : ICellEditBinding { - private readonly Subject _changedSubject = new Subject(); + private readonly LightweightSubject _changedSubject = new(); private readonly List _validationErrors = new List(); private readonly SubjectWrapper _inner; public bool IsValid => _validationErrors.Count <= 0; public IEnumerable ValidationErrors => _validationErrors; public IObservable ValidationChanged => _changedSubject; - public ISubject InternalSubject => _inner; + public IAvaloniaSubject InternalSubject => _inner; - public CellEditBinding(ISubject bindingSourceSubject) + public CellEditBinding(IAvaloniaSubject bindingSourceSubject) { _inner = new SubjectWrapper(bindingSourceSubject, this); } @@ -48,16 +48,16 @@ namespace Avalonia.Controls.Utils return IsValid; } - class SubjectWrapper : LightweightObservableBase, ISubject, IDisposable + class SubjectWrapper : LightweightObservableBase, IAvaloniaSubject, IDisposable { - private readonly ISubject _sourceSubject; + private readonly IAvaloniaSubject _sourceSubject; private readonly CellEditBinding _editBinding; private IDisposable _subscription; private object _controlValue; private bool _isControlValueSet = false; private bool _settingSourceValue = false; - public SubjectWrapper(ISubject bindingSourceSubject, CellEditBinding editBinding) + public SubjectWrapper(IAvaloniaSubject bindingSourceSubject, CellEditBinding editBinding) { _sourceSubject = bindingSourceSubject; _editBinding = editBinding; diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index dc9a0207ad..5b652cce19 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Concurrency; -using System.Threading; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; @@ -231,7 +229,6 @@ namespace Avalonia .Bind().ToConstant(FocusManager) .Bind().ToConstant(InputManager) .Bind().ToTransient() - .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance); // TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 4b88f6b537..22b5f8236d 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Avalonia.Metadata; namespace Avalonia.Controls.ApplicationLifetimes @@ -19,7 +18,7 @@ namespace Avalonia.Controls.ApplicationLifetimes /// /// Gets the arguments passed to the - /// + /// /// method. /// string[]? Args { get; } diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index e0d986f2b4..98885e11ca 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -10,7 +10,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; using Avalonia.Collections; @@ -474,6 +474,7 @@ namespace Avalonia.Controls FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); + ItemTemplateProperty.Changed.AddClassHandler((x,e) => x.OnItemTemplatePropertyChanged(e)); IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index cf4beab6d4..42c577041a 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -6,7 +6,6 @@ - diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs index 7afbf9edcf..b0e5c30e2f 100644 --- a/src/Avalonia.Controls/BorderVisual.cs +++ b/src/Avalonia.Controls/BorderVisual.cs @@ -50,7 +50,7 @@ class CompositionBorderVisual : CompositionDrawListVisual if (ClipToBounds) { var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y))); - if (_cornerRadius.IsEmpty) + if (_cornerRadius.IsDefault) canvas.PushClip(clipRect); else canvas.PushClip(new RoundedRect(clipRect, _cornerRadius)); @@ -63,9 +63,9 @@ class CompositionBorderVisual : CompositionDrawListVisual } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); if (reader.Read()) _cornerRadius = reader.Read(); } @@ -73,4 +73,4 @@ class CompositionBorderVisual : CompositionDrawListVisual protected override bool HandlesClipToBounds => true; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 8e5d4e1e06..1ec6f8dabc 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -5,6 +5,7 @@ using System.Windows.Input; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index e455c6c6f3..4b2997f431 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -1,7 +1,6 @@ -using System; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; -using Avalonia.Data; +using Avalonia.Reactive; using Avalonia.Input; using Avalonia.Interactivity; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index ec1273ca98..b17648f5bb 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -7,7 +7,7 @@ using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs index adee7d4d90..cd81c6e59b 100644 --- a/src/Avalonia.Controls/Canvas.cs +++ b/src/Avalonia.Controls/Canvas.cs @@ -1,7 +1,4 @@ -using System; -using System.Reactive.Concurrency; using Avalonia.Input; -using Avalonia.Layout; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 6d7e542bb2..f5fcbed9fb 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Chrome [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] public class CaptionButtons : TemplatedControl { - private CompositeDisposable? _disposables; + private IDisposable? _disposables; /// /// Currently attached window. @@ -28,17 +28,14 @@ namespace Avalonia.Controls.Chrome { HostWindow = hostWindow; - _disposables = new CompositeDisposable - { - HostWindow.GetObservable(Window.WindowStateProperty) + _disposables = HostWindow.GetObservable(Window.WindowStateProperty) .Subscribe(x => { PseudoClasses.Set(":minimized", x == WindowState.Minimized); PseudoClasses.Set(":normal", x == WindowState.Normal); PseudoClasses.Set(":maximized", x == WindowState.Maximized); PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }) - }; + }); } } diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 1bf13111a9..47b0bb6e2d 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Chrome if (VisualRoot is Window window) { - _disposables = new CompositeDisposable + _disposables = new CompositeDisposable(6) { window.GetObservable(Window.WindowDecorationMarginProperty) .Subscribe(x => UpdateSize(window)), diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index a96cfe4690..8da99b5f25 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Avalonia.Automation.Peers; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; diff --git a/src/Avalonia.Controls/ComboBoxItem.cs b/src/Avalonia.Controls/ComboBoxItem.cs index 83057d139f..7db713d692 100644 --- a/src/Avalonia.Controls/ComboBoxItem.cs +++ b/src/Avalonia.Controls/ComboBoxItem.cs @@ -1,5 +1,4 @@ -using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Automation; using Avalonia.Automation.Peers; @@ -12,8 +11,14 @@ namespace Avalonia.Controls { public ComboBoxItem() { - this.GetObservable(ComboBoxItem.IsFocusedProperty).Where(focused => focused) - .Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this)); + this.GetObservable(ComboBoxItem.IsFocusedProperty) + .Subscribe(focused => + { + if (focused) + { + (Parent as ComboBox)?.ItemFocused(this); + } + }); } static ComboBoxItem() diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 020a6de539..b7ff75339d 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -15,6 +15,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Styling; using Avalonia.Automation; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 26db431a0e..ed24c3c7c2 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -392,14 +392,6 @@ namespace Avalonia.Controls OnUnloadedCore(); } - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - InvalidateMirrorTransform(); - } - /// protected override void OnGotFocus(GotFocusEventArgs e) { diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index bceeb58adb..889a4cc79f 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -1,8 +1,5 @@ using System; -using System.Linq; -using Avalonia.Data; -using Avalonia.LogicalTree; -using Avalonia.Styling; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs index 00a06101c5..b082909807 100644 --- a/src/Avalonia.Controls/DataValidationErrors.cs +++ b/src/Avalonia.Controls/DataValidationErrors.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Templates; using Avalonia.Data; diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 64a02ccb46..5c35a09f1c 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Controls @@ -805,14 +806,12 @@ namespace Avalonia.Controls /// The properties. protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure(); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidateObserver); } } } diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs index 9fddb91231..343b628fb9 100644 --- a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -8,6 +8,6 @@ /// /// Provides access to the internal for use in DevTools. /// - public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; + public static readonly AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; } } diff --git a/src/Avalonia.Controls/Documents/Run.cs b/src/Avalonia.Controls/Documents/Run.cs index 5d7b8998e6..f64cf79127 100644 --- a/src/Avalonia.Controls/Documents/Run.cs +++ b/src/Avalonia.Controls/Documents/Run.cs @@ -54,6 +54,11 @@ namespace Avalonia.Controls.Documents { var text = Text ?? ""; + if (string.IsNullOrEmpty(text)) + { + return; + } + var textRunProperties = CreateTextRunProperties(); var textCharacters = new TextCharacters(text, textRunProperties); diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 30bdb4c60e..e4487d29fa 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -3,6 +3,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using System; +using Avalonia.Reactive; using Avalonia.Media.Immutable; namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 8455495830..9d4abec549 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -7,6 +7,7 @@ using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.Reactive; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index dc3c621db4..de753f0bd0 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; using Avalonia.Input; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 766a712c88..ce254684b7 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -4,7 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Media; namespace Avalonia.Controls @@ -424,9 +424,9 @@ namespace Avalonia.Controls if (newTransform != null) { - _transformChangedEvent = Observable.FromEventPattern( + _transformChangedEvent = Observable.FromEventPattern( v => newTransform.Changed += v, v => newTransform.Changed -= v) - .Subscribe(onNext: v => ApplyLayoutTransform()); + .Subscribe(_ => ApplyLayoutTransform()); } ApplyLayoutTransform(); diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 3e00cea430..40941d00be 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Windows.Input; using Avalonia.Automation.Peers; using Avalonia.Controls.Generators; @@ -159,12 +159,13 @@ namespace Avalonia.Controls // menu layout. var parentSharedSizeScope = this.GetObservable(VisualParentProperty) - .SelectMany(x => + .Select(x => { var parent = x as Control; return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ?? Observable.Return(null); - }); + }) + .Switch(); this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope); } diff --git a/src/Avalonia.Controls/Mixins/DisposableMixin.cs b/src/Avalonia.Controls/Mixins/DisposableMixin.cs deleted file mode 100644 index 9b30b4ba4c..0000000000 --- a/src/Avalonia.Controls/Mixins/DisposableMixin.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Reactive.Disposables; - -namespace Avalonia.Controls.Mixins -{ - /// - /// Extension methods associated with the IDisposable interface. - /// - public static class DisposableMixin - { - /// - /// Ensures the provided disposable is disposed with the specified . - /// - /// - /// The type of the disposable. - /// - /// - /// The disposable we are going to want to be disposed by the CompositeDisposable. - /// - /// - /// The to which will be added. - /// - /// - /// The disposable. - /// - public static T DisposeWith(this T item, CompositeDisposable compositeDisposable) - where T : IDisposable - { - if (compositeDisposable is null) - { - throw new ArgumentNullException(nameof(compositeDisposable)); - } - - compositeDisposable.Add(item); - return item; - } - } -} diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index a04a741e3e..2a8fe7b976 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -1,7 +1,7 @@ using System; using Avalonia.Interactivity; using Avalonia.Controls.Primitives; -using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Controls.Mixins { diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index fe650ab41e..c556ce7b02 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; using Avalonia.Controls.Platform; -using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index ac7a45a547..3953de8165 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index d22fdb2f84..526eff0f12 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Metadata; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs index a103adf185..663bd3358a 100644 --- a/src/Avalonia.Controls/Notifications/NotificationCard.cs +++ b/src/Avalonia.Controls/Notifications/NotificationCard.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Interactivity; using Avalonia.LogicalTree; @@ -37,30 +37,29 @@ namespace Avalonia.Controls.Notifications RaiseEvent(new RoutedEventArgs(NotificationClosedEvent)); }); - // Disabling nullable checking because of https://github.com/dotnet/reactive/issues/1525 -#pragma warning disable CS8620 this.GetObservable(ContentProperty) - .OfType() -#pragma warning restore CS8620 .Subscribe(x => { - switch (x.Type) + if (x is Notification notification) { - case NotificationType.Error: - PseudoClasses.Add(":error"); - break; - - case NotificationType.Information: - PseudoClasses.Add(":information"); - break; - - case NotificationType.Success: - PseudoClasses.Add(":success"); - break; - - case NotificationType.Warning: - PseudoClasses.Add(":warning"); - break; + switch (notification.Type) + { + case NotificationType.Error: + PseudoClasses.Add(":error"); + break; + + case NotificationType.Information: + PseudoClasses.Add(":information"); + break; + + case NotificationType.Success: + PseudoClasses.Add(":success"); + break; + + case NotificationType.Warning: + PseudoClasses.Add(":warning"); + break; + } } }); } diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 46c772f3b1..3ccddf4155 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 8ed6abd52b..ac4f699313 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -9,6 +9,7 @@ using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.Reactive; using Avalonia.Threading; using Avalonia.Utilities; diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 7134e1b9e7..007d18c813 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -5,6 +5,7 @@ using System.Linq; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Reactive; using Avalonia.Styling; namespace Avalonia.Controls @@ -86,9 +87,11 @@ namespace Avalonia.Controls protected static void AffectsParentArrange(params AvaloniaProperty[] properties) where TPanel : Panel { + var invalidateObserver = new AnonymousObserver( + static e => AffectsParentArrangeInvalidate(e)); foreach (var property in properties) { - property.Changed.Subscribe(AffectsParentArrangeInvalidate); + property.Changed.Subscribe(invalidateObserver); } } @@ -99,9 +102,11 @@ namespace Avalonia.Controls protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) where TPanel : Panel { + var invalidateObserver = new AnonymousObserver( + static e => AffectsParentMeasureInvalidate(e)); foreach (var property in properties) { - property.Changed.Subscribe(AffectsParentMeasureInvalidate); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 6d525da150..e09da02f17 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -6,9 +6,9 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls.Platform { diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 5b2356a7ce..bfd8bd73cb 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using System.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; @@ -17,7 +15,7 @@ namespace Avalonia.Platform private const RawInputModifiers MOUSE_INPUTMODIFIERS = RawInputModifiers.LeftMouseButton|RawInputModifiers.MiddleMouseButton|RawInputModifiers.RightMouseButton; private readonly IDragDropDevice _dragDrop; private readonly IInputManager _inputManager; - private readonly Subject _result = new Subject(); + private readonly LightweightSubject _result = new(); private DragDropEffects _allowedEffects; private IDataObject? _draggedData; @@ -41,14 +39,28 @@ namespace Avalonia.Platform { _draggedData = data; _lastRoot = null; - _lastPosition = default(Point); + _lastPosition = default; _allowedEffects = allowedEffects; - using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) + var inputObserver = new AnonymousObserver(arg => { - using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents)) + switch (arg) { - var effect = await _result.FirstAsync(); + case RawPointerEventArgs pointerEventArgs: + ProcessMouseEvents(pointerEventArgs); + break; + case RawKeyEventArgs keyEventArgs: + ProcessKeyEvents(keyEventArgs); + break; + } + }); + + using (_inputManager.PreProcess.Subscribe(inputObserver)) + { + var tcs = new TaskCompletionSource(); + using (_result.Subscribe(new AnonymousObserver(tcs))) + { + var effect = await tcs.Task; return effect; } } diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index de7708e869..6f043463d4 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Metadata; using Avalonia.Platform; @@ -26,21 +26,14 @@ namespace Avalonia.Controls.Platform public static IWindowImpl CreateWindow() { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform == null) - { - throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); - } + var platform = AvaloniaLocator.Current.GetRequiredService(); return s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow(); } public static IWindowImpl CreateEmbeddableWindow() { - var platform = AvaloniaLocator.Current.GetService(); - if (platform == null) - throw new Exception("Could not CreateEmbeddableWindow(): IWindowingPlatform is not registered."); + var platform = AvaloniaLocator.Current.GetRequiredService(); return platform.CreateEmbeddableWindow(); } } diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 7b435e287d..0e283c3c4b 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -1,6 +1,6 @@ using System.Collections.Specialized; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Animation; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index 1f0f2a2f67..b9f856cf39 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Specialized; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; using Avalonia.Input; diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 328facba0b..5a691ce665 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Utilities; @@ -94,6 +92,7 @@ namespace Avalonia.Controls.Presenters { AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested); AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture); + AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded); this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); } @@ -500,7 +499,7 @@ namespace Avalonia.Controls.Presenters if (e.OldValue != null) { - Offset = default(Vector); + Offset = default; } } @@ -542,7 +541,7 @@ namespace Avalonia.Controls.Presenters if (logicalScroll != scrollable.IsLogicalScrollEnabled) { UpdateScrollableSubscription(Child); - Offset = default(Vector); + Offset = default; InvalidateMeasure(); } else if (scrollable.IsLogicalScrollEnabled) diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 7e5b34acd9..49e76d0728 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -1,7 +1,6 @@ -using System; -using System.Diagnostics.CodeAnalysis; using Avalonia.Automation.Peers; using Avalonia.Input; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Media.TextFormatting; diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 89abe1cdaa..c9585d50ae 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.VisualTree; diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index cd26ea4f6e..e0f72cae54 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Interactivity; using Avalonia.Media; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3c329a9a3e..bf79198939 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,6 @@ using System; using System.ComponentModel; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Automation.Peers; using Avalonia.Controls.Mixins; using Avalonia.Controls.Diagnostics; diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 165bec3a95..7e0d695264 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs index d0b8178add..2c29b19d48 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.PullToRefresh; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index f2f735aaa9..8723304f8f 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -1,12 +1,12 @@ using System; using System.Numerics; -using System.Reactive.Linq; using Avalonia.Animation.Easings; using Avalonia.Controls.Primitives; using Avalonia.Controls.PullToRefresh; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; diff --git a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs index c3aebc82c5..844973bd28 100644 --- a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs +++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs @@ -197,12 +197,12 @@ namespace Avalonia.Controls.PullToRefresh throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual"); } - if (content.Parent is not InputElement) + if (content.Parent is not InputElement parent) { throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement"); } - MakeInteractionSource(content.Parent as InputElement); + MakeInteractionSource(parent); if (_scrollViewer != null) { diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs index 9dbc5f040e..68da24d79f 100644 --- a/src/Avalonia.Controls/RadioButton.cs +++ b/src/Avalonia.Controls/RadioButton.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Controls.Primitives; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.VisualTree; diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index 10e3dd57a0..56e0cda8fe 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -4,16 +4,8 @@ // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; -using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Avalonia.Controls.Presenters; -using Avalonia.Input; using Avalonia.Layout; using Avalonia.Logging; -using Avalonia.Media; -using Avalonia.Reactive; using Avalonia.Threading; using Avalonia.VisualTree; @@ -124,22 +116,6 @@ namespace Avalonia.Controls } } } - - private Rect GetLayoutVisibleWindowDiscardAnchor() - { - var visibleWindow = _visibleWindow; - - if (HasScroller) - { - visibleWindow = new Rect( - visibleWindow.X + _layoutExtent.X + _expectedViewportShift.X + _unshiftableShift.X, - visibleWindow.Y + _layoutExtent.Y + _expectedViewportShift.Y + _unshiftableShift.Y, - visibleWindow.Width, - visibleWindow.Height); - } - - return visibleWindow; - } public Rect GetLayoutVisibleWindow() { diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 12fb9ba5c5..503187e2d3 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index b343439f98..f4c4d54951 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -300,7 +300,11 @@ namespace Avalonia.Controls _wordSelectionStart = SelectionStart; - SelectionEnd = StringUtils.NextWord(text, index); + if (!StringUtils.IsEndOfWord(text, index)) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + break; case 3: _wordSelectionStart = -1; diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index e2a13512a5..c8bf95b3f7 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -2,6 +2,7 @@ using System; using Avalonia.Collections; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Reactive; namespace Avalonia.Controls.Shapes { diff --git a/src/Avalonia.Controls/Spinner.cs b/src/Avalonia.Controls/Spinner.cs index cfcf0ee376..bf6ed060bd 100644 --- a/src/Avalonia.Controls/Spinner.cs +++ b/src/Avalonia.Controls/Spinner.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/SplitButton/SplitButton.cs b/src/Avalonia.Controls/SplitButton/SplitButton.cs index f39064435d..1d2bab8d27 100644 --- a/src/Avalonia.Controls/SplitButton/SplitButton.cs +++ b/src/Avalonia.Controls/SplitButton/SplitButton.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index fcadafaab5..35b135e152 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -6,6 +6,7 @@ using Avalonia.Media; using Avalonia.Metadata; using Avalonia.VisualTree; using System; +using Avalonia.Reactive; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index cfd4234a27..6fedf2b1cd 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; namespace Avalonia.Controls.Templates diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs index d4ecdd6cf0..a093d976f7 100644 --- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -59,7 +59,7 @@ namespace Avalonia.Controls.Templates /// The untyped function. private static Func CastMatch(Func f) { - return o => (o is T) && f((T)o); + return o => o is T arg && f(arg); } /// @@ -72,7 +72,7 @@ namespace Avalonia.Controls.Templates { return (o, s) => f((T)o!, s); } - + /// /// Casts a function with a typed parameter to an untyped function. /// diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1bdec878d9..f388dc871e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -2,7 +2,7 @@ using Avalonia.Input.Platform; using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; @@ -981,7 +981,7 @@ namespace Avalonia.Controls } } - length += grapheme.Text.Length; + length += grapheme.Length; } if (length < input.Length) @@ -1475,7 +1475,11 @@ namespace Avalonia.Controls _wordSelectionStart = SelectionStart; - SelectionEnd = StringUtils.NextWord(text, index); + if (!StringUtils.IsEndOfWord(text, index)) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + break; case 3: _wordSelectionStart = -1; diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index bb18bf4c64..d3339e3edf 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -2,6 +2,7 @@ using System; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 2cd55dc3ab..1f5b8f80d2 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Notifications; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 545032befb..2e0a36ad19 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -69,6 +69,10 @@ public class TransitioningContentControl : ContentControl { Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content)); } + else if (change.Property == CurrentContentProperty) + { + UpdateLogicalTree(change.OldValue, change.NewValue); + } } protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e) @@ -94,8 +98,6 @@ public class TransitioningContentControl : ContentControl if (PageTransition != null) await PageTransition.Start(this, null, true, localToken); - UpdateLogicalTree(CurrentContent, content); - if (localToken.IsCancellationRequested) { return; diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs index 41a1abd838..d1a7a1f727 100644 --- a/src/Avalonia.Controls/TrayIcon.cs +++ b/src/Avalonia.Controls/TrayIcon.cs @@ -6,7 +6,7 @@ using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Platform; -using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index ffdd32f95c..f98bab7d40 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index d2e3feb55a..f8e06ba35d 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs @@ -1,8 +1,5 @@ using System; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Controls.Utils { @@ -13,7 +10,7 @@ namespace Avalonia.Controls.Utils private readonly StyledElement _control; private readonly Type _ancestorType; public IObservable Observable => _subject; - private readonly Subject _subject = new Subject(); + private readonly LightweightSubject _subject = new(); private FinderNode? _child; private IDisposable? _disposable; @@ -31,24 +28,21 @@ namespace Avalonia.Controls.Utils private void OnValueChanged(StyledElement? next) { - if (next == null || _ancestorType.IsAssignableFrom(next.GetType())) + if (next == null || _ancestorType.IsInstanceOfType(next)) _subject.OnNext(next); else { _child?.Dispose(); _child = new FinderNode(next, _ancestorType); - _child.Observable.Subscribe(OnChildValueChanged); + _child.Observable.Subscribe(_subject); _child.Init(); } } - private void OnChildValueChanged(StyledElement? control) => _subject.OnNext(control); - - public void Dispose() { _child?.Dispose(); - _subject.Dispose(); + _subject.OnCompleted(); _disposable?.Dispose(); } } @@ -61,7 +55,7 @@ namespace Avalonia.Controls.Utils public static IObservable Create(StyledElement control, Type ancestorType) { - return new AnonymousObservable(observer => + return Observable.Create(observer => { var finder = new FinderNode(control, ancestorType); var subscription = finder.Observable.Subscribe(observer); diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs index b2e56434b2..6c459d7985 100644 --- a/src/Avalonia.Controls/Utils/StringUtils.cs +++ b/src/Avalonia.Controls/Utils/StringUtils.cs @@ -67,6 +67,55 @@ namespace Avalonia.Controls.Utils } } + public static bool IsEndOfWord(string text, int index) + { + if (index >= text.Length) + { + return true; + } + + var codepoint = new Codepoint(text[index]); + + if (!codepoint.IsWhiteSpace) + { + return false; + } + // A 'word' starts with an AlphaNumeric or some punctuation symbols immediately + // preceeded by lwsp. + if (index > 0) + { + var nextCodePoint = new Codepoint(text[index + 1]); + + if (nextCodePoint.IsBreakChar) + { + return true; + } + } + + switch (codepoint.GeneralCategory) + { + case GeneralCategory.LowercaseLetter: + case GeneralCategory.TitlecaseLetter: + case GeneralCategory.UppercaseLetter: + case GeneralCategory.DecimalNumber: + case GeneralCategory.LetterNumber: + case GeneralCategory.OtherNumber: + case GeneralCategory.DashPunctuation: + case GeneralCategory.InitialPunctuation: + case GeneralCategory.OpenPunctuation: + case GeneralCategory.CurrencySymbol: + case GeneralCategory.MathSymbol: + return false; + + // TODO: How do you do this in .NET? + // case UnicodeCategory.OtherPunctuation: + // // words cannot start with '.', but they can start with '&' or '*' (for example) + // return g_unichar_break_type(buffer->text[index]) == G_UNICODE_BREAK_ALPHABETIC; + default: + return true; + } + } + public static int PreviousWord(string text, int cursor) { if (string.IsNullOrEmpty(text)) diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 6ff72751a6..7b663dd017 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Utils public bool TryGetLastState(out TState? _state) { - _state = default(TState); + _state = default; if (!IsLastState) return false; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 88f3b520ce..a20b4eee58 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Automation.Peers; using Avalonia.Controls.Platform; @@ -800,7 +800,7 @@ namespace Avalonia.Controls Renderer?.Start(); - Observable.FromEventPattern( + Observable.FromEventPattern( x => Closed += x, x => Closed -= x) .Take(1) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 65325aac92..aad0482b50 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -1,9 +1,5 @@ using System; using System.ComponentModel; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using Avalonia.Automation.Peers; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 867c94b126..c5255b22cd 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -19,6 +19,5 @@ - diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index b4cfffcdca..eff190c39e 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -18,12 +18,9 @@ namespace Avalonia.DesignerSupport Control control; using (PlatformManager.DesignerMode()) { - var loader = AvaloniaLocator.Current.GetService(); + var loader = AvaloniaLocator.Current.GetRequiredService(); var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); - if (loader == null) - throw new XamlLoadException("Runtime XAML loader is not registered"); - Uri baseUri = null; if (assemblyPath != null) { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 94679e8ade..3238b394fc 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Automation.Peers; using Avalonia.Controls; diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index fe694b5730..65d1bea298 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 8eadcee6f9..e8cdcfa37b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Views; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Diagnostics { @@ -72,7 +71,11 @@ namespace Avalonia.Diagnostics { throw new ArgumentNullException(nameof(application)); } - var result = Disposable.Empty; + + var openedDisposable = new SerialDisposableValue(); + var result = new CompositeDisposable(2); + result.Add(openedDisposable); + // Skip if call on Design Mode if (!Avalonia.Controls.Design.IsDesignMode && !s_attachedToApplication) @@ -90,13 +93,15 @@ namespace Avalonia.Diagnostics { s_attachedToApplication = true; - application.InputManager.PreProcess.OfType().Subscribe(e => + result.Add(application.InputManager.PreProcess.Subscribe(e => { - if (options.Gesture.Matches(e)) + if (e is RawKeyEventArgs keyEventArgs + && keyEventArgs.Type == RawKeyEventType.KeyUp + && options.Gesture.Matches(keyEventArgs)) { - result = Open(application, options, owner); + openedDisposable.Disposable = Open(application, options, owner); } - }); + })); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 5cf9e17ecf..8bff9ccde0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -33,12 +33,12 @@ namespace Avalonia.Diagnostics.ViewModels { _avaloniaObject = avaloniaObject; - TreePage = treePage; - Layout = avaloniaObject is Visual - ? new ControlLayoutViewModel((Visual)avaloniaObject) + TreePage = treePage; + Layout = avaloniaObject is Visual visual + ? new ControlLayoutViewModel(visual) : default; - NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); + NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); AppliedStyles = new ObservableCollection(); PseudoClasses = new ObservableCollection(); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index 7da43a51b7..0140281d50 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -4,6 +4,7 @@ using Avalonia.Diagnostics.Views; using Avalonia.Interactivity; using Avalonia.Threading; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index e71f0bcaec..3048431af9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -1,10 +1,10 @@ using System; -using System.Reactive.Disposables; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -84,7 +84,7 @@ namespace Avalonia.Diagnostics.ViewModels } nodes.Add(new LogicalTreeNode(window, Owner)); } - _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + _subscriptions = new CompositeDisposable(2) { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 52535aa991..3870cad7c5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,11 +1,11 @@ using System; using System.ComponentModel; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Metadata; using Avalonia.Threading; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -50,15 +50,15 @@ namespace Avalonia.Diagnostics.ViewModels } else { -#nullable disable - _pointerOverSubscription = InputManager.Instance.PreProcess - .OfType() + _pointerOverSubscription = InputManager.Instance!.PreProcess .Subscribe(e => { - PointerOverRoot = e.Root; - PointerOverElement = e.Root.InputHitTest(e.Position); + if (e is Input.Raw.RawPointerEventArgs pointerEventArgs) + { + PointerOverRoot = pointerEventArgs.Root; + PointerOverElement = pointerEventArgs.Root.InputHitTest(pointerEventArgs.Position); + } }); -#nullable restore } Console = new ConsoleViewModel(UpdateConsoleContext); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 65bfd7fc36..aafaa096e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Specialized; -using System.Reactive; -using System.Reactive.Linq; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -28,18 +28,8 @@ namespace Avalonia.Diagnostics.ViewModels { ElementName = control.Name; - var removed = Observable.FromEventPattern( - x => control.DetachedFromLogicalTree += x, - x => control.DetachedFromLogicalTree -= x); - var classesChanged = Observable.FromEventPattern< - NotifyCollectionChangedEventHandler, - NotifyCollectionChangedEventArgs>( - x => control.Classes.CollectionChanged += x, - x => control.Classes.CollectionChanged -= x) - .TakeUntil(removed); - - _classesSubscription = classesChanged.Select(_ => Unit.Default) - .StartWith(Unit.Default) + _classesSubscription = ((IObservable)control.Classes.GetWeakCollectionChangedObservable()) + .StartWith(null) .Subscribe(_ => { if (control.Classes.Count > 0) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index a1cd01c78b..f9fb0d18ef 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,12 +1,11 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Styling; using Avalonia.VisualTree; +using Avalonia.Reactive; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; @@ -61,9 +60,12 @@ namespace Avalonia.Diagnostics.ViewModels IPopupHostProvider popupHostProvider, string? providerName = null) { - return Observable.FromEvent( - x => popupHostProvider.PopupHostChanged += x, - x => popupHostProvider.PopupHostChanged -= x) + return Observable.Create(observer => + { + void Handler(IPopupHost? args) => observer.OnNext(args); + popupHostProvider.PopupHostChanged += Handler; + return Disposable.Create(() => popupHostProvider.PopupHostChanged -= Handler); + }) .StartWith(popupHostProvider.PopupHost) .Select(popupHost => { @@ -80,29 +82,39 @@ namespace Avalonia.Diagnostics.ViewModels { Popup p => GetPopupHostObservable(p), Control c => Observable.CombineLatest( - c.GetObservable(Control.ContextFlyoutProperty), - c.GetObservable(Control.ContextMenuProperty), - c.GetObservable(FlyoutBase.AttachedFlyoutProperty), - c.GetObservable(ToolTipDiagnostics.ToolTipProperty), - c.GetObservable(Button.FlyoutProperty), - (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip, ButtonFlyout) => + new IObservable[] + { + c.GetObservable(Control.ContextFlyoutProperty), + c.GetObservable(Control.ContextMenuProperty), + c.GetObservable(FlyoutBase.AttachedFlyoutProperty), + c.GetObservable(ToolTipDiagnostics.ToolTipProperty), + c.GetObservable(Button.FlyoutProperty) + }) + .Select( + items => { - if (ContextMenu != null) + var contextFlyout = items[0]; + var contextMenu = (ContextMenu?)items[1]; + var attachedFlyout = items[2]; + var toolTip = items[3]; + var buttonFlyout = items[4]; + + if (contextMenu != null) //Note: ContextMenus are special since all the items are added as visual children. //So we don't need to go via Popup - return Observable.Return(new PopupRoot(ContextMenu)); + return Observable.Return(new PopupRoot(contextMenu)); - if (ContextFlyout != null) - return GetPopupHostObservable(ContextFlyout, "ContextFlyout"); + if (contextFlyout != null) + return GetPopupHostObservable(contextFlyout, "ContextFlyout"); - if (AttachedFlyout != null) - return GetPopupHostObservable(AttachedFlyout, "AttachedFlyout"); + if (attachedFlyout != null) + return GetPopupHostObservable(attachedFlyout, "AttachedFlyout"); - if (ToolTip != null) - return GetPopupHostObservable(ToolTip, "ToolTip"); + if (toolTip != null) + return GetPopupHostObservable(toolTip, "ToolTip"); - if (ButtonFlyout != null) - return GetPopupHostObservable(ButtonFlyout, "Flyout"); + if (buttonFlyout != null) + return GetPopupHostObservable(buttonFlyout, "Flyout"); return Observable.Return(null); }) @@ -188,7 +200,7 @@ namespace Avalonia.Diagnostics.ViewModels } nodes.Add(new VisualTreeNode(window, Owner)); } - _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + _subscriptions = new CompositeDisposable(2) { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs index c81e3cadf4..56d8737d79 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Diagnostics.Controls; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 13b8cf5e8a..dbc4c98f78 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; @@ -13,13 +12,13 @@ using Avalonia.Markup.Xaml; using Avalonia.Styling; using Avalonia.Themes.Simple; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { internal class MainWindow : Window, IStyleHost { - private readonly IDisposable? _keySubscription; - private readonly IDisposable? _pointerSubscription; + private readonly IDisposable? _inputSubscription; private readonly Dictionary _frozenPopupStates; private AvaloniaObject? _root; private PixelPoint _lastPointerPosition; @@ -33,15 +32,19 @@ namespace Avalonia.Diagnostics.Views if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme) Theme = windowTheme; - _keySubscription = InputManager.Instance?.Process - .OfType() - .Where(x => x.Type == RawKeyEventType.KeyDown) - .Subscribe(RawKeyDown); - _pointerSubscription = InputManager.Instance?.Process - .OfType() - .Subscribe(x => _lastPointerPosition = ((Visual)x.Root).PointToScreen(x.Position)); - - + _inputSubscription = InputManager.Instance?.Process + .Subscribe(x => + { + if (x is RawPointerEventArgs pointerEventArgs) + { + _lastPointerPosition = ((Visual)x.Root).PointToScreen(pointerEventArgs.Position); + } + else if (x is RawKeyEventArgs keyEventArgs && keyEventArgs.Type == RawKeyEventType.KeyDown) + { + RawKeyDown(keyEventArgs); + } + }); + _frozenPopupStates = new Dictionary(); EventHandler? lh = default; @@ -94,8 +97,7 @@ namespace Avalonia.Diagnostics.Views protected override void OnClosed(EventArgs e) { base.OnClosed(e); - _keySubscription?.Dispose(); - _pointerSubscription?.Dispose(); + _inputSubscription?.Dispose(); foreach (var state in _frozenPopupStates) { diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 7d0072c6ea..df67c06b73 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Runtime.InteropServices; using Avalonia.Collections; using Avalonia.Controls; @@ -117,13 +116,11 @@ namespace Avalonia.Dialogs.Internal ?? new ManagedFileChooserSources(); var sub1 = AvaloniaLocator.Current - .GetService() + .GetRequiredService() .Listen(ManagedFileChooserSources.MountedVolumes); - var sub2 = Observable.FromEventPattern(ManagedFileChooserSources.MountedVolumes, - nameof(ManagedFileChooserSources.MountedVolumes.CollectionChanged)) - .ObserveOn(AvaloniaScheduler.Instance) - .Subscribe(x => RefreshQuickLinks(quickSources)); + var sub2 = ManagedFileChooserSources.MountedVolumes.GetWeakCollectionChangedObservable() + .Subscribe(x => Dispatcher.UIThread.Post(() => RefreshQuickLinks(quickSources))); _disposables.Add(sub1); _disposables.Add(sub2); diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 791431dfa7..0f499c6066 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Reactive.Concurrency; using System.Reflection; using System.Threading.Tasks; using Avalonia.Input; diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs index 3a1c65e7c9..7e22988270 100644 --- a/src/Avalonia.FreeDesktop/DBusMenu.cs +++ b/src/Avalonia.FreeDesktop/DBusMenu.cs @@ -36,10 +36,10 @@ namespace Avalonia.FreeDesktop.DBusMenu [Dictionary] class DBusMenuProperties { - public uint Version { get; set; } = default (uint); - public string? TextDirection { get; set; } = default (string); - public string? Status { get; set; } = default (string); - public string[]? IconThemePath { get; set; } = default (string[]); + public uint Version { get; set; } = default; + public string? TextDirection { get; set; } = default; + public string? Status { get; set; } = default; + public string[]? IconThemePath { get; set; } = default; } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 657e324010..560d6fc252 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index e37067d05c..b44762161b 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -2,7 +2,7 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs index 34c1506a67..9f6b056d30 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using Avalonia.Controls.Platform; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using System.Text.RegularExpressions; using System.Runtime.InteropServices; using System.Text; +using Avalonia.Threading; namespace Avalonia.FreeDesktop { @@ -17,21 +18,17 @@ namespace Avalonia.FreeDesktop private const string DevByLabelDir = "/dev/disk/by-label/"; private const string ProcPartitionsDir = "/proc/partitions"; private const string ProcMountsDir = "/proc/mounts"; - private CompositeDisposable _disposables; + private IDisposable _disposable; private ObservableCollection _targetObs; private bool _beenDisposed = false; public LinuxMountedVolumeInfoListener(ref ObservableCollection target) { - _disposables = new CompositeDisposable(); this._targetObs = target; - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } private static string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); @@ -43,7 +40,7 @@ namespace Avalonia.FreeDesktop private static string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); - private void Poll(long _) + private bool Poll() { var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) .Skip(1) @@ -77,13 +74,14 @@ namespace Avalonia.FreeDesktop var mountVolInfos = q1.ToArray(); if (_targetObs.SequenceEqual(mountVolInfos)) - return; + return true; else { _targetObs.Clear(); foreach (var i in mountVolInfos) _targetObs.Add(i); + return true; } } @@ -93,7 +91,7 @@ namespace Avalonia.FreeDesktop { if (disposing) { - _disposables.Dispose(); + _disposable.Dispose(); _targetObs.Clear(); } diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index e7cba13ce0..301a23b608 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs index b233b46dd0..046e4645e3 100644 --- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading; using Avalonia.Platform; using Avalonia.Threading; diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index e6a5a371d0..515835a3bd 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; namespace Avalonia.Native.Interop diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index de3be6142e..424d2c7310 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; using Avalonia.Media.Imaging; using Avalonia.Platform.Interop; diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index 1907a3d129..6f45baeec2 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -2,15 +2,16 @@ using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using Avalonia.Controls.Platform; +using Avalonia.Threading; namespace Avalonia.Native { internal class MacOSMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; + private readonly IDisposable _disposable; private bool _beenDisposed = false; private ObservableCollection mountedDrives; @@ -18,17 +19,12 @@ namespace Avalonia.Native { this.mountedDrives = mountedDrives; - _disposables = new CompositeDisposable(); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); - - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } - private void Poll(long _) + private bool Poll() { var mountVolInfos = Directory.GetDirectories("/Volumes/") .Where(p=> p != null) @@ -41,13 +37,14 @@ namespace Avalonia.Native .ToArray(); if (mountedDrives.SequenceEqual(mountVolInfos)) - return; + return true; else { mountedDrives.Clear(); foreach (var i in mountVolInfos) mountedDrives.Add(i); + return true; } } diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index a9136c2c99..9b38c150fa 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -13,4 +13,9 @@ + + + + + diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 4d75a776c3..0b4b7d26a1 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading; using Avalonia.Platform; +using Avalonia.Reactive; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index eea2587587..c67d7674a8 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; +using Avalonia.Reactive; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs index fbfaf1bd3d..5573eb39fa 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl; @@ -132,4 +131,4 @@ class EglConfigInfo SampleCount = sampleCount; StencilSize = stencilSize; } -} \ No newline at end of file +} diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index a913c05996..06aafb4f57 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -35,7 +35,7 @@ namespace Avalonia.OpenGL.Egl static Func Load(string library) { - var dyn = AvaloniaLocator.Current.GetService(); + var dyn = AvaloniaLocator.Current.GetRequiredService(); var lib = dyn.LoadLibrary(library); return (s) => dyn.GetProcAddress(lib, s, true); } diff --git a/src/Avalonia.ReactiveUI/Attributes.cs b/src/Avalonia.ReactiveUI/Attributes.cs index 31c374392f..1b85cd2f47 100644 --- a/src/Avalonia.ReactiveUI/Attributes.cs +++ b/src/Avalonia.ReactiveUI/Attributes.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using Avalonia.Metadata; [assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")] \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs b/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs index bd21503a58..ae9a8787bc 100644 --- a/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs +++ b/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs @@ -2,10 +2,10 @@ using Avalonia; using Avalonia.VisualTree; using Avalonia.Controls; using System.Threading; +using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Reactive.Linq; -using System.Reactive; using ReactiveUI; using System; using Avalonia.Controls.ApplicationLifetimes; diff --git a/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs b/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs new file mode 100644 index 0000000000..4cd329beb6 --- /dev/null +++ b/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs @@ -0,0 +1,110 @@ +using System.Reactive; +using System.Reactive.Subjects; +using Avalonia.Data; + +namespace Avalonia.ReactiveUI; + +public static class AvaloniaObjectReactiveExtensions +{ + /// + /// Gets a subject for an . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), + o.GetObservable(property)); + } + + /// + /// Gets a subject for an . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), + o.GetObservable(property)); + } + + /// + /// Gets a subject for a . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject> GetBindingSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create>( + Observer.Create>(x => + { + if (x.HasValue) + { + o.SetValue(property, x.Value, priority); + } + }), + o.GetBindingObservable(property)); + } + + /// + /// Gets a subject for a . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject> GetBindingSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create>( + Observer.Create>(x => + { + if (x.HasValue) + { + o.SetValue(property, x.Value, priority); + } + }), + o.GetBindingObservable(property)); + } +} diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.ReactiveUI/AvaloniaScheduler.cs similarity index 98% rename from src/Avalonia.Base/Threading/AvaloniaScheduler.cs rename to src/Avalonia.ReactiveUI/AvaloniaScheduler.cs index 6423d86e7c..61c503e7a5 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.ReactiveUI/AvaloniaScheduler.cs @@ -1,8 +1,9 @@ using System; using System.Reactive.Concurrency; using System.Reactive.Disposables; +using Avalonia.Threading; -namespace Avalonia.Threading +namespace Avalonia.ReactiveUI { /// /// A reactive scheduler that uses Avalonia's . diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index ab543703d9..fb52b3b147 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index 9abcf5d32b..28f8649e2d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -50,7 +50,8 @@ Opacity="0" Fill="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" Stretch="Uniform" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + FlowDirection="LeftToRight" /> @@ -146,7 +147,6 @@ - diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj index 1dd6426b39..4aa6b66743 100644 --- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj +++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index c762349c1c..061c516ef6 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -10,7 +10,12 @@ - - - + + + + + + + + diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index def5228e94..88006f0b47 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL; +using Avalonia.Reactive; namespace Avalonia.X11.Glx { class GlxContext : IGlContext diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs index 94f930e9ec..a9fedff8b5 100644 --- a/src/Avalonia.X11/X11Framebuffer.cs +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -25,7 +25,7 @@ namespace Avalonia.X11 RowBytes = width * 4; Dpi = new Vector(96, 96) * factor; Format = PixelFormat.Bgra8888; - _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * height); + _blob = AvaloniaLocator.Current.GetRequiredService().AllocBlob(RowBytes * height); Address = _blob.Address; } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 9692e0a384..0f9ef72a3f 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -105,7 +105,7 @@ namespace Avalonia.X11 var gl = AvaloniaLocator.Current.GetService(); if (options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl); + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); else RenderInterface = new(gl); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 810a806c8a..5bbcf3af8c 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Avalonia.Reactive; using System.Text; using System.Threading.Tasks; using System.Threading; diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index a2f1b55b6f..a9cad0538f 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs index 2cc74273c0..68efea31d6 100644 --- a/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs @@ -8,9 +8,10 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using BrowserView = Avalonia.Browser.AvaloniaView; +[assembly: SupportedOSPlatform("browser")] + namespace Avalonia.Browser.Blazor; -[SupportedOSPlatform("browser")] public class AvaloniaView : ComponentBase { private Browser.AvaloniaView? _browserView; diff --git a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs index 5e101f7f17..a7bb5a62df 100644 --- a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs @@ -5,7 +5,6 @@ using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Browser.Blazor; -[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static AppBuilder SetupWithSingleViewLifetime( diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj index 014d387cb2..2fc3cc885a 100644 --- a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj +++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj @@ -1,7 +1,6 @@ net7.0 - preview enable true diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs index abda618b0d..294216ee03 100644 --- a/src/Browser/Avalonia.Browser/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -18,7 +18,6 @@ using SkiaSharp; namespace Avalonia.Browser { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings public partial class AvaloniaView : ITextInputMethodImpl { private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); @@ -107,7 +106,7 @@ namespace Avalonia.Browser _dpi = DomHelper.ObserveDpi(OnDpiChanged); - _useGL = AvaloniaLocator.Current.GetRequiredService() != null; + _useGL = AvaloniaLocator.Current.GetService() != null; if (_useGL) { diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index 3adcb8e539..add69760ee 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -5,7 +5,6 @@ using System.Runtime.Versioning; namespace Avalonia.Browser; -[SupportedOSPlatform("browser")] public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime { public AvaloniaView? View; @@ -22,7 +21,6 @@ public class BrowserPlatformOptions public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}"); } -[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static AppBuilder SetupBrowserApp( diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 85725e4cf3..4e6be3268b 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.Versioning; using Avalonia.Browser.Skia; using Avalonia.Browser.Storage; using Avalonia.Controls; @@ -14,9 +15,10 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; +[assembly: SupportedOSPlatform("browser")] + namespace Avalonia.Browser { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { private Size _clientSize; @@ -201,7 +203,11 @@ namespace Avalonia.Browser public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { - + if (transparencyLevel == WindowTransparencyLevel.None + || transparencyLevel == WindowTransparencyLevel.Transparent) + { + TransparencyLevel = transparencyLevel; + } } public Size ClientSize => _clientSize; @@ -221,7 +227,7 @@ namespace Avalonia.Browser public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard; - public WindowTransparencyLevel TransparencyLevel { get; } + public WindowTransparencyLevel TransparencyLevel { get; private set; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } public ITextInputMethodImpl TextInputMethod => _avaloniaView; diff --git a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs index 8321b00658..27a2b1dcb7 100644 --- a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs @@ -6,7 +6,6 @@ namespace Avalonia.Browser.Interop; internal record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); -[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings internal static partial class CanvasHelper { diff --git a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index c44af810d1..e50f8790ef 100644 --- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -45,7 +45,7 @@ internal static partial class StorageHelper [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs>] - public static partial Task GetItems(JSObject item); + public static partial Task GetItems(JSObject item); [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)] public static partial JSObject[] ItemsArray(JSObject item); diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs index e56efdb4a8..4d3d8dcd97 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.Platform; using Avalonia.Skia; +using Avalonia.Reactive; namespace Avalonia.Browser.Skia { diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs index f69dd3c344..9424122ce8 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs @@ -14,14 +14,12 @@ namespace Avalonia.Browser.Skia _size = browserSkiaSurface.Size; var glFbInfo = new GRGlFramebufferInfo(browserSkiaSurface.GlInfo.FboId, browserSkiaSurface.ColorType.ToGlSizedFormat()); - { - _browserSkiaSurface = browserSkiaSurface; - _renderTarget = new GRBackendRenderTarget( - (int)(browserSkiaSurface.Size.Width * browserSkiaSurface.Scaling), - (int)(browserSkiaSurface.Size.Height * browserSkiaSurface.Scaling), - browserSkiaSurface.GlInfo.Samples, - browserSkiaSurface.GlInfo.Stencils, glFbInfo); - } + _browserSkiaSurface = browserSkiaSurface; + _renderTarget = new GRBackendRenderTarget( + (int)(browserSkiaSurface.Size.Width * browserSkiaSurface.Scaling), + (int)(browserSkiaSurface.Size.Height * browserSkiaSurface.Scaling), + browserSkiaSurface.GlInfo.Samples, + browserSkiaSurface.GlInfo.Stencils, glFbInfo); } public void Dispose() diff --git a/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs index 4fce190346..3404452bca 100644 --- a/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs @@ -7,7 +7,6 @@ using Avalonia.Browser.Interop; namespace Avalonia.Browser.Storage; -[System.Runtime.Versioning.SupportedOSPlatform("browser")] internal class BlobReadableStream : Stream { private JSObject? _jSReference; diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index 28de55092b..dc580f0fcb 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -13,7 +13,6 @@ namespace Avalonia.Browser.Storage; internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); -[SupportedOSPlatform("browser")] internal class BrowserStorageProvider : IStorageProvider { internal const string PickerCancelMessage = "The user aborted a request"; diff --git a/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs index f29f7420ac..b2f14b6a7a 100644 --- a/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs @@ -7,7 +7,6 @@ using Avalonia.Browser.Interop; namespace Avalonia.Browser.Storage; -[System.Runtime.Versioning.SupportedOSPlatform("browser")] // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream internal sealed class WriteableStream : Stream { diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts index 385cdd4c41..e8e37faf58 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts @@ -17,7 +17,6 @@ export class AvaloniaDOM { const canvas = document.createElement("canvas"); canvas.id = `canvas${randomIdPart}`; canvas.classList.add("avalonia-canvas"); - canvas.style.backgroundColor = "#ccc"; canvas.style.width = "100%"; canvas.style.position = "absolute"; diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json index 06e94629d7..2d875e84db 100644 --- a/src/Browser/Avalonia.Browser/webapp/package-lock.json +++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json @@ -2162,9 +2162,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -4699,9 +4699,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index ac54365f51..6032f3a92c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -25,7 +25,7 @@ namespace Avalonia.LinuxFramebuffer Surfaces = new object[] { _outputBackend }; - Invalidate(default(Rect)); + Invalidate(default); _inputBackend.Initialize(this, e => Input?.Invoke(e)); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index 2bb91e8a32..e3d154ffdd 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -179,7 +179,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } finally { - if( _sreCanSave) + if(!success && _sreCanSave) DumpRuntimeCompilationResults(); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 267bd2520e..da739c754c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -66,7 +66,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs index dcd60f7a79..fc6a8557f3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs @@ -24,7 +24,7 @@ namespace Avalonia.Markup.Xaml.Converters if(uri.IsAbsoluteUri && uri.IsFile) return new Bitmap(uri.LocalPath); - var assets = AvaloniaLocator.Current.GetService(); + var assets = AvaloniaLocator.Current.GetRequiredService(); return new Bitmap(assets.Open(uri, context.GetContextBaseUri())); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs index 24b690b6f1..698f5a9327 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs @@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.Converters if(uri.IsAbsoluteUri && uri.IsFile) return new WindowIcon(uri.LocalPath); - var assets = AvaloniaLocator.Current.GetService(); + var assets = AvaloniaLocator.Current.GetRequiredService(); return new WindowIcon(assets.Open(uri, context.GetContextBaseUri())); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs index 45e23db84f..170a31fd64 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Data.Core; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs index 77ffa24687..cb21df0507 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; using System.Text; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index 81f1224650..abb166a92b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -5,6 +5,7 @@ using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs index 8489dd9d19..7c9f7559c6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs @@ -1,10 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { @@ -30,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case TaskStatus.Faulted: return HandleCompleted(task); default: - var subject = new Subject(); + var subject = new LightweightSubject(); task.ContinueWith( x => HandleCompleted(task).Subscribe(subject), TaskScheduler.FromCurrentSynchronizationContext()) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 07c79d7077..23166170db 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -7,7 +7,6 @@ namespace Avalonia.Markup.Xaml.Templates public static class TemplateContent { public static ControlTemplateResult Load(object templateContent) - { if (templateContent is Func direct) { diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 753d577105..e3878b5bc6 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 6f836a799a..66907f33d0 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -1,15 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Controls; -using Avalonia.Data.Converters; using Avalonia.Data.Core; -using Avalonia.LogicalTree; using Avalonia.Markup.Parsers; -using Avalonia.Reactive; -using Avalonia.VisualTree; namespace Avalonia.Data { diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index c035f0b05d..90f312a249 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -1,9 +1,5 @@ - using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data.Converters; using Avalonia.Data.Core; @@ -247,6 +243,7 @@ namespace Avalonia.Data // Content property is bound to a value which becomes the ContentPresenter's // DataContext - it is from this that the child hosted by the ContentPresenter needs to // inherit its DataContext. + return target.GetObservable(Visual.VisualParentProperty) .Select(x => { @@ -255,7 +252,7 @@ namespace Avalonia.Data }).Switch(); } - private class UpdateSignal : SingleSubscriberObservableBase + private class UpdateSignal : SingleSubscriberObservableBase { private readonly AvaloniaObject _target; private readonly AvaloniaProperty _property; @@ -280,7 +277,7 @@ namespace Avalonia.Data { if (e.Property == _property) { - PublishNext(Unit.Default); + PublishNext(default); } } } diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 4693b0c617..1515ff2c90 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Data.Converters; using Avalonia.Metadata; diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 4dcdfb3c0e..4270063f87 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Reactive.Subjects; using Avalonia.Data.Converters; using Avalonia.Reactive; using Avalonia.Styling; @@ -13,7 +12,7 @@ namespace Avalonia.Data public class TemplateBinding : SingleSubscriberObservableBase, IBinding, IDescription, - ISubject, + IAvaloniaSubject, ISetterValue { private bool _isSetterValue; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index 6e6a163989..00f40dfcd3 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Utilities; @@ -64,7 +63,7 @@ namespace Avalonia.Markup.Parsers public static ExpressionObserver Build( Func rootGetter, string expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null, Func? typeResolver = null) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index f9a3a61736..4fc17e440b 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs index 697612ca12..ffbd34d492 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Data.Core; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs index c420a9df8d..ba1c1edffe 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs @@ -5,7 +5,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Reflection; using Avalonia.Data; using Avalonia.Data.Core; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 4d6d16a3ce..dbf37f0900 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -552,7 +552,8 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class; + return obj is ClassSyntax syntax && + syntax.Class == Class; } } @@ -562,7 +563,8 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is NameSyntax && ((NameSyntax)obj).Name == Name; + return obj is NameSyntax syntax && + syntax.Name == Name; } } @@ -574,9 +576,9 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is PropertySyntax && - ((PropertySyntax)obj).Property == Property && - ((PropertySyntax)obj).Value == Value; + return obj is PropertySyntax syntax && + syntax.Property == Property && + syntax.Value == Value; } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 48e8c761eb..8b71f4e17e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -224,9 +224,9 @@ namespace Avalonia.Skia var impl = (GeometryImpl) geometry; var size = geometry.Bounds.Size; - using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper)) + using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default) using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, - size.Inflate(new Thickness(pen?.Thickness / 2 ?? 0))) : default(PaintWrapper)) + size.Inflate(new Thickness(pen?.Thickness / 2 ?? 0))) : default) { if (fill.Paint != null) { diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 1ae47c8b7a..05fad25f1b 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 1141763097..15a3ebff40 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -245,7 +245,7 @@ namespace Avalonia.Skia { CachedStrokePath?.Dispose(); CachedGeometryRenderBounds = default; - _cachedStrokeWidth = default(float); + _cachedStrokeWidth = default; } } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 5bf1272c2f..4b3c7a016d 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.OpenGL; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index 5ca7f40d17..f7a86c11ff 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -37,7 +37,7 @@ namespace Avalonia.Skia var typeFaceCollection = new SKTypefaceCollection(); - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); foreach (var asset in fontAssets) { diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index a7998353d9..e5fb182a3b 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index dcb267b2a3..9864a14a9c 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -96,9 +96,9 @@ namespace Avalonia.Skia SKColorType colorType = format.ToSkColorType(); SKAlphaType alphaType = alphaFormat.ToSkAlphaType(); - - var runtimePlatform = AvaloniaLocator.Current?.GetService(); - + + var runtimePlatform = AvaloniaLocator.Current.GetService(); + if (runtimePlatform != null) { _bitmap = new SKBitmap(); diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index 5fecaef100..32bcdba758 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs index e5f87e71a2..4663a6561f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs @@ -22,7 +22,7 @@ namespace Avalonia.Direct2D1.Media { var factory1 = factory; - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); foreach (var asset in fontAssets) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 180ae491b3..3f2298eb22 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -409,7 +409,7 @@ namespace Avalonia.Direct2D1.Media } else { - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index a5474803a1..f900204504 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -127,7 +127,7 @@ namespace Avalonia.Win32.Interop.Wpf DeviceWindowHandle = GetDesktopWindow(), PresentationInterval = PresentInterval.Default }; - s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetService() + s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetRequiredService() .QueryInterface(); s_d3DDevice = new DeviceEx(s_d3DContext, 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, presentparams); @@ -156,7 +156,7 @@ namespace Avalonia.Win32.Interop.Wpf _impl.ImageSource = _image; RemoveAndDispose(ref _backBuffer); - if (size == default(IntSize)) + if (size == default) { _image.Lock(); _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero); @@ -171,7 +171,7 @@ namespace Avalonia.Win32.Interop.Wpf static void RemoveAndDispose(ref T d) where T : IDisposable { d?.Dispose(); - d = default(T); + d = default; } void Swap() diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs index b8648dfc50..3bfbf4bd92 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs @@ -28,7 +28,7 @@ namespace Avalonia.Win32.Interop.Wpf public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; - return obj is IntSize && Equals((IntSize) obj); + return obj is IntSize size && Equals(size); } public override int GetHashCode() diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 6153c7c75c..82fd1109f4 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Input; diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index b7a567ea97..7d22e57a30 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -205,7 +205,7 @@ namespace Avalonia.Win32 if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) return DV_E_FORMATETC; - * medium = default(Interop.STGMEDIUM); + * medium = default; medium->tymed = TYMED.TYMED_HGLOBAL; return WriteDataToHGlobal(fmt, ref medium->unionmember); } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index b4eb6d8851..8bbdfe5fd9 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -121,7 +121,7 @@ namespace Avalonia.Win32 _dragDevice, RawDragEventType.DragLeave, _target, - default(Point), + default, null, DragDropEffects.None, RawInputModifiers.None diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs index d535efc5b7..b4002d8bc7 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.OpenGl.WglConsts; diff --git a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs index 2b8bf6f8bf..fdf36206d0 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Platform; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 585a9cdf19..e3d0cd9e5f 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index 32019f4c15..5be25a3d4b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -1,8 +1,8 @@ using System; using System.Numerics; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL.Egl; +using Avalonia.Reactive; using Avalonia.Win32.Interop; using MicroCom.Runtime; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index d393bd304a..fb27ab7856 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -109,7 +109,7 @@ namespace Avalonia.Win32 if (_owner is Window window) { - var visual = window.Renderer.HitTestFirst(position, (Visual)_owner, x => + var visual = window.Renderer.HitTestFirst(position, window, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index ba1bfda949..94e9c0c814 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -2,33 +2,30 @@ using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using Avalonia.Controls.Platform; using Avalonia.Logging; +using Avalonia.Threading; namespace Avalonia.Win32 { internal class WindowsMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; + private readonly IDisposable _disposable; private bool _beenDisposed = false; private ObservableCollection mountedDrives; public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) { this.mountedDrives = mountedDrives; - _disposables = new CompositeDisposable(); - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } - private void Poll(long _) + private bool Poll() { var allDrives = DriveInfo.GetDrives(); @@ -56,13 +53,14 @@ namespace Avalonia.Win32 .ToArray(); if (mountedDrives.SequenceEqual(mountVolInfos)) - return; + return true; else { mountedDrives.Clear(); foreach (var i in mountVolInfos) mountedDrives.Add(i); + return true; } } @@ -72,7 +70,7 @@ namespace Avalonia.Win32 { if (disposing) { - + _disposable.Dispose(); } _beenDisposed = true; } diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs index 7a5e1a496d..8f7c1f3308 100644 --- a/src/iOS/Avalonia.iOS/EaglDisplay.cs +++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Reactive; using OpenGLES; namespace Avalonia.iOS diff --git a/src/tools/DevAnalyzers/DevAnalyzers.csproj b/src/tools/DevAnalyzers/DevAnalyzers.csproj index 53e3e74e76..e5c2fc6cf6 100644 --- a/src/tools/DevAnalyzers/DevAnalyzers.csproj +++ b/src/tools/DevAnalyzers/DevAnalyzers.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 enable diff --git a/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs b/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs index 8ecd9119f6..ecd6154e0b 100644 --- a/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs +++ b/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs @@ -16,7 +16,7 @@ public class GenericVirtualAnalyzer : DiagnosticAnalyzer "Performance", DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Generic virtual methods affect JIT startup time adversly and should be avoided."); + description: "Generic virtual methods affect JIT startup time adversely and should be avoided."); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index f8d5ad826e..3b5d3d8c3f 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -257,7 +257,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator if (cl.Properties.Count > 0) { server = server.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration( - $"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt){{}}") + $"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt){{}}") !) .WithBody(ApplyDeserializeChangesEpilogue(deserializeMethodBody, cl))); server = server.AddMembers(MethodDeclaration(ParseTypeName("void"), "OnFieldsDeserialized") @@ -481,7 +481,7 @@ return; private static BlockSyntax DeserializeChangesPrologue(GClass cl) { return Block(ParseStatement($@" -base.DeserializeChangesCore(reader, commitedAt); +base.DeserializeChangesCore(reader, committedAt); DeserializeChangesExtra(reader); var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); ")); @@ -500,7 +500,7 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); { code = $@" if((changed & {changedFieldsType}.{prop.Name}Animated) == {changedFieldsType}.{prop.Name}Animated) - SetAnimatedValue({CompositionPropertyField(prop)}, ref {PropertyBackingFieldName(prop)}, commitedAt, reader.ReadObject()); + SetAnimatedValue({CompositionPropertyField(prop)}, ref {PropertyBackingFieldName(prop)}, committedAt, reader.ReadObject()); else "; } diff --git a/src/tools/DevGenerators/DevGenerators.csproj b/src/tools/DevGenerators/DevGenerators.csproj index 069ff159fc..30da940514 100644 --- a/src/tools/DevGenerators/DevGenerators.csproj +++ b/src/tools/DevGenerators/DevGenerators.csproj @@ -4,7 +4,6 @@ netstandard2.0 enable false - 10 diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 0162f53f5e..4cdce8df26 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -3,7 +3,6 @@ net6.0 Library true - latest diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 547b2cb176..030d6ba215 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1036,7 +1036,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public async Task Bind_With_Scheduler_Executes_On_Scheduler() + public async Task Bind_With_Scheduler_Executes_On_UI_Thread() { var target = new Class1(); var source = new Subject(); @@ -1047,7 +1047,6 @@ namespace Avalonia.Base.UnitTests .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); var services = new TestServices( - scheduler: AvaloniaScheduler.Instance, threadingInterface: threadingInterfaceMock.Object); using (UnitTestApplication.Start(services)) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs index 435980bd9a..612f3ff80a 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs @@ -41,7 +41,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Completes() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -54,7 +54,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Errors() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -87,7 +87,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Unsubscribe_From_Update_Observable() { var scheduler = new TestScheduler(); - var update = scheduler.CreateColdObservable(); + var update = scheduler.CreateColdObservable(); var data = new { Foo = "foo" }; var target = ExpressionObserver.Create(() => data, o => o.Foo, update); var result = new List(); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 144d9e6668..22d5645f76 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -359,14 +359,14 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Empty_Expression_Should_Track_Root() { var data = new Class1 { Foo = "foo" }; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => data.Foo, o => o, update); var result = new List(); target.Subscribe(x => result.Add(x)); data.Foo = "bar"; - update.OnNext(Unit.Default); + update.OnNext(default); Assert.Equal(new[] { "foo", "bar" }, result); @@ -533,15 +533,15 @@ namespace Avalonia.Base.UnitTests.Data.Core var first = new Class1 { Foo = "foo" }; var second = new Class1 { Foo = "bar" }; var root = first; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => root, o => o.Foo, update); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); root = second; - update.OnNext(Unit.Default); + update.OnNext(default); root = null; - update.OnNext(Unit.Default); + update.OnNext(default); Assert.Equal( new object[] @@ -640,7 +640,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void RootGetter_Is_Reevaluated_On_Subscribe() { var data = "foo"; - var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); + var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); diff --git a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index aa042ffec8..afc25ab88e 100644 --- a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -97,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Media.Fonts { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var fontFamily = new FontFamily("resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests#Noto Mono"); diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs index 4e0207a85d..c57bd6c002 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs @@ -42,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting enumerator.MoveNext(); - var actual = enumerator.Current.Text; + var actual = text.AsSpan(enumerator.Current.Offset, enumerator.Current.Length); var pass = true; @@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting while (enumerator.MoveNext()) { - Assert.Equal(1, enumerator.Current.Text.Length); + Assert.Equal(1, enumerator.Current.Length); count++; } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 27faf1e13c..3d8369faeb 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -82,6 +82,39 @@ public class CompositorHitTestingTests : CompositorTestsBase } } + [Theory, + InlineData(false, false), + InlineData(true, false), + InlineData(false, true), + InlineData(true, true), + ] + public void HitTest_Should_Find_Zero_Opacity_Controls_At_Point(bool parent, bool child) + { + + using (var s = new CompositorServices(new Size(200, 200))) + { + Border visible, border; + s.TopLevel.Content = border = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Opacity = parent ? 0 : 1, + Child = visible = new Border + { + Opacity = child ? 0 : 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + } + }; + + s.AssertHitTest(new Point(100, 100), null, visible, border); + } + } + [Fact] public void HitTest_Should_Not_Find_Control_Outside_Point() { diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs index e69eae43f0..9f7d9854fd 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs @@ -3,6 +3,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Reactive; using Avalonia.Styling.Activators; +using Observable = Avalonia.Reactive.Observable; namespace Avalonia.Base.UnitTests.Styling { @@ -10,12 +11,12 @@ namespace Avalonia.Base.UnitTests.Styling { public static IDisposable Subscribe(this IStyleActivator activator, Action action) { - return activator.ToObservable().Subscribe(action); + return Observable.Subscribe(activator.ToObservable(), action); } public static async Task Take(this IStyleActivator activator, int value) { - return await activator.ToObservable().Take(value); + return await System.Reactive.Linq.Observable.Take(activator.ToObservable(), value); } public static IObservable ToObservable(this IStyleActivator activator) diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs new file mode 100644 index 0000000000..e1b3c6a99c --- /dev/null +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -0,0 +1,62 @@ +using System; +using Avalonia.Media; +using Avalonia.Media.TextFormatting; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Text; + +[MemoryDiagnoser] +public class HugeTextLayout : IDisposable +{ + private readonly IDisposable _app; + + public HugeTextLayout() + { + _app = UnitTestApplication.Start( + TestServices.StyledWindow.With( + renderInterface: new NullRenderingPlatform(), + threadingInterface: new NullThreadingPlatform(), + standardCursorFactory: new NullCursorFactory())); + } + + private const string Text = @"Though, the objectives of the development of the prominent landmarks can be neglected in most cases, it should be realized that after the completion of the strategic decision gives rise to The Expertise of Regular Program (Carlton Cartwright in The Book of the Key Factor) +A number of key issues arise from the belief that the explicit examination of strategic management should correlate with the conceptual design. +By all means, the unification of the reliably developed techniques indicates the importance of the ultimate advantage of episodic skill over alternate practices. +Let's consider, that the portion of the continuing support can be regarded as relentlessly insignificant. The hardware maintenance focuses our attention on the structure absorption. The real reason of the permanent growth drastically the preliminary action plan the ultimate advantage of useful probability over alternate practices. +Let it not be said that a section of the essential component discards the principle of the more interconnection of critical thinking with productivity boosting of the referential arguments. +One should, however, not forget that concentration of violations of the strategic management requires urgent actions to be taken towards the comprehensive set of policy statements. Therefore, the concept of the design aspects can be treated as the only solution. +In a loose sense concentration of the center of the critical thinking provides a deep insight into the emergency planning. The comparison is quite a powerful matter. +Resulting from review or analysis of threats and opportunities, we can presume that either significant improvement or basics of planning and scheduling reveals the patterns of the final draft. Therefore, the concept of the crucial component can be treated as the only solution. +One should, nevertheless, consider that the exceptional results of the diverse sources of information gives an overview of the production cycle. It should rather be regarded as an integral part of the direct access to key resources. +Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. +In respect that the structure of the sufficient amount poses problems and challenges for both the set of related commands and controls and the ability bias."; + + [Benchmark] + public TextLayout BuildTextLayout() => new TextLayout(Text, Typeface.Default, 12d, Brushes.Black); + + private const string Emojis = @"😀 😁 😂 🤣 😃 😄 😅 😆 😉 😊 😋 😎 😍 😘 🥰 😗 😙 😚 ☺️ 🙂 🤗 🤩 🤔 🤨 😐 😑 😶 🙄 😏 😣 😥 😮 🤐 😯 😪 😫 😴 😌 😛 😜 😝 🤤 😒 😓 😔 😕 🙃 🤑 😲 ☹️ 🙁 😖 😞 😟 😤 😢 😭 😦 😧 😨 😩 🤯 😬 😰 😱 🥵 🥶 😳 🤪 😵 😡 😠 🤬 😷 🤒 🤕 🤢 🤮 🤧 😇 🤠 🤡 🥳 🥴 🥺 🤥 🤫 🤭 🧐 🤓 😈 👿 👹 👺 💀 👻 👽 🤖 💩 😺 😸 😹 😻 😼 😽 🙀 😿 😾 +👶 👧 🧒 👦 👩 🧑 👨 👵 🧓 👴 👲 👳‍♀️ 👳‍♂️ 🧕 🧔 👱‍♂️ 👱‍♀️ 👨‍🦰 👩‍🦰 👨‍🦱 👩‍🦱 👨‍🦲 👩‍🦲 👨‍🦳 👩‍🦳 🦸‍♀️ 🦸‍♂️ 🦹‍♀️ 🦹‍♂️ 👮‍♀️ 👮‍♂️ 👷‍♀️ 👷‍♂️ 💂‍♀️ 💂‍♂️ 🕵️‍♀️ 🕵️‍♂️ 👩‍⚕️ 👨‍⚕️ 👩‍🌾 👨‍🌾 👩‍🍳 👨‍🍳 👩‍🎓 👨‍🎓 👩‍🎤 👨‍🎤 👩‍🏫 👨‍🏫 👩‍🏭 👨‍🏭 👩‍💻 👨‍💻 👩‍💼 👨‍💼 👩‍🔧 👨‍🔧 👩‍🔬 👨‍🔬 👩‍🎨 👨‍🎨 👩‍🚒 👨‍🚒 👩‍✈️ 👨‍✈️ 👩‍🚀 👨‍🚀 👩‍⚖️ 👨‍⚖️ 👰 🤵 👸 🤴 🤶 🎅 🧙‍♀️ 🧙‍♂️ 🧝‍♀️ 🧝‍♂️ 🧛‍♀️ 🧛‍♂️ 🧟‍♀️ 🧟‍♂️ 🧞‍♀️ 🧞‍♂️ 🧜‍♀️ 🧜‍♂️ 🧚‍♀️ 🧚‍♂️ 👼 🤰 🤱 🙇‍♀️ 🙇‍♂️ 💁‍♀️ 💁‍♂️ 🙅‍♀️ 🙅‍♂️ 🙆‍♀️ 🙆‍♂️ 🙋‍♀️ 🙋‍♂️ 🤦‍♀️ 🤦‍♂️ 🤷‍♀️ 🤷‍♂️ 🙎‍♀️ 🙎‍♂️ 🙍‍♀️ 🙍‍♂️ 💇‍♀️ 💇‍♂️ 💆‍♀️ 💆‍♂️ 🧖‍♀️ 🧖‍♂️ 💅 🤳 💃 🕺 👯‍♀️ 👯‍♂️ 🕴 🚶‍♀️ 🚶‍♂️ 🏃‍♀️ 🏃‍♂️ 👫 👭 👬 💑 👩‍❤️‍👩 👨‍❤️‍👨 💏 👩‍❤️‍💋‍👩 👨‍❤️‍💋‍👨 👪 👨‍👩‍👧 👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👩‍👩‍👦 👩‍👩‍👧 👩‍👩‍👧‍👦 👩‍👩‍👦‍👦 👩‍👩‍👧‍👧 👨‍👨‍👦 👨‍👨‍👧 👨‍👨‍👧‍👦 👨‍👨‍👦‍👦 👨‍👨‍👧‍👧 👩‍👦 👩‍👧 👩‍👧‍👦 👩‍👦‍👦 👩‍👧‍👧 👨‍👦 👨‍👧 👨‍👧‍👦 👨‍👦‍👦 👨‍👧‍👧 🤲 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤟 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🦵 🦶 🖕 ✍️ 🙏 💍 💄 💋 👄 👅 👂 👃 👣 👁 👀 🧠 🦴 🦷 🗣 👤 👥 +🧥 👚 👕 👖 👔 👗 👙 👘 👠 👡 👢 👞 👟 🥾 🥿 🧦 🧤 🧣 🎩 🧢 👒 🎓 ⛑ 👑 👝 👛 👜 💼 🎒 👓 🕶 🥽 🥼 🌂 🧵 🧶 +👶🏻 👦🏻 👧🏻 👨🏻 👩🏻 👱🏻‍♀️ 👱🏻 👴🏻 👵🏻 👲🏻 👳🏻‍♀️ 👳🏻 👮🏻‍♀️ 👮🏻 👷🏻‍♀️ 👷🏻 💂🏻‍♀️ 💂🏻 🕵🏻‍♀️ 🕵🏻 👩🏻‍⚕️ 👨🏻‍⚕️ 👩🏻‍🌾 👨🏻‍🌾 👩🏻‍🍳 👨🏻‍🍳 👩🏻‍🎓 👨🏻‍🎓 👩🏻‍🎤 👨🏻‍🎤 👩🏻‍🏫 👨🏻‍🏫 👩🏻‍🏭 👨🏻‍🏭 👩🏻‍💻 👨🏻‍💻 👩🏻‍💼 👨🏻‍💼 👩🏻‍🔧 👨🏻‍🔧 👩🏻‍🔬 👨🏻‍🔬 👩🏻‍🎨 👨🏻‍🎨 👩🏻‍🚒 👨🏻‍🚒 👩🏻‍✈️ 👨🏻‍✈️ 👩🏻‍🚀 👨🏻‍🚀 👩🏻‍⚖️ 👨🏻‍⚖️ 🤶🏻 🎅🏻 👸🏻 🤴🏻 👰🏻 🤵🏻 👼🏻 🤰🏻 🙇🏻‍♀️ 🙇🏻 💁🏻 💁🏻‍♂️ 🙅🏻 🙅🏻‍♂️ 🙆🏻 🙆🏻‍♂️ 🙋🏻 🙋🏻‍♂️ 🤦🏻‍♀️ 🤦🏻‍♂️ 🤷🏻‍♀️ 🤷🏻‍♂️ 🙎🏻 🙎🏻‍♂️ 🙍🏻 🙍🏻‍♂️ 💇🏻 💇🏻‍♂️ 💆🏻 💆🏻‍♂️ 🕴🏻 💃🏻 🕺🏻 🚶🏻‍♀️ 🚶🏻 🏃🏻‍♀️ 🏃🏻 🤲🏻 👐🏻 🙌🏻 👏🏻 🙏🏻 👍🏻 👎🏻 👊🏻 ✊🏻 🤛🏻 🤜🏻 🤞🏻 ✌🏻 🤟🏻 🤘🏻 👌🏻 👈🏻 👉🏻 👆🏻 👇🏻 ☝🏻 ✋🏻 🤚🏻 🖐🏻 🖖🏻 👋🏻 🤙🏻 💪🏻 🖕🏻 ✍🏻 🤳🏻 💅🏻 👂🏻 👃🏻 +👶🏼 👦🏼 👧🏼 👨🏼 👩🏼 👱🏼‍♀️ 👱🏼 👴🏼 👵🏼 👲🏼 👳🏼‍♀️ 👳🏼 👮🏼‍♀️ 👮🏼 👷🏼‍♀️ 👷🏼 💂🏼‍♀️ 💂🏼 🕵🏼‍♀️ 🕵🏼 👩🏼‍⚕️ 👨🏼‍⚕️ 👩🏼‍🌾 👨🏼‍🌾 👩🏼‍🍳 👨🏼‍🍳 👩🏼‍🎓 👨🏼‍🎓 👩🏼‍🎤 👨🏼‍🎤 👩🏼‍🏫 👨🏼‍🏫 👩🏼‍🏭 👨🏼‍🏭 👩🏼‍💻 👨🏼‍💻 👩🏼‍💼 👨🏼‍💼 👩🏼‍🔧 👨🏼‍🔧 👩🏼‍🔬 👨🏼‍🔬 👩🏼‍🎨 👨🏼‍🎨 👩🏼‍🚒 👨🏼‍🚒 👩🏼‍✈️ 👨🏼‍✈️ 👩🏼‍🚀 👨🏼‍🚀 👩🏼‍⚖️ 👨🏼‍⚖️ 🤶🏼 🎅🏼 👸🏼 🤴🏼 👰🏼 🤵🏼 👼🏼 🤰🏼 🙇🏼‍♀️ 🙇🏼 💁🏼 💁🏼‍♂️ 🙅🏼 🙅🏼‍♂️ 🙆🏼 🙆🏼‍♂️ 🙋🏼 🙋🏼‍♂️ 🤦🏼‍♀️ 🤦🏼‍♂️ 🤷🏼‍♀️ 🤷🏼‍♂️ 🙎🏼 🙎🏼‍♂️ 🙍🏼 🙍🏼‍♂️ 💇🏼 💇🏼‍♂️ 💆🏼 💆🏼‍♂️ 🕴🏼 💃🏼 🕺🏼 🚶🏼‍♀️ 🚶🏼 🏃🏼‍♀️ 🏃🏼 🤲🏼 👐🏼 🙌🏼 👏🏼 🙏🏼 👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼 🤜🏼 🤞🏼 ✌🏼 🤟🏼 🤘🏼 👌🏼 👈🏼 👉🏼 👆🏼 👇🏼 ☝🏼 ✋🏼 🤚🏼 🖐🏼 🖖🏼 👋🏼 🤙🏼 💪🏼 🖕🏼 ✍🏼 🤳🏼 💅🏼 👂🏼 👃🏼 +👶🏽 👦🏽 👧🏽 👨🏽 👩🏽 👱🏽‍♀️ 👱🏽 👴🏽 👵🏽 👲🏽 👳🏽‍♀️ 👳🏽 👮🏽‍♀️ 👮🏽 👷🏽‍♀️ 👷🏽 💂🏽‍♀️ 💂🏽 🕵🏽‍♀️ 🕵🏽 👩🏽‍⚕️ 👨🏽‍⚕️ 👩🏽‍🌾 👨🏽‍🌾 👩🏽‍🍳 👨🏽‍🍳 👩🏽‍🎓 👨🏽‍🎓 👩🏽‍🎤 👨🏽‍🎤 👩🏽‍🏫 👨🏽‍🏫 👩🏽‍🏭 👨🏽‍🏭 👩🏽‍💻 👨🏽‍💻 👩🏽‍💼 👨🏽‍💼 👩🏽‍🔧 👨🏽‍🔧 👩🏽‍🔬 👨🏽‍🔬 👩🏽‍🎨 👨🏽‍🎨 👩🏽‍🚒 👨🏽‍🚒 👩🏽‍✈️ 👨🏽‍✈️ 👩🏽‍🚀 👨🏽‍🚀 👩🏽‍⚖️ 👨🏽‍⚖️ 🤶🏽 🎅🏽 👸🏽 🤴🏽 👰🏽 🤵🏽 👼🏽 🤰🏽 🙇🏽‍♀️ 🙇🏽 💁🏽 💁🏽‍♂️ 🙅🏽 🙅🏽‍♂️ 🙆🏽 🙆🏽‍♂️ 🙋🏽 🙋🏽‍♂️ 🤦🏽‍♀️ 🤦🏽‍♂️ 🤷🏽‍♀️ 🤷🏽‍♂️ 🙎🏽 🙎🏽‍♂️ 🙍🏽 🙍🏽‍♂️ 💇🏽 💇🏽‍♂️ 💆🏽 💆🏽‍♂️ 🕴🏼 💃🏽 🕺🏽 🚶🏽‍♀️ 🚶🏽 🏃🏽‍♀️ 🏃🏽 🤲🏽 👐🏽 🙌🏽 👏🏽 🙏🏽 👍🏽 👎🏽 👊🏽 ✊🏽 🤛🏽 🤜🏽 🤞🏽 ✌🏽 🤟🏽 🤘🏽 👌🏽 👈🏽 👉🏽 👆🏽 👇🏽 ☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽 👋🏽 🤙🏽 💪🏽 🖕🏽 ✍🏽 🤳🏽 💅🏽 👂🏽 👃🏽 +👶🏾 👦🏾 👧🏾 👨🏾 👩🏾 👱🏾‍♀️ 👱🏾 👴🏾 👵🏾 👲🏾 👳🏾‍♀️ 👳🏾 👮🏾‍♀️ 👮🏾 👷🏾‍♀️ 👷🏾 💂🏾‍♀️ 💂🏾 🕵🏾‍♀️ 🕵🏾 👩🏾‍⚕️ 👨🏾‍⚕️ 👩🏾‍🌾 👨🏾‍🌾 👩🏾‍🍳 👨🏾‍🍳 👩🏾‍🎓 👨🏾‍🎓 👩🏾‍🎤 👨🏾‍🎤 👩🏾‍🏫 👨🏾‍🏫 👩🏾‍🏭 👨🏾‍🏭 👩🏾‍💻 👨🏾‍💻 👩🏾‍💼 👨🏾‍💼 👩🏾‍🔧 👨🏾‍🔧 👩🏾‍🔬 👨🏾‍🔬 👩🏾‍🎨 👨🏾‍🎨 👩🏾‍🚒 👨🏾‍🚒 👩🏾‍✈️ 👨🏾‍✈️ 👩🏾‍🚀 👨🏾‍🚀 👩🏾‍⚖️ 👨🏾‍⚖️ 🤶🏾 🎅🏾 👸🏾 🤴🏾 👰🏾 🤵🏾 👼🏾 🤰🏾 🙇🏾‍♀️ 🙇🏾 💁🏾 💁🏾‍♂️ 🙅🏾 🙅🏾‍♂️ 🙆🏾 🙆🏾‍♂️ 🙋🏾 🙋🏾‍♂️ 🤦🏾‍♀️ 🤦🏾‍♂️ 🤷🏾‍♀️ 🤷🏾‍♂️ 🙎🏾 🙎🏾‍♂️ 🙍🏾 🙍🏾‍♂️ 💇🏾 💇🏾‍♂️ 💆🏾 💆🏾‍♂️ 🕴🏾 💃🏾 🕺🏾 🚶🏾‍♀️ 🚶🏾 🏃🏾‍♀️ 🏃🏾 🤲🏾 👐🏾 🙌🏾 👏🏾 🙏🏾 👍🏾 👎🏾 👊🏾 ✊🏾 🤛🏾 🤜🏾 🤞🏾 ✌🏾 🤟🏾 🤘🏾 👌🏾 👈🏾 👉🏾 👆🏾 👇🏾 ☝🏾 ✋🏾 🤚🏾 🖐🏾 🖖🏾 👋🏾 🤙🏾 💪🏾 🖕🏾 ✍🏾 🤳🏾 💅🏾 👂🏾 👃🏾 +👶🏿 👦🏿 👧🏿 👨🏿 👩🏿 👱🏿‍♀️ 👱🏿 👴🏿 👵🏿 👲🏿 👳🏿‍♀️ 👳🏿 👮🏿‍♀️ 👮🏿 👷🏿‍♀️ 👷🏿 💂🏿‍♀️ 💂🏿 🕵🏿‍♀️ 🕵🏿 👩🏿‍⚕️ 👨🏿‍⚕️ 👩🏿‍🌾 👨🏿‍🌾 👩🏿‍🍳 👨🏿‍🍳 👩🏿‍🎓 👨🏿‍🎓 👩🏿‍🎤 👨🏿‍🎤 👩🏿‍🏫 👨🏿‍🏫 👩🏿‍🏭 👨🏿‍🏭 👩🏿‍💻 👨🏿‍💻 👩🏿‍💼 👨🏿‍💼 👩🏿‍🔧 👨🏿‍🔧 👩🏿‍🔬 👨🏿‍🔬 👩🏿‍🎨 👨🏿‍🎨 👩🏿‍🚒 👨🏿‍🚒 👩🏿‍✈️ 👨🏿‍✈️ 👩🏿‍🚀 👨🏿‍🚀 👩🏿‍⚖️ 👨🏿‍⚖️ 🤶🏿 🎅🏿 👸🏿 🤴🏿 👰🏿 🤵🏿 👼🏿 🤰🏿 🙇🏿‍♀️ 🙇🏿 💁🏿 💁🏿‍♂️ 🙅🏿 🙅🏿‍♂️ 🙆🏿 🙆🏿‍♂️ 🙋🏿 🙋🏿‍♂️ 🤦🏿‍♀️ 🤦🏿‍♂️ 🤷🏿‍♀️ 🤷🏿‍♂️ 🙎🏿 🙎🏿‍♂️ 🙍🏿 🙍🏿‍♂️ 💇🏿 💇🏿‍♂️ 💆🏿 💆🏿‍♂️ 🕴🏿 💃🏿 🕺🏿 🚶🏿‍♀️ 🚶🏿 🏃🏿‍♀️ 🏃🏿 🤲🏿 👐🏿 🙌🏿 👏🏿 🙏🏿 👍🏿 👎🏿 👊🏿 ✊🏿 🤛🏿 🤜🏿 🤞🏿 ✌🏿 🤟🏿 🤘🏿 👌🏿 👈🏿 👉🏿 👆🏿 👇🏿 ☝🏿 ✋🏿 🤚🏿 🖐🏿 🖖🏿 👋🏿 🤙🏿 💪🏿 🖕🏿 ✍🏿 🤳🏿 💅🏿 👂🏿 👃🏿 +🐶 🐱 🐭 🐹 🐰 🦊 🦝 🐻 🐼 🦘 🦡 🐨 🐯 🦁 🐮 🐷 🐽 🐸 🐵 🙈 🙉 🙊 🐒 🐔 🐧 🐦 🐤 🐣 🐥 🦆 🦢 🦅 🦉 🦚 🦜 🦇 🐺 🐗 🐴 🦄 🐝 🐛 🦋 🐌 🐚 🐞 🐜 🦗 🕷 🕸 🦂 🦟 🦠 🐢 🐍 🦎 🦖 🦕 🐙 🦑 🦐 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🐘 🦏 🦛 🐪 🐫 🦙 🦒 🐃 🐂 🐄 🐎 🐖 🐏 🐑 🐐 🦌 🐕 🐩 🐈 🐓 🦃 🕊 🐇 🐁 🐀 🐿 🦔 🐾 🐉 🐲 🌵 🎄 🌲 🌳 🌴 🌱 🌿 ☘️ 🍀 🎍 🎋 🍃 🍂 🍁 🍄 🌾 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻 🌞 🌝 🌛 🌜 🌚 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔 🌙 🌎 🌍 🌏 💫 ⭐️ 🌟 ✨ ⚡️ ☄️ 💥 🔥 🌪 🌈 ☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫 +🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🍈 🍒 🍑 🍍 🥭 🥥 🥝 🍅 🍆 🥑 🥦 🥒 🥬 🌶 🌽 🥕 🥔 🍠 🥐 🍞 🥖 🥨 🥯 🧀 🥚 🍳 🥞 🥓 🥩 🍗 🍖 🌭 🍔 🍟 🍕 🥪 🥙 🌮 🌯 🥗 🥘 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🍤 🍙 🍚 🍘 🍥 🥮 🥠 🍢 🍡 🍧 🍨 🍦 🥧 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🧂 🍩 🍪 🌰 🥜 🍯 🥛 🍼 ☕️ 🍵 🥤 🍶 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🍾 🥄 🍴 🍽 🥣 🥡 🥢 +⚽️ 🏀 🏈 ⚾️ 🥎 🏐 🏉 🎾 🥏 🎱 🏓 🏸 🥅 🏒 🏑 🥍 🏏 ⛳️ 🏹 🎣 🥊 🥋 🎽 ⛸ 🥌 🛷 🛹 🎿 ⛷ 🏂 🏋️‍♀️ 🏋🏻‍♀️ 🏋🏼‍♀️ 🏋🏽‍♀️ 🏋🏾‍♀️ 🏋🏿‍♀️ 🏋️‍♂️ 🏋🏻‍♂️ 🏋🏼‍♂️ 🏋🏽‍♂️ 🏋🏾‍♂️ 🏋🏿‍♂️ 🤼‍♀️ 🤼‍♂️ 🤸‍♀️ 🤸🏻‍♀️ 🤸🏼‍♀️ 🤸🏽‍♀️ 🤸🏾‍♀️ 🤸🏿‍♀️ 🤸‍♂️ 🤸🏻‍♂️ 🤸🏼‍♂️ 🤸🏽‍♂️ 🤸🏾‍♂️ 🤸🏿‍♂️ ⛹️‍♀️ ⛹🏻‍♀️ ⛹🏼‍♀️ ⛹🏽‍♀️ ⛹🏾‍♀️ ⛹🏿‍♀️ ⛹️‍♂️ ⛹🏻‍♂️ ⛹🏼‍♂️ ⛹🏽‍♂️ ⛹🏾‍♂️ ⛹🏿‍♂️ 🤺 🤾‍♀️ 🤾🏻‍♀️ 🤾🏼‍♀️ 🤾🏾‍♀️ 🤾🏾‍♀️ 🤾🏿‍♀️ 🤾‍♂️ 🤾🏻‍♂️ 🤾🏼‍♂️ 🤾🏽‍♂️ 🤾🏾‍♂️ 🤾🏿‍♂️ 🏌️‍♀️ 🏌🏻‍♀️ 🏌🏼‍♀️ 🏌🏽‍♀️ 🏌🏾‍♀️ 🏌🏿‍♀️ 🏌️‍♂️ 🏌🏻‍♂️ 🏌🏼‍♂️ 🏌🏽‍♂️ 🏌🏾‍♂️ 🏌🏿‍♂️ 🏇 🏇🏻 🏇🏼 🏇🏽 🏇🏾 🏇🏿 🧘‍♀️ 🧘🏻‍♀️ 🧘🏼‍♀️ 🧘🏽‍♀️ 🧘🏾‍♀️ 🧘🏿‍♀️ 🧘‍♂️ 🧘🏻‍♂️ 🧘🏼‍♂️ 🧘🏽‍♂️ 🧘🏾‍♂️ 🧘🏿‍♂️ 🏄‍♀️ 🏄🏻‍♀️ 🏄🏼‍♀️ 🏄🏽‍♀️ 🏄🏾‍♀️ 🏄🏿‍♀️ 🏄‍♂️ 🏄🏻‍♂️ 🏄🏼‍♂️ 🏄🏽‍♂️ 🏄🏾‍♂️ 🏄🏿‍♂️ 🏊‍♀️ 🏊🏻‍♀️ 🏊🏼‍♀️ 🏊🏽‍♀️ 🏊🏾‍♀️ 🏊🏿‍♀️ 🏊‍♂️ 🏊🏻‍♂️ 🏊🏼‍♂️ 🏊🏽‍♂️ 🏊🏾‍♂️ 🏊🏿‍♂️ 🤽‍♀️ 🤽🏻‍♀️ 🤽🏼‍♀️ 🤽🏽‍♀️ 🤽🏾‍♀️ 🤽🏿‍♀️ 🤽‍♂️ 🤽🏻‍♂️ 🤽🏼‍♂️ 🤽🏽‍♂️ 🤽🏾‍♂️ 🤽🏿‍♂️ 🚣‍♀️ 🚣🏻‍♀️ 🚣🏼‍♀️ 🚣🏽‍♀️ 🚣🏾‍♀️ 🚣🏿‍♀️ 🚣‍♂️ 🚣🏻‍♂️ 🚣🏼‍♂️ 🚣🏽‍♂️ 🚣🏾‍♂️ 🚣🏿‍♂️ 🧗‍♀️ 🧗🏻‍♀️ 🧗🏼‍♀️ 🧗🏽‍♀️ 🧗🏾‍♀️ 🧗🏿‍♀️ 🧗‍♂️ 🧗🏻‍♂️ 🧗🏼‍♂️ 🧗🏽‍♂️ 🧗🏾‍♂️ 🧗🏿‍♂️ 🚵‍♀️ 🚵🏻‍♀️ 🚵🏼‍♀️ 🚵🏽‍♀️ 🚵🏾‍♀️ 🚵🏿‍♀️ 🚵‍♂️ 🚵🏻‍♂️ 🚵🏼‍♂️ 🚵🏽‍♂️ 🚵🏾‍♂️ 🚵🏿‍♂️ 🚴‍♀️ 🚴🏻‍♀️ 🚴🏼‍♀️ 🚴🏽‍♀️ 🚴🏾‍♀️ 🚴🏿‍♀️ 🚴‍♂️ 🚴🏻‍♂️ 🚴🏼‍♂️ 🚴🏽‍♂️ 🚴🏾‍♂️ 🚴🏿‍♂️ 🏆 🥇 🥈 🥉 🏅 🎖 🏵 🎗 🎫 🎟 🎪 🤹‍♀️ 🤹🏻‍♀️ 🤹🏼‍♀️ 🤹🏽‍♀️ 🤹🏾‍♀️ 🤹🏿‍♀️ 🤹‍♂️ 🤹🏻‍♂️ 🤹🏼‍♂️ 🤹🏽‍♂️ 🤹🏾‍♂️ 🤹🏿‍♂️ 🎭 🎨 🎬 🎤 🎧 🎼 🎹 🥁 🎷 🎺 🎸 🎻 🎲 🧩 ♟ 🎯 🎳 🎮 🎰 +🚗 🚕 🚙 🚌 🚎 🏎 🚓 🚑 🚒 🚐 🚚 🚛 🚜 🛴 🚲 🛵 🏍 🚨 🚔 🚍 🚘 🚖 🚡 🚠 🚟 🚃 🚋 🚞 🚝 🚄 🚅 🚈 🚂 🚆 🚇 🚊 🚉 ✈️ 🛫 🛬 🛩 💺 🛰 🚀 🛸 🚁 🛶 ⛵️ 🚤 🛥 🛳 ⛴ 🚢 ⚓️ ⛽️ 🚧 🚦 🚥 🚏 🗺 🗿 🗽 🗼 🏰 🏯 🏟 🎡 🎢 🎠 ⛲️ ⛱ 🏖 🏝 🏜 🌋 ⛰ 🏔 🗻 🏕 ⛺️ 🏠 🏡 🏘 🏚 🏗 🏭 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛 ⛪️ 🕌 🕍 🕋 ⛩ 🛤 🛣 🗾 🎑 🏞 🌅 🌄 🌠 🎇 🎆 🌇 🌆 🏙 🌃 🌌 🌉 🌁 +⌚️ 📱 📲 💻 ⌨️ 🖥 🖨 🖱 🖲 🕹 🗜 💽 💾 💿 📀 📼 📷 📸 📹 🎥 📽 🎞 📞 ☎️ 📟 📠 📺 📻 🎙 🎚 🎛 ⏱ ⏲ ⏰ 🕰 ⌛️ ⏳ 📡 🔋 🔌 💡 🔦 🕯 🗑 🛢 💸 💵 💴 💶 💷 💰 💳 🧾 💎 ⚖️ 🔧 🔨 ⚒ 🛠 ⛏ 🔩 ⚙️ ⛓ 🔫 💣 🔪 🗡 ⚔️ 🛡 🚬 ⚰️ ⚱️ 🏺 🧭 🧱 🔮 🧿 🧸 📿 💈 ⚗️ 🔭 🧰 🧲 🧪 🧫 🧬 🧯 🔬 🕳 💊 💉 🌡 🚽 🚰 🚿 🛁 🛀 🛀🏻 🛀🏼 🛀🏽 🛀🏾 🛀🏿 🧴 🧵 🧶 🧷 🧹 🧺 🧻 🧼 🧽 🛎 🔑 🗝 🚪 🛋 🛏 🛌 🖼 🛍 🧳 🛒 🎁 🎈 🎏 🎀 🎊 🎉 🧨 🎎 🏮 🎐 🧧 ✉️ 📩 📨 📧 💌 📥 📤 📦 🏷 📪 📫 📬 📭 📮 📯 📜 📃 📄 📑 📊 📈 📉 🗒 🗓 📆 📅 📇 🗃 🗳 🗄 📋 📁 📂 🗂 🗞 📰 📓 📔 📒 📕 📗 📘 📙 📚 📖 🔖 🔗 📎 🖇 📐 📏 📌 📍 ✂️ 🖊 🖋 ✒️ 🖌 🖍 📝 ✏️ 🔍 🔎 🔏 🔐 🔒 🔓 +❤️ 🧡 💛 💚 💙 💜 🖤 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 💟 ☮️ ✝️ ☪️ 🕉 ☸️ ✡️ 🔯 🕎 ☯️ ☦️ 🛐 ⛎ ♈️ ♉️ ♊️ ♋️ ♌️ ♍️ ♎️ ♏️ ♐️ ♑️ ♒️ ♓️ 🆔 ⚛️ 🉑 ☢️ ☣️ 📴 📳 🈶 🈚️ 🈸 🈺 🈷️ ✴️ 🆚 💮 🉐 ㊙️ ㊗️ 🈴 🈵 🈹 🈲 🅰️ 🅱️ 🆎 🆑 🅾️ 🆘 ❌ ⭕️ 🛑 ⛔️ 📛 🚫 💯 💢 ♨️ 🚷 🚯 🚳 🚱 🔞 📵 🚭 ❗️ ❕ ❓ ❔ ‼️ ⁉️ 🔅 🔆 〽️ ⚠️ 🚸 🔱 ⚜️ 🔰 ♻️ ✅ 🈯️ 💹 ❇️ ✳️ ❎ 🌐 💠 Ⓜ️ 🌀 💤 🏧 🚾 ♿️ 🅿️ 🈳 🈂️ 🛂 🛃 🛄 🛅 🚹 🚺 🚼 🚻 🚮 🎦 📶 🈁 🔣 ℹ️ 🔤 🔡 🔠 🆖 🆗 🆙 🆒 🆕 🆓 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 🔢 #️⃣ *️⃣ ⏏️ ▶️ ⏸ ⏯ ⏹ ⏺ ⏭ ⏮ ⏩ ⏪ ⏫ ⏬ ◀️ 🔼 🔽 ➡️ ⬅️ ⬆️ ⬇️ ↗️ ↘️ ↙️ ↖️ ↕️ ↔️ ↪️ ↩️ ⤴️ ⤵️ 🔀 🔁 🔂 🔄 🔃 🎵 🎶 ➕ ➖ ➗ ✖️ ♾ 💲 💱 ™️ ©️ ®️ 〰️ ➰ ➿ 🔚 🔙 🔛 🔝 🔜 ✔️ ☑️ 🔘 ⚪️ ⚫️ 🔴 🔵 🔺 🔻 🔸 🔹 🔶 🔷 🔳 🔲 ▪️ ▫️ ◾️ ◽️ ◼️ ◻️ ⬛️ ⬜️ 🔈 🔇 🔉 🔊 🔔 🔕 📣 📢 👁‍🗨 💬 💭 🗯 ♠️ ♣️ ♥️ ♦️ 🃏 🎴 🀄️ 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 +🏳️ 🏴 🏁 🚩 🏳️‍🌈 🏴‍☠️ 🇦🇫 🇦🇽 🇦🇱 🇩🇿 🇦🇸 🇦🇩 🇦🇴 🇦🇮 🇦🇶 🇦🇬 🇦🇷 🇦🇲 🇦🇼 🇦🇺 🇦🇹 🇦🇿 🇧🇸 🇧🇭 🇧🇩 🇧🇧 🇧🇾 🇧🇪 🇧🇿 🇧🇯 🇧🇲 🇧🇹 🇧🇴 🇧🇦 🇧🇼 🇧🇷 🇮🇴 🇻🇬 🇧🇳 🇧🇬 🇧🇫 🇧🇮 🇰🇭 🇨🇲 🇨🇦 🇮🇨 🇨🇻 🇧🇶 🇰🇾 🇨🇫 🇹🇩 🇨🇱 🇨🇳 🇨🇽 🇨🇨 🇨🇴 🇰🇲 🇨🇬 🇨🇩 🇨🇰 🇨🇷 🇨🇮 🇭🇷 🇨🇺 🇨🇼 🇨🇾 🇨🇿 🇩🇰 🇩🇯 🇩🇲 🇩🇴 🇪🇨 🇪🇬 🇸🇻 🇬🇶 🇪🇷 🇪🇪 🇪🇹 🇪🇺 🇫🇰 🇫🇴 🇫🇯 🇫🇮 🇫🇷 🇬🇫 🇵🇫 🇹🇫 🇬🇦 🇬🇲 🇬🇪 🇩🇪 🇬🇭 🇬🇮 🇬🇷 🇬🇱 🇬🇩 🇬🇵 🇬🇺 🇬🇹 🇬🇬 🇬🇳 🇬🇼 🇬🇾 🇭🇹 🇭🇳 🇭🇰 🇭🇺 🇮🇸 🇮🇳 🇮🇩 🇮🇷 🇮🇶 🇮🇪 🇮🇲 🇮🇱 🇮🇹 🇯🇲 🇯🇵 🎌 🇯🇪 🇯🇴 🇰🇿 🇰🇪 🇰🇮 🇽🇰 🇰🇼 🇰🇬 🇱🇦 🇱🇻 🇱🇧 🇱🇸 🇱🇷 🇱🇾 🇱🇮 🇱🇹 🇱🇺 🇲🇴 🇲🇰 🇲🇬 🇲🇼 🇲🇾 🇲🇻 🇲🇱 🇲🇹 🇲🇭 🇲🇶 🇲🇷 🇲🇺 🇾🇹 🇲🇽 🇫🇲 🇲🇩 🇲🇨 🇲🇳 🇲🇪 🇲🇸 🇲🇦 🇲🇿 🇲🇲 🇳🇦 🇳🇷 🇳🇵 🇳🇱 🇳🇨 🇳🇿 🇳🇮 🇳🇪 🇳🇬 🇳🇺 🇳🇫 🇰🇵 🇲🇵 🇳🇴 🇴🇲 🇵🇰 🇵🇼 🇵🇸 🇵🇦 🇵🇬 🇵🇾 🇵🇪 🇵🇭 🇵🇳 🇵🇱 🇵🇹 🇵🇷 🇶🇦 🇷🇪 🇷🇴 🇷🇺 🇷🇼 🇼🇸 🇸🇲 🇸🇦 🇸🇳 🇷🇸 🇸🇨 🇸🇱 🇸🇬 🇸🇽 🇸🇰 🇸🇮 🇬🇸 🇸🇧 🇸🇴 🇿🇦 🇰🇷 🇸🇸 🇪🇸 🇱🇰 🇧🇱 🇸🇭 🇰🇳 🇱🇨 🇵🇲 🇻🇨 🇸🇩 🇸🇷 🇸🇿 🇸🇪 🇨🇭 🇸🇾 🇹🇼 🇹🇯 🇹🇿 🇹🇭 🇹🇱 🇹🇬 🇹🇰 🇹🇴 🇹🇹 🇹🇳 🇹🇷 🇹🇲 🇹🇨 🇹🇻 🇻🇮 🇺🇬 🇺🇦 🇦🇪 🇬🇧 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁳󠁣󠁴󠁿 🏴󠁧󠁢󠁷󠁬󠁳󠁿 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇺 🇻🇦 🇻🇪 🇻🇳 🇼🇫 🇪🇭 🇾🇪 🇿🇲 🇿🇼 +🥱 🤏 🦾 🦿 🦻 🧏 🧏‍♂️ 🧏‍♀️ 🧍 🧍‍♂️ 🧍‍♀️ 🧎 🧎‍♂️ 🧎‍♀️ 👨‍🦯 👩‍🦯 👨‍🦼 👩‍🦼 👨‍🦽 👩‍🦽 🦧 🦮 🐕‍🦺 🦥 🦦 🦨 🦩 🧄 🧅 🧇 🧆 🧈 🦪 🧃 🧉 🧊 🛕 🦽 🦼 🛺 🪂 🪐 🤿 🪀 🪁 🦺 🥻 🩱 🩲 🩳 🩰 🪕 🪔 🪓 🦯 🩸 🩹 🩺 🪑 🪒 🤎 🤍 🟠 🟡 🟢 🟣 🟤 🟥 🟧 🟨 🟩 🟦 🟪 🟫"; + + [Benchmark] + public TextLayout BuildEmojisTextLayout() => new TextLayout(Emojis, Typeface.Default, 12d, Brushes.Black); + + public void Dispose() + { + _app?.Dispose(); + } +} diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj index eb1bf24d0c..20c2f711ad 100644 --- a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj @@ -1,7 +1,6 @@ net6.0 - latest Library true diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 12eb290fde..471f19f948 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -1,7 +1,6 @@  net6.0 - latest Library true diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index d6c521decd..baf933bd66 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -596,7 +596,7 @@ namespace Avalonia.Controls.UnitTests private static Window PreparedWindow(object content = null) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs index 496b5bc1b6..02767a21eb 100644 --- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs @@ -570,7 +570,7 @@ namespace Avalonia.Controls.UnitTests private static Window PreparedWindow(object content = null) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 90c6fd3b21..c1d9fad6f4 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -827,7 +827,7 @@ namespace Avalonia.Controls.UnitTests { AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 2d1dad6be0..469bcecfb9 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -569,7 +569,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = CreateTarget(itemCount: 10); var items = (IList)target.Items; target.ApplyTemplate(); - target.Measure(default(Size)); + target.Measure(default); target.Arrange(default); // Check for issue #591: this should not throw. diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d4193d33ee..65957fda6d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -564,7 +564,7 @@ namespace Avalonia.Controls.UnitTests.Primitives using (CreateServices()) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index bd6d5d55e2..531a2869cd 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -767,7 +767,7 @@ namespace Avalonia.Controls.UnitTests { AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); @@ -876,7 +876,7 @@ namespace Avalonia.Controls.UnitTests AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 81936711ef..f526465b9b 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1248,7 +1248,7 @@ namespace Avalonia.Controls.UnitTests using (Application()) { var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); + var navigation = AvaloniaLocator.Current.GetRequiredService(); var data = CreateTestTreeData(); var target = new TreeView @@ -1293,7 +1293,6 @@ namespace Avalonia.Controls.UnitTests using (Application()) { var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); var data = CreateTestTreeData(); var selectedNode = new Node { Value = "Out of Tree Selected Item" }; @@ -1353,7 +1352,7 @@ namespace Avalonia.Controls.UnitTests var rootNode = tree[0]; - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1400,7 +1399,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1447,7 +1446,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index eb57cfb8da..0c0857b680 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -389,9 +389,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -405,9 +405,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -444,18 +444,6 @@ "sprintf-js": "~1.0.2" } }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" - } - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -469,9 +457,9 @@ "dev": true }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "brace-expansion": { @@ -538,9 +526,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -554,19 +542,19 @@ "dev": true }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.6.0" } }, "clean-stack": { @@ -576,42 +564,14 @@ "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "color-convert": { @@ -708,15 +668,6 @@ "strip-bom": "^4.0.0" } }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -724,68 +675,23 @@ "dev": true }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "dev": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -829,13 +735,10 @@ } }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "foreground-child": { "version": "2.0.0", @@ -860,18 +763,12 @@ "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -911,9 +808,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -931,33 +828,12 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, "hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -1008,12 +884,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1023,51 +893,27 @@ "binary-extensions": "^2.0.0" } }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-callable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", - "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1075,24 +921,9 @@ "dev": true }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, "is-stream": { @@ -1101,39 +932,24 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1222,22 +1038,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true - }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", - "dev": true, - "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1261,13 +1061,10 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true }, "locate-path": { "version": "6.0.0", @@ -1291,12 +1088,13 @@ "dev": true }, "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "loose-envify": { @@ -1332,43 +1130,133 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "mocha": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", - "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.2", - "debug": "4.1.1", - "diff": "4.0.2", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "4.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.1" + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "moq.ts": { @@ -1386,6 +1274,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -1570,30 +1464,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1604,12 +1474,12 @@ } }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -1679,9 +1549,9 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -1741,19 +1611,6 @@ "fromentries": "^1.2.0" } }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", - "dev": true, - "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" - } - }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -1792,9 +1649,9 @@ "dev": true }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -1858,9 +1715,9 @@ "dev": true }, "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -1938,42 +1795,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -1983,9 +1821,9 @@ "dev": true }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { @@ -2090,82 +1928,21 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "wrappy": { @@ -2193,214 +1970,57 @@ "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", - "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "decamelize": "^1.2.0", - "flat": "^4.1.0", - "is-plain-obj": "^1.1.0", - "yargs": "^14.2.3" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -2409,6 +2029,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json index dbb12e192a..580c4059ba 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -16,7 +16,7 @@ "@types/mocha": "8.0.3", "@types/react": "^16.3.14", "chai": "^4.2.0", - "mocha": "^8.1.3", + "mocha": "^10.2.0", "moq.ts": "^6.4.0", "nyc": "^15.1.0", "react": "^16.3.2", diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 03d9332051..57338a1e08 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -15,4 +15,5 @@ + diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 53c186111d..656c2cbbbc 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -334,7 +334,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.IsType(((BindingExpression)result).Converter); } @@ -350,7 +350,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.Same(converter.Object, ((BindingExpression)result).Converter); } @@ -367,7 +367,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Bar", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.Same("foo", ((BindingExpression)result).ConverterParameter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 44829aae1e..45deb97f51 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -20,7 +20,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: false); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); @@ -38,7 +38,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); @@ -56,7 +56,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template }; var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index cb5c625c35..a7ef2c4e4d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -84,6 +84,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("fallback", target.Text); } @@ -106,6 +107,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("(null)", target.Text); } @@ -128,6 +130,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("1,2,(unset)", target.Text); } @@ -150,6 +153,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("1,2,Fallback", target.Text); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 6dce5eaab5..fa4957c24c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -3,7 +3,6 @@ net6.0;net47 Library true - latest diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs index 196375fb40..30326adba5 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs @@ -47,7 +47,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) { var isLaunchingReceived = false; - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; // Initialize ReactiveUI Suspension as in real-world scenario. @@ -65,7 +65,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) using (var lifetime = new ExoticApplicationLifetimeWithoutLifecycleEvents()) { - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; Assert.Throws(() => new AutoSuspendHelper(application.ApplicationLifetime)); } @@ -88,7 +88,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) { var shouldPersistReceived = false; - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; // Initialize ReactiveUI Suspension as in real-world scenario. diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs similarity index 95% rename from tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs rename to tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs index 0f2e8ebc21..81f04dc53e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Linq; using Xunit; -namespace Avalonia.Base.UnitTests +namespace Avalonia.ReactiveUI.UnitTests { public class AvaloniaObjectTests_GetSubject { diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs new file mode 100644 index 0000000000..c833017212 --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; +#else +namespace Avalonia.Direct2D1.RenderTests.Controls; +#endif + +public class AdornerTests : TestBase +{ + public AdornerTests() + : base(@"Controls\Adorner") + { + } + + [Fact] + public async Task Focus_Adorner_Is_Properly_Clipped() + { + Border adorned; + var tree = new Decorator + { + Child = new VisualLayerManager + { + Child = new Border + { + Background = Brushes.Red, + Padding = new Thickness(10, 50, 10,10), + Child = new Border() + { + Background = Brushes.White, + ClipToBounds = true, + Padding = new Thickness(0, -30, 0, 0), + Child = adorned = new Border + { + Background = Brushes.Green, + VerticalAlignment = VerticalAlignment.Top, + Height = 100, + Width = 50 + } + } + } + }, + Width = 200, + Height = 200 + }; + var adorner = new Border + { + BorderThickness = new Thickness(2), + BorderBrush = Brushes.Black + }; + + var size = new Size(tree.Width, tree.Height); + tree.Measure(size); + tree.Arrange(new Rect(size)); + + + adorned.AttachedToVisualTree += delegate + { + AdornerLayer.SetAdornedElement(adorner, adorned); + AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner); + }; + tree.Measure(size); + tree.Arrange(new Rect(size)); + + await RenderToFile(tree); + CompareImages(skipImmediate: true, skipDeferred: true); + } +} \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 8a127897d7..edde62f041 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -91,7 +91,7 @@ namespace Avalonia.Direct2D1.RenderTests var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png"); - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetRequiredService(); var pixelSize = new PixelSize((int)target.Width, (int)target.Height); var size = new Size(target.Width, target.Height); var dpiVector = new Vector(dpi, dpi); @@ -156,7 +156,8 @@ namespace Avalonia.Direct2D1.RenderTests public ILockedFramebuffer Lock() => _bitmap.Lock(); } - protected void CompareImages([CallerMemberName] string testName = "") + protected void CompareImages([CallerMemberName] string testName = "", + bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false) { var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); @@ -172,17 +173,17 @@ namespace Avalonia.Direct2D1.RenderTests var deferredError = CompareImages(deferred, expected); var compositedError = CompareImages(composited, expected); - if (immediateError > 0.022) + if (immediateError > 0.022 && !skipImmediate) { Assert.True(false, immediatePath + ": Error = " + immediateError); } - if (deferredError > 0.022) + if (deferredError > 0.022 && !skipDeferred) { Assert.True(false, deferredPath + ": Error = " + deferredError); } - if (compositedError > 0.022) + if (compositedError > 0.022 && !skipCompositor) { Assert.True(false, compositedPath + ": Error = " + compositedError); } diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj index ccde66a50e..ea91b8c196 100644 --- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj +++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj @@ -1,7 +1,6 @@  net6.0 - latest diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs index aa499bb135..81d7b1854b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs @@ -1,6 +1,4 @@ -using System; -using Avalonia.Media.TextFormatting; -using Avalonia.Utilities; +using Avalonia.Media.TextFormatting; namespace Avalonia.Skia.UnitTests.Media.TextFormatting { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 33d4fba5f1..1b6fd537eb 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -62,6 +62,69 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + private class TextSourceWithDummyRuns : ITextSource + { + private readonly TextRunProperties _properties; + private readonly List> _textRuns; + + public TextSourceWithDummyRuns(TextRunProperties properties) + { + _properties = properties; + + _textRuns = new List> + { + new ValueSpan(0, 5, new TextCharacters("Hello", _properties)), + new ValueSpan(5, 1, new DummyRun()), + new ValueSpan(6, 1, new DummyRun()), + new ValueSpan(7, 6, new TextCharacters(" World", _properties)) + }; + } + + public TextRun GetTextRun(int textSourceIndex) + { + foreach (var run in _textRuns) + { + if (textSourceIndex < run.Start + run.Length) + { + return run.Value; + } + } + + return new TextEndOfParagraph(); + } + + private class DummyRun : TextRun + { + public DummyRun() + { + Length = DefaultTextSourceLength; + } + + public override int Length { get; } + } + } + + [Fact] + public void Should_Format_TextLine_With_Non_Text_TextRuns() + { + using (Start()) + { + var defaultProperties = + new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black); + + var textSource = new TextSourceWithDummyRuns(defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + Assert.Equal(5, textLine.TextRuns.Count); + + Assert.Equal(14, textLine.Length); + } + } + [Fact] public void Should_Format_TextRuns_With_TextRunStyles() { @@ -520,7 +583,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var expectedTextLine = formatter.FormatLine(new SingleBufferTextSource(text, defaultProperties), 0, double.PositiveInfinity, paragraphProperties); - var expectedRuns = expectedTextLine.TextRuns.Cast().ToList(); + var expectedRuns = expectedTextLine.TextRuns.Cast().ToList(); var expectedGlyphs = expectedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); @@ -539,7 +602,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties); - var shapedRuns = textLine.TextRuns.Cast().ToList(); + var shapedRuns = textLine.TextRuns.Cast().ToList(); var actualGlyphs = shapedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index a407b38eb1..7501bf21fe 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -141,7 +141,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting black, textWrapping: TextWrapping.Wrap); - var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() + var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); var outer = new GraphemeEnumerator(new CharacterBufferRange(text)); @@ -153,7 +153,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { while (inner.MoveNext()) { - j += inner.Current.Text.Length; + j += inner.Current.Length; if (j + i > text.Length) { @@ -174,7 +174,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textWrapping: TextWrapping.Wrap, textStyleOverrides: spans); - var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() + var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); Assert.Equal(expectedGlyphs.Count, actualGlyphs.Count); @@ -192,7 +192,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting inner = new GraphemeEnumerator(new CharacterBufferRange(text)); - i += outer.Current.Text.Length; + i += outer.Current.Length; } } } @@ -447,7 +447,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting 12.0f, Brushes.Black.ToImmutable()); - var shapedRun = (ShapedTextCharacters)layout.TextLines[0].TextRuns[0]; + var shapedRun = (ShapedTextRun)layout.TextLines[0].TextRuns[0]; var glyphRun = shapedRun.GlyphRun; @@ -481,7 +481,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; var glyphClusters = shapedRun.ShapedBuffer.GlyphClusters; @@ -514,13 +514,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, layout.TextLines[0].TextRuns.Count); - Assert.Equal(expectedLength, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphClusters.Count); + Assert.Equal(expectedLength, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphClusters.Count); - Assert.Equal(5, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[5]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[5]); if (expectedLength == 7) { - Assert.Equal(5, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[6]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[6]); } } } @@ -555,7 +555,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var textRun = (ShapedTextCharacters)textLine.TextRuns[0]; + var textRun = (ShapedTextRun)textLine.TextRuns[0]; Assert.Equal(7, textRun.Length); @@ -775,7 +775,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(textLine.WidthIncludingTrailingWhitespace, rect.Width); } - var rects = layout.TextLines.SelectMany(x => x.TextRuns.Cast()) + var rects = layout.TextLines.SelectMany(x => x.TextRuns.Cast()) .SelectMany(x => x.ShapedBuffer.GlyphAdvances).ToArray(); for (var i = 0; i < SingleLineText.Length; i++) @@ -814,7 +814,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { Assert.True(textLine.Width <= maxWidth); - var actual = new string(textLine.TextRuns.Cast() + var actual = new string(textLine.TextRuns.Cast() .OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar) .SelectMany(x => new CharacterBufferRange(x.CharacterBufferReference, x.Length)).ToArray()); @@ -855,7 +855,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Brushes.Black, flowDirection: FlowDirection.RightToLeft); - var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters; + var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextRun; var hit = layout.HitTestPoint(new Point()); @@ -881,7 +881,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting currentX += advance; } - var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters; + var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextRun; hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0)); @@ -928,7 +928,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var firstRun = (ShapedTextCharacters)textLine.TextRuns[0]; + var firstRun = (ShapedTextRun)textLine.TextRuns[0]; var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0]; @@ -974,9 +974,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var grapheme = graphemeEnumerator.Current; - var textStyleOverrides = new[] { new ValueSpan(i, grapheme.Text.Length, new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Red)) }; + var textStyleOverrides = new[] { new ValueSpan(i, grapheme.Length, new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Red)) }; - i += grapheme.Text.Length; + i += grapheme.Length; var layout = new TextLayout( text, @@ -987,7 +987,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var shapedRuns = textLine.TextRuns.Cast().ToList(); + var shapedRuns = textLine.TextRuns.Cast().ToList(); var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphClusters).ToList(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index d6257a0de8..2c6ccfa896 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -92,7 +92,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar)) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; clusters.AddRange(shapedRun.IsReversed ? shapedRun.ShapedBuffer.GlyphClusters.Reverse() : @@ -139,7 +139,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar)) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; clusters.AddRange(shapedRun.IsReversed ? shapedRun.ShapedBuffer.GlyphClusters.Reverse() : @@ -246,7 +246,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting formatter.FormatLine(textSource, 0, double.PositiveInfinity, new GenericTextParagraphProperties(defaultProperties)); - var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters) + var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters) .ToArray(); var previousCharacterHit = new CharacterHit(text.Length); @@ -308,7 +308,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var run in textLine.TextRuns) { - var textRun = (ShapedTextCharacters)run; + var textRun = (ShapedTextRun)run; var glyphRun = textRun.GlyphRun; @@ -326,7 +326,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } - Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length))); + var actualDistance = textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length)); + + Assert.Equal(currentDistance, actualDistance); } } @@ -634,7 +636,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting formatter.FormatLine(textSource, 0, double.PositiveInfinity, new GenericTextParagraphProperties(defaultProperties)); - var textRuns = textLine.TextRuns.Cast().ToList(); + var textRuns = textLine.TextRuns.Cast().ToList(); var lineWidth = textLine.WidthIncludingTrailingWhitespace; @@ -732,14 +734,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting private static bool IsRightToLeft(TextLine textLine) { - return textLine.TextRuns.Cast().Any(x => !x.ShapedBuffer.IsLeftToRight); + return textLine.TextRuns.Cast().Any(x => !x.ShapedBuffer.IsLeftToRight); } private static List BuildGlyphClusters(TextLine textLine) { var glyphClusters = new List(); - var shapedTextRuns = textLine.TextRuns.Cast().ToList(); + var shapedTextRuns = textLine.TextRuns.Cast().ToList(); var lastCluster = -1; @@ -774,7 +776,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var lastCluster = -1; - var shapedTextRuns = textLine.TextRuns.Cast().ToList(); + var shapedTextRuns = textLine.TextRuns.Cast().ToList(); foreach (var textRun in shapedTextRuns) { @@ -820,16 +822,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var text = "0123"; var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture); - var firstRun = new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties); + var firstRun = new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties); var textRuns = new List { new CustomDrawableRun(), firstRun, new CustomDrawableRun(), - new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties), + new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties), new CustomDrawableRun(), - new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties) + new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties) }; var textSource = new FixedRunsTextSource(textRuns); @@ -885,14 +887,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, 3); - var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = textLine.TextRuns[0] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(3, 4); - var secondRun = textLine.TextRuns[1] as ShapedTextCharacters; + var secondRun = textLine.TextRuns[1] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); @@ -932,14 +934,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, 4); - var secondRun = textLine.TextRuns[1] as ShapedTextCharacters; + var secondRun = textLine.TextRuns[1] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(4, 3); - var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = textLine.TextRuns[0] as ShapedTextRun; Assert.Equal(1, textBounds.Count); diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index cb6884cad8..17448ade76 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,11 +1,9 @@  netstandard2.0 - latest false Library false - latest ..\..\build\avalonia.snk false true diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 2b1685f358..55ac16054d 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -76,13 +76,8 @@ namespace Avalonia.UnitTests var asset = fontAssets.First(); - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - if (assetLoader == null) - { - throw new NotSupportedException("IAssetLoader is not registered."); - } - var stream = assetLoader.Open(asset); return new HarfBuzzGlyphTypefaceImpl(stream); diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 8f132433ec..40306a4513 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -71,7 +71,6 @@ namespace Avalonia.UnitTests IRuntimePlatform platform = null, IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, - IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, @@ -91,7 +90,6 @@ namespace Avalonia.UnitTests RenderInterface = renderInterface; FontManagerImpl = fontManagerImpl; TextShaperImpl = textShaperImpl; - Scheduler = scheduler; StandardCursorFactory = standardCursorFactory; Theme = theme; ThreadingInterface = threadingInterface; @@ -110,7 +108,6 @@ namespace Avalonia.UnitTests public IPlatformRenderInterface RenderInterface { get; } public IFontManagerImpl FontManagerImpl { get; } public ITextShaperImpl TextShaperImpl { get; } - public IScheduler Scheduler { get; } public ICursorFactory StandardCursorFactory { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } @@ -149,7 +146,6 @@ namespace Avalonia.UnitTests renderInterface: renderInterface ?? RenderInterface, fontManagerImpl: fontManagerImpl ?? FontManagerImpl, textShaperImpl: textShaperImpl ?? TextShaperImpl, - scheduler: scheduler ?? Scheduler, standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, theme: theme ?? Theme, threadingInterface: threadingInterface ?? ThreadingInterface, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 03e19359c3..82626877d0 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -66,7 +66,6 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.FontManagerImpl) .Bind().ToConstant(Services.TextShaperImpl) .Bind().ToConstant(Services.ThreadingInterface) - .Bind().ToConstant(Services.Scheduler) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000..6a67087d41 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png differ diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000..6a67087d41 Binary files /dev/null and b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png differ