diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46e8665945..2f63750cdc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,7 +21,7 @@ - [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation ## Breaking changes - + ## Obsoletions / Deprecations diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props index 85e7a1f34d..620ec58ff3 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,7 +1,7 @@  - - - + + + diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index c3b136d41d..1911c02677 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 3d9548ab9d..1b60bb4df9 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.10.999 + 11.0.999 Copyright 2022 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index d54cffba08..cc573825cd 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,7 +1,7 @@  - - - + + + diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 95f61422cb..af8c53cb33 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -121,7 +121,7 @@ void WindowImpl::BringToFront() { if(Window != nullptr) { - if (![Window isMiniaturized]) + if ([Window isVisible] && ![Window isMiniaturized]) { if(IsDialog()) { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d98a068d84..b1bacc6483 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -115,10 +115,6 @@ namespace ControlCatalog.NetCore UseDBusMenu = true, EnableIme = true }) - .With(new Win32PlatformOptions - { - EnableMultitouch = true - }) .UseSkia() .AfterSetup(builder => { diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 5251a2fa55..c778b31c42 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" - xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> + xmlns:local="clr-namespace:ControlCatalog" SystemDecorations="None" Name="Window"> diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 58433f13ce..7133ddaa6a 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -22,8 +22,8 @@ namespace ControlCatalog if (AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo().IsDesktop == true) { - IList tabItems = ((IList)sideBar.Items); - tabItems.Add(new TabItem() + var tabItems = (sideBar.Items as IList); + tabItems?.Add(new TabItem() { Header = "Screens", Content = new ScreenPage() @@ -36,7 +36,7 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - var themeStyle = Application.Current.Styles[0]; + var themeStyle = Application.Current!.Styles[0]; if (theme == CatalogTheme.FluentLight) { if (App.Fluent.Mode != FluentThemeMode.Light) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 1e4bf2de38..d5513904c0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -18,15 +18,15 @@ - - + + + Click="OnCloseClicked" /> diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs index 2dfa02c7ed..99bc50250b 100644 --- a/samples/ControlCatalog/Models/Person.cs +++ b/samples/ControlCatalog/Models/Person.cs @@ -85,7 +85,7 @@ namespace ControlCatalog.Models } else { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (_errorLookup.TryGetValue(propertyName, out var errorList)) { errorList.Clear(); errorList.Add(error!); @@ -114,12 +114,12 @@ namespace ControlCatalog.Models PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - public IEnumerable? GetErrors(string propertyName) + public IEnumerable GetErrors(string? propertyName) { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (propertyName is { } && _errorLookup.TryGetValue(propertyName, out var errorList)) return errorList; else - return null; + return Array.Empty(); } } } diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index 46f3705ffd..faa12bc8da 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -28,7 +28,7 @@ - + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 7a0957b02d..bc18327f12 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -1,8 +1,6 @@ using Avalonia.Controls; using Avalonia.LogicalTree; -using Avalonia.Markup; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Data; using System; using System.Collections.Generic; using System.Linq; @@ -161,23 +159,23 @@ namespace ControlCatalog.Pages private bool LastWordContains(string? searchText, string? item) { var words = searchText?.Split(' ') ?? Array.Empty(); - var options = Sentences.Select(x => x.First).ToArray(); + var options = Sentences.Select(x => x.First) + .ToArray?>(); for (var i = 0; i < words.Length; ++i) { var word = words[i]; for (var j = 0; word is { } && j < options.Length; ++j) { - var option = options[j]; - if (option == null) - continue; - - if (i == words.Length - 1) - { - options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; - } - else + if (options[i] is { } option) { - options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + if (i == words.Length - 1) + { + options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; + } + else + { + options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + } } } } diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs index 5c584b8781..e7450075ad 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs @@ -21,20 +21,23 @@ namespace ControlCatalog.Pages public void OnSpin(object sender, SpinEventArgs e) { var spinner = (ButtonSpinner)sender; - var txtBox = (TextBlock)spinner.Content; - int value = Array.IndexOf(_mountains, txtBox?.Text); - if (e.Direction == SpinDirection.Increase) - value++; - else - value--; + if (spinner.Content is TextBlock txtBox) + { + int value = Array.IndexOf(_mountains, txtBox.Text); + if (e.Direction == SpinDirection.Increase) + value++; + else + value--; - if (value < 0) - value = _mountains.Length - 1; - else if (value >= _mountains.Length) - value = 0; + if (value < 0) + value = _mountains.Length - 1; + else if (value >= _mountains.Length) + value = 0; + + txtBox.Text = _mountains[value]; + } - txtBox.Text = _mountains[value]; } private readonly string[] _mountains = new[] diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs index 3e748dd6f6..2d63f1fee9 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - public void OnRepeatButtonClick(object sender, object args) + public void OnRepeatButtonClick(object? sender, object args) { repeatButtonClickCount++; var textBlock = this.Get("RepeatButtonTextBlock"); diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index 5b74e3e19e..c6aab5c4d5 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -33,7 +33,7 @@ namespace ControlCatalog.Pages } - private void TransitionChanged(object sender, SelectionChangedEventArgs e) + private void TransitionChanged(object? sender, SelectionChangedEventArgs e) { switch (_transition.SelectedIndex) { diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index eed46265ff..ef3d2bbafa 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -23,55 +23,79 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - private async void CopyText(object sender, RoutedEventArgs args) + private async void CopyText(object? sender, RoutedEventArgs args) { - await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text); + if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent) + await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty); } - private async void PasteText(object sender, RoutedEventArgs args) + private async void PasteText(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync(); + if(Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetTextAsync(); + } } - private async void CopyTextDataObject(object sender, RoutedEventArgs args) + private async void CopyTextDataObject(object? sender, RoutedEventArgs args) { - var dataObject = new DataObject(); - dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); + if (Application.Current!.Clipboard is { } clipboard) + { + var dataObject = new DataObject(); + dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); + await clipboard.SetDataObjectAsync(dataObject); + } } - private async void PasteTextDataObject(object sender, RoutedEventArgs args) + private async void PasteTextDataObject(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + } } - private async void CopyFilesDataObject(object sender, RoutedEventArgs args) + private async void CopyFilesDataObject(object? sender, RoutedEventArgs args) { - var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - if (files.Length == 0) + if (Application.Current!.Clipboard is { } clipboard) { - return; + var files = (ClipboardContent.Text ?? String.Empty) + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + if (files.Length == 0) + { + return; + } + var dataObject = new DataObject(); + dataObject.Set(DataFormats.FileNames, files); + await clipboard.SetDataObjectAsync(dataObject); } - var dataObject = new DataObject(); - dataObject.Set(DataFormats.FileNames, files); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); } - private async void PasteFilesDataObject(object sender, RoutedEventArgs args) + private async void PasteFilesDataObject(object? sender, RoutedEventArgs args) { - var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; - ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; + ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + } } private async void GetFormats(object sender, RoutedEventArgs args) { - var formats = await Application.Current.Clipboard.GetFormatsAsync(); - ClipboardContent.Text = string.Join(Environment.NewLine, formats); + if (Application.Current!.Clipboard is { } clipboard) + { + var formats = await clipboard.GetFormatsAsync(); + ClipboardContent.Text = string.Join(Environment.NewLine, formats); + } } private async void Clear(object sender, RoutedEventArgs args) { - await Application.Current.Clipboard.ClearAsync(); + if (Application.Current!.Clipboard is { } clipboard) + { + await clipboard.ClearAsync(); + } + } } } diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs index d304bf227d..6d624c9a07 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs @@ -17,7 +17,7 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - var fontComboBox = this.Find("fontComboBox"); + var fontComboBox = this.Get("fontComboBox"); fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x)); fontComboBox.SelectedIndex = 0; } diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 18069ca857..61e0ed5acb 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; @@ -18,7 +12,7 @@ namespace ControlCatalog.Pages; public partial class CompositionPage : UserControl { - private ImplicitAnimationCollection _implicitAnimations; + private ImplicitAnimationCollection? _implicitAnimations; public CompositionPage() { @@ -28,7 +22,7 @@ public partial class CompositionPage : UserControl protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - this.FindControl("Items").Items = CreateColorItems(); + this.Get("Items").Items = CreateColorItems(); } private List CreateColorItems() @@ -115,7 +109,6 @@ public partial class CompositionPage : UserControl public static void SetEnableAnimations(Border border, bool value) { - var page = border.FindAncestorOfType(); if (page == null) { @@ -127,8 +120,11 @@ public partial class CompositionPage : UserControl return; page.EnsureImplicitAnimations(); - ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = - page._implicitAnimations; + if (border.GetVisualParent() is Visual visualParent + && ElementComposition.GetElementVisual(visualParent) is CompositionVisual compositionVisual) + { + compositionVisual.ImplicitAnimations = page._implicitAnimations; + } } } @@ -150,4 +146,4 @@ public class CompositionPageColorItem { Color = color; } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs index 4d72fc5311..8bd1f4d85a 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs @@ -52,13 +52,13 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { @@ -67,20 +67,20 @@ namespace ControlCatalog.Pages } } - private void CloseFlyout(object sender, RoutedEventArgs e) + private void CloseFlyout(object? sender, RoutedEventArgs e) { _textBox.ContextFlyout?.Hide(); } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; - - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 4581642024..96fcb54650 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -35,30 +35,31 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); - e.Cancel = cancelCloseCheckBox.IsChecked ?? false; + e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { var cancelCloseCheckBox = this.FindControl("CancelOpenCheckBox"); - cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; + cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 219b7aeac4..3565d113bc 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages addButton.Click += (a, b) => collectionView3.AddNew(); } - private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e) + private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e) { e.Row.Header = e.Row.GetIndex() + 1; } @@ -74,7 +74,7 @@ namespace ControlCatalog.Pages private class ReversedStringComparer : IComparer, IComparer { - public int Compare(object x, object y) + public int Compare(object? x, object? y) { if (x is string left && y is string right) { diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 036dccde0e..67e9ef4e40 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -111,9 +111,16 @@ namespace ControlCatalog.Pages Title = "Select folder", Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null }.ShowAsync(GetWindow()); - lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); - results.Items = new [] { result }; - resultsVisible.IsVisible = result != null; + if (string.IsNullOrEmpty(result)) + { + resultsVisible.IsVisible = false; + } + else + { + lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); + results.Items = new[] { result }; + resultsVisible.IsVisible = true; + } }; this.Get diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs index 9c524a7932..bf27747154 100644 --- a/samples/RenderDemo/Pages/CustomSkiaPage.cs +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -40,14 +40,16 @@ namespace RenderDemo.Pages static Stopwatch St = Stopwatch.StartNew(); public void Render(IDrawingContextImpl context) { - var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; - if (canvas == null) + var leaseFeature = context.GetFeature(); + if (leaseFeature == null) using (var c = new DrawingContext(context, false)) { c.DrawText(_noSkia, new Point()); } else { + using var lease = leaseFeature.Lease(); + var canvas = lease.SkCanvas; canvas.Save(); // create the first shader var colors = new SKColor[] { diff --git a/samples/RenderDemo/Pages/DrawingPage.xaml b/samples/RenderDemo/Pages/DrawingPage.xaml index 7bdadf586b..a658dbc089 100644 --- a/samples/RenderDemo/Pages/DrawingPage.xaml +++ b/samples/RenderDemo/Pages/DrawingPage.xaml @@ -1,134 +1,141 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - - \ No newline at end of file + + diff --git a/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs b/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs index 92eb2e7dec..57a5c7101f 100644 --- a/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs +++ b/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs @@ -78,7 +78,7 @@ namespace RenderDemo.Pages _defaultProperties = defaultProperties; } - public TextRun? GetTextRun(int textSourceIndex) + public TextRun GetTextRun(int textSourceIndex) { if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength) { @@ -107,7 +107,7 @@ namespace RenderDemo.Pages public Control Control => _control; public override Size Size => _control.DesiredSize; public override double Baseline => 0; - public override TextRunProperties? Properties { get; } + public override TextRunProperties Properties { get; } public override void Draw(DrawingContext drawingContext, Point origin) { diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 0c72c389dc..ed5b46d398 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -8,7 +8,8 @@ using Avalonia.Input.Platform; using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Skia; +using Avalonia.Rendering.Composition; +using Avalonia.OpenGL; namespace Avalonia { @@ -16,10 +17,8 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - var options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); - return builder - .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android") + .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") .UseSkia(); } } @@ -42,9 +41,11 @@ namespace Avalonia.Android public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); - public static void Initialize(AndroidPlatformOptions options) + internal static Compositor Compositor { get; private set; } + + public static void Initialize() { - Options = options; + Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -58,16 +59,24 @@ namespace Avalonia.Android .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton(); - if (options.UseGpu) + if (Options.UseGpu) { EglPlatformOpenGlInterface.TryInitialize(); } + + if (Options.UseCompositor) + { + Compositor = new Compositor( + AvaloniaLocator.Current.GetRequiredService(), + AvaloniaLocator.Current.GetService()); + } } } public sealed class AndroidPlatformOptions { - public bool UseDeferredRendering { get; set; } = true; + public bool UseDeferredRendering { get; set; } = false; public bool UseGpu { get; set; } = true; + public bool UseCompositor { get; set; } = true; } } diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index e72f0aed90..de9149e9a1 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -14,6 +14,7 @@ namespace Avalonia.Android internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface { private Handler _handler; + private static Thread s_uiThread; public AndroidThreadingInterface() { @@ -26,46 +27,33 @@ namespace Avalonia.Android { if (interval.TotalMilliseconds < 10) interval = TimeSpan.FromMilliseconds(10); - object l = new object(); + var stopped = false; Timer timer = null; - var scheduled = false; timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + EnsureInvokeOnMainThread(() => { - if (stopped) + try { - timer.Dispose(); - return; + tick(); } - if (scheduled) - return; - scheduled = true; - EnsureInvokeOnMainThread(() => + finally { - try - { - tick(); - } - finally - { - lock (l) - { - scheduled = false; - } - } - }); - } - }, null, TimeSpan.Zero, interval); + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); return Disposable.Create(() => { - lock (l) - { - stopped = true; - timer.Dispose(); - } + stopped = true; + timer.Dispose(); }); } @@ -76,7 +64,25 @@ namespace Avalonia.Android EnsureInvokeOnMainThread(() => Signaled?.Invoke(null)); } - public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); + public bool CurrentThreadIsLoopThread + { + get + { + if (s_uiThread != null) + return s_uiThread == Thread.CurrentThread; + + var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23) + ? Looper.MainLooper.IsCurrentThread + : Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); + if (isOnMainThread) + { + s_uiThread = Thread.CurrentThread; + return true; + } + + return false; + } + } public event Action Signaled; } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index 5343b57251..33501ece06 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -47,18 +47,6 @@ namespace Avalonia.Android } } - [Obsolete("deprecated")] - public override void Invalidate(global::Android.Graphics.Rect dirty) - { - Invalidate(); - } - - [Obsolete("deprecated")] - public override void Invalidate(int l, int t, int r, int b) - { - Invalidate(); - } - public void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) { Log.Info("AVALONIA", "Surface Changed"); diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index fd97e293f9..17a0d6b63a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; + using Android.Content; using Android.Graphics; using Android.Views; @@ -19,6 +20,7 @@ using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -29,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly IFramebufferPlatformSurface _framebuffer; private readonly AndroidKeyboardEventsHelper _keyboardHelper; - private readonly AndroidTouchEventsHelper _touchHelper; + private readonly AndroidMotionEventsHelper _pointerHelper; private readonly ITextInputMethodImpl _textInputMethod; private ViewImpl _view; @@ -38,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view = new ViewImpl(avaloniaView.Context, this, placeOnTop); _textInputMethod = new AndroidInputMethod(_view); _keyboardHelper = new AndroidKeyboardEventsHelper(this); - _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, - GetAvaloniaPointFromEvent); + _pointerHelper = new AndroidMotionEventsHelper(this); _gl = GlPlatformSurface.TryCreate(this); _framebuffer = new FramebufferManager(this); @@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IEnumerable Surfaces => new object[] { _gl, _framebuffer, Handle }; public IRenderer CreateRenderer(IRenderRoot root) => - AndroidPlatform.Options.UseDeferredRendering - ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer(root); + AndroidPlatform.Options.UseCompositor + ? new CompositingRenderer(root, AndroidPlatform.Compositor) + : AndroidPlatform.Options.UseDeferredRendering + ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } + : new ImmediateRenderer(root); public virtual void Hide() { @@ -157,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform _tl.Draw(); } + protected override bool DispatchGenericPointerEvent(MotionEvent e) + { + bool callBase; + bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); + bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false; + + return result != null ? result.Value : baseResult; + } + public override bool DispatchTouchEvent(MotionEvent e) { bool callBase; - bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase); + bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); bool baseResult = callBase ? base.DispatchTouchEvent(e) : false; return result != null ? result.Value : baseResult; diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs new file mode 100644 index 0000000000..ce385ebe34 --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; + +using Android.Views; + +using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Collections.Pooled; +using Avalonia.Input; +using Avalonia.Input.Raw; + +#nullable enable + +namespace Avalonia.Android.Platform.Specific.Helpers +{ + internal class AndroidMotionEventsHelper : IDisposable + { + private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); + private static readonly float s_radiansToDegree = (float)(180f * Math.PI); + private readonly TouchDevice _touchDevice; + private readonly MouseDevice _mouseDevice; + private readonly PenDevice _penDevice; + private readonly TopLevelImpl _view; + private bool _disposed; + + public AndroidMotionEventsHelper(TopLevelImpl view) + { + _touchDevice = new TouchDevice(); + _penDevice = new PenDevice(); + _mouseDevice = new MouseDevice(); + _view = view; + } + + public bool? DispatchMotionEvent(MotionEvent e, out bool callBase) + { + callBase = true; + if (_disposed) + { + return null; + } + + var eventTime = (ulong)DateTime.Now.Millisecond; + var inputRoot = _view.InputRoot; + var actionMasked = e.ActionMasked; + var modifiers = GetModifiers(e.MetaState, e.ButtonState); + + if (actionMasked == MotionEventActions.Move) + { + for (int index = 0; index < e.PointerCount; index++) + { + var toolType = e.GetToolType(index); + var device = GetDevice(toolType); + var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move; + var point = CreatePoint(e, index); + modifiers |= GetToolModifiers(toolType); + + // ButtonState reports only mouse buttons, but not touch or stylus pointer. + if (toolType != MotionEventToolType.Mouse) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + + var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index)) + { + IntermediatePoints = new Lazy?>(() => + { + var site = e.HistorySize; + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = site; + + for (int pos = 0; pos < site; pos++) + { + s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos)); + } + + return s_intermediatePointsPooledList; + }) + }; + _view.Input(args); + } + } + else + { + var index = e.ActionIndex; + var toolType = e.GetToolType(index); + var device = GetDevice(toolType); + modifiers |= GetToolModifiers(toolType); + var point = CreatePoint(e, index); + + if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse) + { + var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll)); + var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None); + _view.Input(args); + } + else + { + var eventType = GetActionType(e, actionMasked, toolType); + if (eventType >= 0) + { + var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index)); + _view.Input(args); + } + } + } + + return true; + } + + private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState) + { + var modifiers = RawInputModifiers.None; + if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn)) + { + modifiers |= RawInputModifiers.Shift; + } + if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn)) + { + modifiers |= RawInputModifiers.Control; + } + if (metaState.HasAnyFlag(MetaKeyStates.AltOn)) + { + modifiers |= RawInputModifiers.Alt; + } + if (metaState.HasAnyFlag(MetaKeyStates.MetaOn)) + { + modifiers |= RawInputModifiers.Meta; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Primary)) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary)) + { + modifiers |= RawInputModifiers.RightMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary)) + { + modifiers |= RawInputModifiers.MiddleMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Back)) + { + modifiers |= RawInputModifiers.XButton1MouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Forward)) + { + modifiers |= RawInputModifiers.XButton2MouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary)) + { + modifiers |= RawInputModifiers.PenBarrelButton; + } + return modifiers; + } + +#pragma warning disable CA1416 // Validate platform compatibility + private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType) + { + var isTouch = toolType == MotionEventToolType.Finger; + var isMouse = toolType == MotionEventToolType.Mouse; + switch (actionMasked) + { + // DOWN + case MotionEventActions.Down when !isMouse: + case MotionEventActions.PointerDown when !isMouse: + return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown; + case MotionEventActions.ButtonPress: + return e.ActionButton switch + { + MotionEventButtonState.Back => RawPointerEventType.XButton1Down, + MotionEventButtonState.Forward => RawPointerEventType.XButton2Down, + MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown, + MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown, + MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown, + MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown, + MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown, + _ => RawPointerEventType.LeftButtonDown + }; + // UP + case MotionEventActions.Up when !isMouse: + case MotionEventActions.PointerUp when !isMouse: + return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp; + case MotionEventActions.ButtonRelease: + return e.ActionButton switch + { + MotionEventButtonState.Back => RawPointerEventType.XButton1Up, + MotionEventButtonState.Forward => RawPointerEventType.XButton2Up, + MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp, + MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp, + MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp, + MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp, + MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp, + _ => RawPointerEventType.LeftButtonUp + }; + // MOVE + case MotionEventActions.Outside: + case MotionEventActions.HoverMove: + case MotionEventActions.Move: + return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move; + // CANCEL + case MotionEventActions.Cancel: + return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow; + default: + return (RawPointerEventType)(-1); + } + } +#pragma warning restore CA1416 // Validate platform compatibility + + private IPointerDevice GetDevice(MotionEventToolType type) + { + return type switch + { + MotionEventToolType.Mouse => _mouseDevice, + MotionEventToolType.Stylus => _penDevice, + MotionEventToolType.Eraser => _penDevice, + MotionEventToolType.Finger => _touchDevice, + _ => _touchDevice + }; + } + + private RawPointerPoint CreatePoint(MotionEvent e, int index) + { + return new RawPointerPoint + { + Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling, + Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices + Twist = e.GetOrientation(index) * s_radiansToDegree + }; + } + + private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos) + { + return new RawPointerPoint + { + Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling, + Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1), + Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree + }; + } + + private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType) + { + // Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here. + return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None; + } + + public void Dispose() + { + _disposed = true; + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs deleted file mode 100644 index 6142598514..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using Android.Views; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Platform; - -namespace Avalonia.Android.Platform.Specific.Helpers -{ - public class AndroidTouchEventsHelper : IDisposable where TView : ITopLevelImpl, IAndroidView - { - private TView _view; - public bool HandleEvents { get; set; } - - public AndroidTouchEventsHelper(TView view, Func getInputRoot, Func getPointfunc) - { - this._view = view; - HandleEvents = true; - _getPointFunc = getPointfunc; - _getInputRoot = getInputRoot; - } - - private TouchDevice _touchDevice = new TouchDevice(); - private Func _getPointFunc; - private Func _getInputRoot; - - public bool? DispatchTouchEvent(MotionEvent e, out bool callBase) - { - if (!HandleEvents) - { - callBase = true; - return null; - } - - var eventTime = DateTime.Now; - - //Basic touch support - var pointerEventType = e.Action switch - { - MotionEventActions.Down => RawPointerEventType.TouchBegin, - MotionEventActions.Up => RawPointerEventType.TouchEnd, - MotionEventActions.Cancel => RawPointerEventType.TouchCancel, - _ => RawPointerEventType.TouchUpdate - }; - - if (e.Action.HasFlag(MotionEventActions.PointerDown)) - { - pointerEventType = RawPointerEventType.TouchBegin; - } - - if (e.Action.HasFlag(MotionEventActions.PointerUp)) - { - pointerEventType = RawPointerEventType.TouchEnd; - } - - for (int i = 0; i < e.PointerCount; i++) - { - //if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event - var point = _getPointFunc(e, i); - - double x = _view.View.GetX(); - double y = _view.View.GetY(); - double r = x + _view.View.Width; - double b = y + _view.View.Height; - - if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y) - { - var inputRoot = _getInputRoot(); - - var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot, - i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i)); - _view.Input(mouseEvent); - } - } - - callBase = true; - //if return false events for move and up are not received!!! - return e.Action != MotionEventActions.Up; - } - - public void Dispose() - { - HandleEvents = false; - } - } -} diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index 03b2d17e44..6bb06367de 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -172,23 +172,6 @@ namespace Avalonia.Animation set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); } } - /// - /// Obsolete: Do not use this property, use instead. - /// - /// - [Obsolete("This property has been superceded by IterationCount.")] - public string RepeatCount - { - get { return IterationCount.ToString(); } - set - { - var val = value.ToUpper(); - val = val.Replace("LOOP", "INFINITE"); - val = val.Replace("NONE", "1"); - IterationCount = IterationCount.Parse(val); - } - } - /// /// Gets the children of the . /// diff --git a/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs index b87b2681d6..9256c7be5e 100644 --- a/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs @@ -37,17 +37,4 @@ namespace Avalonia.Animation.Animators } } - [Obsolete("Use ISolidColorBrushAnimator instead")] - public class SolidColorBrushAnimator : Animator - { - public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue) - { - if (oldValue is null || newValue is null) - { - return progress >= 0.5 ? newValue : oldValue; - } - - return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); - } - } } diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 134e3b2ac7..2d7bab6cd6 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -468,41 +468,6 @@ namespace Avalonia }); } - /// - /// Subscribes to a property changed notifications for changes that originate from a - /// . - /// - /// The type of the property change sender. - /// The property changed observable. - /// Given a TTarget, returns the handler. - /// A disposable that can be used to terminate the subscription. - [Obsolete("Use overload taking Action.")] - public static IDisposable AddClassHandler( - this IObservable observable, - Func> handler) - where TTarget : class - { - return observable.Subscribe(e => SubscribeAdapter(e, handler)); - } - - /// - /// Observer method for . - /// - /// The sender type to accept. - /// The event args. - /// Given a TTarget, returns the handler. - private static void SubscribeAdapter( - AvaloniaPropertyChangedEventArgs e, - Func> handler) - where TTarget : class - { - if (e.Sender is TTarget target) - { - handler(target)(e); - } - } - private class BindingAdaptor : IBinding { private IObservable _source; diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 3937ee5658..14acc2c47f 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -30,21 +30,6 @@ namespace Avalonia _changed = new Subject>(); } - /// - /// Initializes a new instance of the class. - /// - /// The property to copy. - /// The new owner type. - /// Optional overridden metadata. - [Obsolete("Use constructor with AvaloniaProperty instead.", true)] - protected AvaloniaProperty( - AvaloniaProperty source, - Type ownerType, - AvaloniaPropertyMetadata? metadata) - : this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata) - { - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs index fb70729cf8..c4684960d6 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs @@ -140,21 +140,6 @@ namespace Avalonia.Collections } } - [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")] - public static IAvaloniaReadOnlyList CreateDerivedList( - this IAvaloniaReadOnlyList collection, - Func select) - { - var result = new AvaloniaList(); - - collection.ForEachItem( - (i, item) => result.Insert(i, select(item)), - (i, item) => result.RemoveAt(i), - () => result.Clear()); - - return result; - } - /// /// Listens for property changed events from all items in a collection. /// diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 77863e5101..d6197c50c6 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Avalonia.Collections; +using Avalonia.Controls.Templates; namespace Avalonia.Controls { @@ -29,7 +30,11 @@ namespace Avalonia.Controls public object? this[object key] { - get => _inner?[key]; + get + { + TryGetValue(key, out var value); + return value; + } set { Inner[key] = value; @@ -119,6 +124,12 @@ namespace Avalonia.Controls Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); } + public void AddDeferred(object key, Func factory) + { + Inner.Add(key, new DeferredItem(factory)); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } + public void Clear() { if (_inner?.Count > 0) @@ -143,10 +154,8 @@ namespace Avalonia.Controls public bool TryGetResource(object key, out object? value) { - if (_inner is not null && _inner.TryGetValue(key, out value)) - { + if (TryGetValue(key, out value)) return true; - } if (_mergedDictionaries != null) { @@ -165,12 +174,28 @@ namespace Avalonia.Controls public bool TryGetValue(object key, out object? value) { - if (_inner is not null) - return _inner.TryGetValue(key, out value); + if (_inner is not null && _inner.TryGetValue(key, out value)) + { + if (value is DeferredItem deffered) + { + _inner[key] = value = deffered.Factory(null) switch + { + ITemplateResult t => t.Result, + object v => v, + _ => null, + }; + } + return true; + } + value = null; return false; } + public IEnumerator> GetEnumerator() + { + return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); + } void ICollection>.Add(KeyValuePair item) { @@ -198,12 +223,17 @@ namespace Avalonia.Controls return false; } - public IEnumerator> GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal bool ContainsDeferredKey(object key) { - return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); - } + if (_inner is not null && _inner.TryGetValue(key, out var result)) + { + return result is DeferredItem; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return false; + } void IResourceProvider.AddOwner(IResourceHost owner) { @@ -258,5 +288,11 @@ namespace Avalonia.Controls } } } + + private class DeferredItem + { + public DeferredItem(Func factory) => Factory = factory; + public Func Factory { get; } + } } } diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 1758c45650..6121646107 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -132,6 +132,11 @@ namespace Avalonia.Controls { _target.OwnerChanged += OwnerChanged; _owner = _target.Owner; + + if (_owner is object) + { + _owner.ResourcesChanged += ResourcesChanged; + } } protected override void Deinitialize() diff --git a/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs new file mode 100644 index 0000000000..6bd4d735a7 --- /dev/null +++ b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Controls.Templates +{ + public interface ITemplateResult + { + public object? Result { get; } + public INameScope NameScope { get; } + } +} diff --git a/src/Avalonia.Controls/Templates/TemplateResult.cs b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs similarity index 80% rename from src/Avalonia.Controls/Templates/TemplateResult.cs rename to src/Avalonia.Base/Controls/Templates/TemplateResult.cs index 770aecc329..0e38c6c0ce 100644 --- a/src/Avalonia.Controls/Templates/TemplateResult.cs +++ b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs @@ -1,9 +1,10 @@ namespace Avalonia.Controls.Templates { - public class TemplateResult + public class TemplateResult : ITemplateResult { public T Result { get; } public INameScope NameScope { get; } + object? ITemplateResult.Result => Result; public TemplateResult(T result, INameScope nameScope) { diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index efcb7dfecb..86e4bffaa8 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -29,21 +29,6 @@ namespace Avalonia { } - /// - /// Initializes a new instance of the class. - /// - /// The property to copy. - /// The new owner type. - /// Optional overridden metadata. - [Obsolete("Use constructor with DirectPropertyBase instead.", true)] - protected DirectPropertyBase( - AvaloniaProperty source, - Type ownerType, - AvaloniaPropertyMetadata metadata) - : this(source as DirectPropertyBase ?? throw new InvalidOperationException(), ownerType, metadata) - { - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs index 19eb42a700..9b74266b09 100644 --- a/src/Avalonia.Base/EnumExtensions.cs +++ b/src/Avalonia.Base/EnumExtensions.cs @@ -8,10 +8,6 @@ namespace Avalonia /// public static class EnumExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Obsolete("This method is obsolete. Use HasAllFlags instead.")] - public static bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum - => value.HasAllFlags(flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasAllFlags(this T value, T flags) where T : unmanaged, Enum diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index a95d66346a..2ebf01bcf6 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -158,6 +158,8 @@ namespace Avalonia.Input ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers); } } + + _lastPointer = (pointer, root.PointToScreen(position)); } private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element, @@ -195,7 +197,6 @@ namespace Avalonia.Input } el = root.PointerOverElement = element; - _lastPointer = (pointer, root.PointToScreen(position)); e.RoutedEvent = InputElement.PointerEnteredEvent; diff --git a/src/Avalonia.Base/Input/PointerPoint.cs b/src/Avalonia.Base/Input/PointerPoint.cs index 71145b5cb0..c51f286053 100644 --- a/src/Avalonia.Base/Input/PointerPoint.cs +++ b/src/Avalonia.Base/Input/PointerPoint.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input /// /// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact. /// - public sealed class PointerPoint + public struct PointerPoint { public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties) { @@ -33,47 +33,47 @@ namespace Avalonia.Input /// /// Provides extended properties for a PointerPoint object. /// - public sealed class PointerPointProperties + public struct PointerPointProperties { /// /// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device. /// - public bool IsLeftButtonPressed { get; } + public bool IsLeftButtonPressed { get; } = false; /// /// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device. /// - public bool IsMiddleButtonPressed { get; } + public bool IsMiddleButtonPressed { get; } = false; /// /// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device. /// - public bool IsRightButtonPressed { get; } + public bool IsRightButtonPressed { get; } = false; /// /// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1). /// - public bool IsXButton1Pressed { get; } + public bool IsXButton1Pressed { get; } = false; /// /// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2). /// - public bool IsXButton2Pressed { get; } + public bool IsXButton2Pressed { get; } = false; /// /// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed. /// - public bool IsBarrelButtonPressed { get; } + public bool IsBarrelButtonPressed { get; } = false; /// /// Gets a value that indicates whether the input is from a pen eraser. /// - public bool IsEraser { get; } + public bool IsEraser { get; } = false; /// /// Gets a value that indicates whether the digitizer pen is inverted. /// - public bool IsInverted { get; } + public bool IsInverted { get; } = false; /// /// Gets the clockwise rotation in degrees of a pen device around its own major axis (such as when the user spins the pen in their fingers). @@ -81,7 +81,7 @@ namespace Avalonia.Input /// /// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0. /// - public float Twist { get; } + public float Twist { get; } = 0.0F; /// /// Gets a value that indicates the force that the pointer device (typically a pen/stylus) exerts on the surface of the digitizer. @@ -97,7 +97,7 @@ namespace Avalonia.Input /// /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted to the right of perpendicular, and between 0.0 and -90.0 when tilted to the left of perpendicular. The default value is 0.0. /// - public float XTilt { get; } + public float XTilt { get; } = 0.0F; /// /// Gets the plane angle between the X-Z plane and the plane that contains the X axis and the axis of the input device (typically a pen/stylus). @@ -105,14 +105,14 @@ namespace Avalonia.Input /// /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted towards the user, and between 0.0 and -90.0 when tilted away from the user. The default value is 0.0. /// - public float YTilt { get; } + public float YTilt { get; } = 0.0F; /// /// Gets the kind of pointer state change. /// - public PointerUpdateKind PointerUpdateKind { get; } + public PointerUpdateKind PointerUpdateKind { get; } = PointerUpdateKind.LeftButtonPressed; - private PointerPointProperties() + public PointerPointProperties() { } diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs index 0e4e0ed3e2..b8f6f99ae8 100644 --- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs @@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw /// public Point Position { get; set; } + /// public float Twist { get; set; } + /// public float Pressure { get; set; } + /// public float XTilt { get; set; } + /// public float YTilt { get; set; } diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs index a9b7dc8c89..37edb24cc1 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs @@ -113,24 +113,6 @@ namespace Avalonia.Interactivity { } - [Obsolete("Use overload taking Action.")] - public IDisposable AddClassHandler( - Func> handler, - RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, - bool handledEventsToo = false) - where TTarget : class, IInteractive - { - void Adapter(object? sender, RoutedEventArgs e) - { - if (sender is TTarget target && e is TEventArgs args) - { - handler(target)(args); - } - } - - return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo); - } - public IDisposable AddClassHandler( Action handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs index 143ce13a1b..88d95c81fd 100644 --- a/src/Avalonia.Base/Layout/ILayoutManager.cs +++ b/src/Avalonia.Base/Layout/ILayoutManager.cs @@ -44,17 +44,6 @@ namespace Avalonia.Layout /// void ExecuteInitialLayoutPass(); - /// - /// Executes the initial layout pass on a layout root. - /// - /// The control to lay out. - /// - /// You should not usually need to call this method explictly, the layout root will call - /// it to carry out the initial layout of the control. - /// - [Obsolete("Call ExecuteInitialLayoutPass without parameter")] - void ExecuteInitialLayoutPass(ILayoutRoot root); - /// /// Registers a control as wanting to receive effective viewport notifications. /// diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index b9ca6bfbd7..826947c39a 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -196,17 +196,6 @@ namespace Avalonia.Layout ExecuteLayoutPass(); } - [Obsolete("Call ExecuteInitialLayoutPass without parameter")] - public void ExecuteInitialLayoutPass(ILayoutRoot root) - { - if (root != _owner) - { - throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root."); - } - - ExecuteInitialLayoutPass(); - } - public void Dispose() { _disposed = true; diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 101e867d56..527b63292d 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -460,20 +460,6 @@ namespace Avalonia.Layout _effectiveViewportChanged?.Invoke(this, e); } - /// - /// Marks a property as affecting the control's measurement. - /// - /// The properties. - /// - /// After a call to this method in a control's static constructor, any change to the - /// property will cause to be called on the element. - /// - [Obsolete("Use AffectsMeasure and specify the control type.")] - protected static void AffectsMeasure(params AvaloniaProperty[] properties) - { - AffectsMeasure(properties); - } - /// /// Marks a property as affecting the control's measurement. /// @@ -497,20 +483,6 @@ namespace Avalonia.Layout } } - /// - /// Marks a property as affecting the control's arrangement. - /// - /// The properties. - /// - /// After a call to this method in a control's static constructor, any change to the - /// property will cause to be called on the element. - /// - [Obsolete("Use AffectsArrange and specify the control type.")] - protected static void AffectsArrange(params AvaloniaProperty[] properties) - { - AffectsArrange(properties); - } - /// /// Marks a property as affecting the control's arrangement. /// diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 603bb1c1c1..e71f568207 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -228,6 +228,8 @@ namespace Avalonia.Media throw new NotImplementedException(); } + public object? GetFeature(Type t) => null; + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) { throw new NotImplementedException(); diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index 5f1617d778..cf8a31c3e9 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -89,22 +89,6 @@ namespace Avalonia.Media.Imaging PlatformImpl.Dispose(); } - /// - /// Initializes a new instance of the class. - /// - /// The pixel format. - /// The pointer to the source bytes. - /// The size of the bitmap in device pixels. - /// The DPI of the bitmap. - /// The number of bytes per row. - [Obsolete("Use overload taking an AlphaFormat.")] - public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) - { - var ri = GetFactory(); - PlatformImpl = RefCountable.Create(ri - .LoadBitmap(format, ri.DefaultAlphaFormat, data, size, dpi, stride)); - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs index 1f39b1344d..1aac8efac7 100644 --- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs @@ -9,18 +9,6 @@ namespace Avalonia.Media.Imaging /// public class WriteableBitmap : Bitmap { - /// - /// Initializes a new instance of the class. - /// - /// The size of the bitmap in device pixels. - /// The DPI of the bitmap. - /// The pixel format (optional). - /// An . - [Obsolete("Use overload taking an AlphaFormat.")] - public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) - : base(CreatePlatformImpl(size, dpi, format, null)) - { - } /// /// Initializes a new instance of the class. @@ -30,7 +18,7 @@ namespace Avalonia.Media.Imaging /// The pixel format (optional). /// The alpha format (optional). /// An . - public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat) + public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) : base(CreatePlatformImpl(size, dpi, format, alphaFormat)) { } diff --git a/src/Avalonia.Base/Media/KnownColors.cs b/src/Avalonia.Base/Media/KnownColors.cs index 8f07b5f8ac..ae2fca5a60 100644 --- a/src/Avalonia.Base/Media/KnownColors.cs +++ b/src/Avalonia.Base/Media/KnownColors.cs @@ -1,10 +1,11 @@ using System; using System.Reflection; using System.Collections.Generic; +using Avalonia.SourceGenerator; namespace Avalonia.Media { - internal static class KnownColors + internal static partial class KnownColors { private static readonly IReadOnlyDictionary _knownColorNames; private static readonly IReadOnlyDictionary _knownColors; @@ -12,23 +13,25 @@ namespace Avalonia.Media private static readonly Dictionary _knownBrushes; #endif + [GenerateEnumValueDictionary()] + private static partial Dictionary GetKnownColors(); + static KnownColors() { var knownColorNames = new Dictionary(StringComparer.OrdinalIgnoreCase); var knownColors = new Dictionary(); - foreach (var field in typeof(KnownColor).GetRuntimeFields()) + foreach (var field in GetKnownColors()) { - if (field.FieldType != typeof(KnownColor)) continue; - var knownColor = (KnownColor)field.GetValue(null)!; + var knownColor = field.Value; if (knownColor == KnownColor.None) continue; - knownColorNames.Add(field.Name, knownColor); + knownColorNames.Add(field.Key, knownColor); // some known colors have the same value, so use the first if (!knownColors.ContainsKey((uint)knownColor)) { - knownColors.Add((uint)knownColor, field.Name); + knownColors.Add((uint)knownColor, field.Key); } } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index d84a509234..6aa5eeea3d 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -172,6 +172,20 @@ namespace Avalonia.Platform /// /// Custom draw operation void Custom(ICustomDrawOperation custom); + + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + object? GetFeature(Type t); + } + + public static class DrawingContextImplExtensions + { + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + public static T? GetFeature(this IDrawingContextImpl context) where T : class => + (T?)context.GetFeature(typeof(T)); } public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs index cf21e9b8b5..cec20678cf 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs @@ -12,6 +12,11 @@ public class BclStorageFile : IStorageBookmarkFile { private readonly FileInfo _fileInfo; + public BclStorageFile(string fileName) + { + _fileInfo = new FileInfo(fileName); + } + public BclStorageFile(FileInfo fileInfo) { _fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo)); @@ -27,15 +32,14 @@ public class BclStorageFile : IStorageBookmarkFile public Task GetBasicPropertiesAsync() { - var props = new StorageItemProperties(); if (_fileInfo.Exists) { - props = new StorageItemProperties( + return Task.FromResult(new StorageItemProperties( (ulong)_fileInfo.Length, _fileInfo.CreationTimeUtc, - _fileInfo.LastAccessTimeUtc); + _fileInfo.LastAccessTimeUtc)); } - return Task.FromResult(props); + return Task.FromResult(new StorageItemProperties()); } public Task GetParentAsync() diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs index cd6c8be1ae..b91e910777 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs @@ -14,6 +14,15 @@ public class BclStorageFolder : IStorageBookmarkFolder { private readonly DirectoryInfo _directoryInfo; + public BclStorageFolder(string path) + { + _directoryInfo = new DirectoryInfo(path); + if (!_directoryInfo.Exists) + { + throw new ArgumentException("Directory must exist"); + } + } + public BclStorageFolder(DirectoryInfo directoryInfo) { _directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo)); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index db773bc43c..9aa3c25425 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -71,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor if(_queuedUpdate) return; _queuedUpdate = true; - Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); + _compositor.InvokeWhenReadyForNextCommit(_update); } /// diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 45212d0f36..10360f7874 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition internal IEasing DefaultEasing { get; } private List? _invokeOnNextCommit; private readonly Stack> _invokeListPool = new(); + private Task? _lastBatchCompleted; /// /// Creates a new compositor on a specified render loop that would use a particular GPU @@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition if (_invokeOnNextCommit != null) ScheduleCommitCallbacks(batch.Completed); - return batch.Completed; + return _lastBatchCompleted = batch.Completed; } async void ScheduleCommitCallbacks(Task task) @@ -139,5 +140,15 @@ namespace Avalonia.Rendering.Composition _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit.Add(action); } + + public void InvokeWhenReadyForNextCommit(Action action) + { + if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) + Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); + else + _lastBatchCompleted.ContinueWith( + static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition), + action); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 01216d19ed..30b57883fc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW ++_drawOperationIndex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 7eb35a68ed..03859d241f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.Custom(custom); } + public object? GetFeature(Type t) => _impl.GetFeature(t); + public class VisualBrushRenderer : IVisualBrushRenderer { public CompositionDrawList? VisualBrushDrawList { get; set; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index c20594aaca..5c1ac0312c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server if (Root == null) return; + + if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + { + _renderTarget!.Dispose(); + _renderTarget = null; + } + _renderTarget ??= _renderTargetFactory(); Compositor.UpdateServerTime(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 621bc84f4a..bfc2b2d626 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server private void RenderCore() { ApplyPendingBatches(); + CompletePendingBatches(); foreach(var animation in _activeAnimations) _animationsToUpdate.Add(animation); @@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server foreach (var t in _activeTargets) t.Render(); - - CompletePendingBatches(); } public void AddCompositionTarget(ServerCompositionTarget target) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 07082e4ac3..d6766fa9b8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index d381979c1e..3c48c3469e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -324,6 +324,41 @@ namespace Avalonia.Utilities return angle * 2 * Math.PI; } + /// + /// Calculates the point of an angle on an ellipse. + /// + /// The centre point of the ellipse. + /// The x radius of the ellipse. + /// The y radius of the ellipse. + /// The angle in radians. + /// A point on the ellipse. + public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle) + { + return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y); + } + + /// + /// Gets the minimum and maximum from the specified numbers. + /// + /// The first number. + /// The second number. + /// A tuple containing the minimum and maximum of the two specified numbers. + public static (double min, double max) GetMinMax(double a, double b) + { + return a < b ? (a, b) : (b, a); + } + + /// + /// Gets the minimum and maximum from the specified number and the difference with that number. + /// + /// The initial value to use. + /// The difference for . + /// A tuple containing the minimum and maximum of the specified number and the difference with that number. + public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta) + { + return GetMinMax(initialValue, initialValue + delta); + } + private static void ThrowCannotBeGreaterThanException(T min, T max) { throw new ArgumentException($"{min} cannot be greater than {max}."); diff --git a/src/Avalonia.Base/Utilities/WeakObservable.cs b/src/Avalonia.Base/Utilities/WeakObservable.cs index 6bf1d4082f..e1c350d539 100644 --- a/src/Avalonia.Base/Utilities/WeakObservable.cs +++ b/src/Avalonia.Base/Utilities/WeakObservable.cs @@ -9,31 +9,6 @@ namespace Avalonia.Utilities /// public static class WeakObservable { - /// - /// Converts a .NET event 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. - /// Name of the event to convert. - /// - [Obsolete("Use WeakEvent-based overload")] - public static IObservable> FromEventPattern( - TTarget target, - string eventName) - where TEventArgs : EventArgs - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - _ = eventName ?? throw new ArgumentNullException(nameof(eventName)); - - return Observable.Create>(observer => - { - var handler = new Handler(observer); - WeakSubscriptionManager.Subscribe(target, eventName, handler); - return () => WeakSubscriptionManager.Unsubscribe(target, eventName, handler); - }).Publish().RefCount(); - } private class Handler : IWeakSubscriber, diff --git a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs deleted file mode 100644 index dc9e86cc32..0000000000 --- a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Avalonia.Utilities -{ - /// - /// Manages subscriptions to events using weak listeners. - /// - public static class WeakSubscriptionManager - { - /// - /// Subscribes to an event on an object using a weak subscription. - /// - /// The type of the target. - /// The type of the event arguments. - /// The event source. - /// The name of the event. - /// The subscriber. - [Obsolete("Use WeakEvent")] - public static void Subscribe(TTarget target, string eventName, IWeakSubscriber subscriber) - where TEventArgs : EventArgs - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - - var dic = SubscriptionTypeStorage.Subscribers.GetOrCreateValue(target); - - if (!dic.TryGetValue(eventName, out var sub)) - { - dic[eventName] = sub = new Subscription(dic, typeof(TTarget), target, eventName); - } - - sub.Add(new WeakReference>(subscriber)); - } - - /// - /// Unsubscribes from an event. - /// - /// The type of the event arguments. - /// The event source. - /// The name of the event. - /// The subscriber. - public static void Unsubscribe(object target, string eventName, IWeakSubscriber subscriber) - where T : EventArgs - { - if (SubscriptionTypeStorage.Subscribers.TryGetValue(target, out var dic)) - { - if (dic.TryGetValue(eventName, out var sub)) - { - sub.Remove(subscriber); - } - } - } - - private static class SubscriptionTypeStorage - where T : EventArgs - { - public static readonly ConditionalWeakTable> Subscribers - = new ConditionalWeakTable>(); - } - - private class SubscriptionDic : Dictionary> - where T : EventArgs - { - } - - private static readonly Dictionary> Accessors - = new Dictionary>(); - - private class Subscription where T : EventArgs - { - private readonly EventInfo _info; - private readonly SubscriptionDic _sdic; - private readonly object _target; - private readonly string _eventName; - private readonly Delegate _delegate; - - private WeakReference>?[] _data = new WeakReference>?[16]; - private int _count = 0; - - public Subscription(SubscriptionDic sdic, Type targetType, object target, string eventName) - { - _sdic = sdic; - _target = target; - _eventName = eventName; - if (!Accessors.TryGetValue(targetType, out var evDic)) - Accessors[targetType] = evDic = new Dictionary(); - - if (evDic.TryGetValue(eventName, out var info)) - { - _info = info; - } - else - { - var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName); - - if (ev == null) - { - throw new ArgumentException( - $"The event {eventName} was not found on {target.GetType()}."); - } - - evDic[eventName] = _info = ev; - } - - var del = new Action(OnEvent); - _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); - _info.AddMethod!.Invoke(target, new[] { _delegate }); - } - - void Destroy() - { - _info.RemoveMethod!.Invoke(_target, new[] { _delegate }); - _sdic.Remove(_eventName); - } - - public void Add(WeakReference> s) - { - if (_count == _data.Length) - { - //Extend capacity - var ndata = new WeakReference>?[_data.Length*2]; - Array.Copy(_data, ndata, _data.Length); - _data = ndata; - } - _data[_count] = s!; - _count++; - } - - public void Remove(IWeakSubscriber s) - { - var removed = false; - - for (int c = 0; c < _count; ++c) - { - var reference = _data[c]; - IWeakSubscriber? instance; - - if (reference != null && reference.TryGetTarget(out instance) && instance == s) - { - _data[c] = null; - removed = true; - } - } - - if (removed) - { - Compact(); - } - } - - void Compact() - { - int empty = -1; - for (int c = 0; c < _count; c++) - { - var r = _data[c]; - //Mark current index as first empty - if (r == null && empty == -1) - empty = c; - //If current element isn't null and we have an empty one - if (r != null && empty != -1) - { - _data[c] = null; - _data[empty] = r; - empty++; - } - } - if (empty != -1) - _count = empty; - if (_count == 0) - Destroy(); - } - - void OnEvent(object sender, T eventArgs) - { - var needCompact = false; - for(var c=0; c<_count; c++) - { - var r = _data[c]; - if (r?.TryGetTarget(out var sub) == true) - sub!.OnEvent(sender, eventArgs); - else - needCompact = true; - } - if (needCompact) - Compact(); - } - } - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 8feba116f0..69389def56 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -338,22 +338,6 @@ namespace Avalonia Contract.Requires(context != null); } - /// - /// Indicates that a property change should cause to be - /// called. - /// - /// The properties. - /// - /// This method should be called in a control's static constructor with each property - /// on the control which when changed should cause a redraw. This is similar to WPF's - /// FrameworkPropertyMetadata.AffectsRender flag. - /// - [Obsolete("Use AffectsRender and specify the control type.")] - protected static void AffectsRender(params AvaloniaProperty[] properties) - { - AffectsRender(properties); - } - /// /// Indicates that a property change should cause to be /// called. diff --git a/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs b/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs deleted file mode 100644 index e47377063f..0000000000 --- a/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Avalonia.VisualTree -{ - /// - /// Interface for controls that host their own separate visual tree, such as popups. - /// - [Obsolete] - public interface IVisualTreeHost - { - /// - /// Gets the root of the hosted visual tree. - /// - /// - /// The root of the hosted visual tree. - /// - IVisual? Root { get; } - } -} diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index 128a39c082..a1b8894604 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -413,7 +413,7 @@ namespace Avalonia.VisualTree Index = index, ZIndex = element.ZIndex, }) - .OrderBy(x => x, null) + .OrderBy(x => x, ZOrderElement.Comparer) .Select(x => x.Element!); } @@ -448,6 +448,19 @@ namespace Avalonia.VisualTree public int Index { get; set; } public int ZIndex { get; set; } + class ZOrderComparer : IComparer + { + public int Compare(ZOrderElement? x, ZOrderElement? y) + { + if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; + return x.CompareTo(y); + } + } + + public static IComparer Comparer { get; } = new ZOrderComparer(); + public int CompareTo(ZOrderElement? other) { if (other is null) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index dc9064b262..eef574d297 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -108,4 +108,5 @@ + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index a5a94a2322..0a1a9e60c6 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -11,15 +11,20 @@ Stretch="Uniform" DestinationRect="0,0,8,8"> - - - - - - + + + + + + + + + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml index 1e507a91fe..f21b03cee2 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml @@ -11,15 +11,20 @@ Stretch="Uniform" DestinationRect="0,0,8,8"> - - - - - - + + + + + + + + + + + + diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs index ff222658db..ab73ed73c7 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs @@ -12,9 +12,6 @@ namespace Avalonia.Collections { public virtual string PropertyPath => null; - [Obsolete("Use Direction property to read or override sorting direction.")] - public virtual bool Descending => Direction == ListSortDirection.Descending; - public virtual ListSortDirection Direction => ListSortDirection.Ascending; public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath); public abstract IComparer Comparer { get; } @@ -254,13 +251,6 @@ namespace Avalonia.Collections return new DataGridPathSortDescription(propertyPath, direction, null, culture); } - - [Obsolete("Use overload taking a ListSortDirection.")] - public static DataGridSortDescription FromPath(string propertyPath, bool descending, CultureInfo culture = null) - { - return new DataGridPathSortDescription(propertyPath, descending ? ListSortDirection.Descending : ListSortDirection.Ascending, null, culture); - } - public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer) { return new DataGridPathSortDescription(propertyPath, direction, comparer, null); diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d42468f47e..cacd5c5a16 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2167,7 +2167,23 @@ namespace Avalonia.Controls return desiredSize; } + + /// + protected override void OnDataContextBeginUpdate() + { + base.OnDataContextBeginUpdate(); + NotifyDataContextPropertyForAllRowCells(GetAllRows(), true); + } + + /// + protected override void OnDataContextEndUpdate() + { + base.OnDataContextEndUpdate(); + + NotifyDataContextPropertyForAllRowCells(GetAllRows(), false); + } + /// /// Raises the BeginningEdit event. /// @@ -3165,6 +3181,20 @@ namespace Avalonia.Controls } } + private static void NotifyDataContextPropertyForAllRowCells(IEnumerable rowSource, bool arg2) + { + foreach (DataGridRow row in rowSource) + { + foreach (DataGridCell cell in row.Cells) + { + if (cell.Content is StyledElement cellContent) + { + DataContextProperty.Notifying?.Invoke(cellContent, arg2); + } + } + } + } + private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode) { int itemCount = DataConnection.Count; diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 5c95932c1f..c675139831 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -392,6 +392,8 @@ namespace Avalonia.Controls private AutoCompleteSelector? _itemSelector; private AutoCompleteSelector? _textSelector; + private readonly EventHandler _populateDropDownHandler; + public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); @@ -668,6 +670,7 @@ namespace Avalonia.Controls if (newValue == TimeSpan.Zero) { + _delayTimer.Tick -= _populateDropDownHandler; _delayTimer = null; } } @@ -678,7 +681,7 @@ namespace Avalonia.Controls if (_delayTimer == null) { _delayTimer = new DispatcherTimer(); - _delayTimer.Tick += PopulateDropDown; + _delayTimer.Tick += _populateDropDownHandler; } // Set the new tick interval @@ -864,6 +867,7 @@ namespace Avalonia.Controls /// public AutoCompleteBox() { + _populateDropDownHandler = PopulateDropDown; ClearView(); } @@ -1771,10 +1775,7 @@ namespace Avalonia.Controls /// The event arguments. private void PopulateDropDown(object? sender, EventArgs e) { - if (_delayTimer != null) - { - _delayTimer.Stop(); - } + _delayTimer?.Stop(); // Update the prefix/search text. SearchText = Text; diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 2dbb5f02f9..bb838a4f3f 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -224,7 +224,7 @@ namespace Avalonia.Controls /// /// [TemplatePart(PART_ElementMonth, typeof(CalendarItem))] - [TemplatePart(PART_ElementRoot, typeof(Panel))] + [TemplatePart(PART_ElementRoot, typeof(Panel))] public class Calendar : TemplatedControl { internal const int RowsPerMonth = 7; @@ -338,14 +338,11 @@ namespace Avalonia.Controls /// The DependencyPropertyChangedEventArgs. private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e) { - if (DisplayDate != null) - { - int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today); + int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today); - if (i > -2 && i < 2) - { - UpdateMonths(); - } + if (i > -2 && i < 2) + { + UpdateMonths(); } } @@ -655,7 +652,7 @@ namespace Avalonia.Controls SelectedDatesChanged?.Invoke(this, e); } } - + internal Collection RemovedItems { get; set; } internal DateTime? LastSelectedDateInternal { get; set; } internal DateTime? LastSelectedDate @@ -914,7 +911,7 @@ namespace Avalonia.Controls o => o.DisplayDateEnd, (o, v) => o.DisplayDateEnd = v, defaultBindingMode: BindingMode.TwoWay); - + /// /// Gets or sets the last date to be displayed. /// @@ -1242,7 +1239,7 @@ namespace Avalonia.Controls { b.IsSelected = false; } - } + } } } } @@ -1278,7 +1275,7 @@ namespace Avalonia.Controls internal void OnPreviousClick() { - if (DisplayMode == CalendarMode.Month && DisplayDate != null) + if (DisplayMode == CalendarMode.Month) { DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1); if (d.HasValue) @@ -1326,7 +1323,7 @@ namespace Avalonia.Controls } internal void OnNextClick() { - if (DisplayMode == CalendarMode.Month && DisplayDate != null) + if (DisplayMode == CalendarMode.Month) { DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1); if (d.HasValue) @@ -1645,7 +1642,7 @@ namespace Avalonia.Controls { if (DisplayMode == CalendarMode.Month) { - if (LastSelectedDate.HasValue && DisplayDateInternal != null) + if (LastSelectedDate.HasValue) { // If a blackout day is inactive, when clicked on it, the // previous inactive day which is not a blackout day can get @@ -1897,24 +1894,21 @@ namespace Avalonia.Controls { case CalendarMode.Month: { - if (DisplayDate != null) - { - DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1); + DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1); - if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0) - { - // since DisplayDate is not equal to - // DateTime.MaxValue we are sure selectedDate is\ - // not null - selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value; - selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value; - } - else - { - selectedDate = DateTime.MaxValue; - } - ProcessSelection(shift, selectedDate, null); + if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0) + { + // since DisplayDate is not equal to + // DateTime.MaxValue we are sure selectedDate is\ + // not null + selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value; + selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value; } + else + { + selectedDate = DateTime.MaxValue; + } + ProcessSelection(shift, selectedDate, null); break; } case CalendarMode.Year: @@ -2026,7 +2020,6 @@ namespace Avalonia.Controls focusDate = DisplayDate; LastSelectedDate = DisplayDate; } - Debug.Assert(focusDate != null, "focusDate should not be null!"); FocusButton = FindDayButtonFromDay(focusDate); if (FocusButton != null) @@ -2091,17 +2084,17 @@ namespace Avalonia.Controls static Calendar() { - IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnIsEnabledChanged(e)); - FirstDayOfWeekProperty.Changed.AddClassHandler((x,e) => x.OnFirstDayOfWeekChanged(e)); - IsTodayHighlightedProperty.Changed.AddClassHandler((x,e) => x.OnIsTodayHighlightedChanged(e)); - DisplayModeProperty.Changed.AddClassHandler((x,e) => x.OnDisplayModePropertyChanged(e)); - SelectionModeProperty.Changed.AddClassHandler((x,e) => x.OnSelectionModeChanged(e)); - SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e)); - DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e)); - DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e)); - DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e)); - KeyDownEvent.AddClassHandler((x,e) => x.Calendar_KeyDown(e)); - KeyUpEvent.AddClassHandler((x,e) => x.Calendar_KeyUp(e)); + IsEnabledProperty.Changed.AddClassHandler((x, e) => x.OnIsEnabledChanged(e)); + FirstDayOfWeekProperty.Changed.AddClassHandler((x, e) => x.OnFirstDayOfWeekChanged(e)); + IsTodayHighlightedProperty.Changed.AddClassHandler((x, e) => x.OnIsTodayHighlightedChanged(e)); + DisplayModeProperty.Changed.AddClassHandler((x, e) => x.OnDisplayModePropertyChanged(e)); + SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e)); + SelectedDateProperty.Changed.AddClassHandler((x, e) => x.OnSelectedDateChanged(e)); + DisplayDateProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateChanged(e)); + DisplayDateStartProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateStartChanged(e)); + DisplayDateEndProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateEndChanged(e)); + KeyDownEvent.AddClassHandler((x, e) => x.Calendar_KeyDown(e)); + KeyUpEvent.AddClassHandler((x, e) => x.Calendar_KeyUp(e)); } /// diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 4c958c83b7..75e9f52621 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -4,7 +4,6 @@ // All other rights reserved. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Avalonia.Collections.Pooled; @@ -353,7 +352,6 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - Debug.Assert(Owner.DisplayDate != null, "The Owner Calendar's DisplayDate should not be null!"); _currentMonth = Owner.DisplayDateInternal; } else @@ -361,17 +359,14 @@ namespace Avalonia.Controls.Primitives _currentMonth = DateTime.Today; } - if (_currentMonth != null) - { - SetMonthModeHeaderButton(); - SetMonthModePreviousButton(_currentMonth); - SetMonthModeNextButton(_currentMonth); + SetMonthModeHeaderButton(); + SetMonthModePreviousButton(_currentMonth); + SetMonthModeNextButton(_currentMonth); - if (MonthView != null) - { - SetDayTitles(); - SetCalendarDayButtons(_currentMonth); - } + if (MonthView != null) + { + SetDayTitles(); + SetCalendarDayButtons(_currentMonth); } } private void SetMonthModeHeaderButton() @@ -592,7 +587,6 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - Debug.Assert(Owner.SelectedMonth != null, "The Owner Calendar's SelectedMonth should not be null!"); _currentMonth = (DateTime)Owner.SelectedMonth; } else @@ -600,16 +594,13 @@ namespace Avalonia.Controls.Primitives _currentMonth = DateTime.Today; } - if (_currentMonth != null) - { - SetYearModeHeaderButton(); - SetYearModePreviousButton(); - SetYearModeNextButton(); + SetYearModeHeaderButton(); + SetYearModePreviousButton(); + SetYearModeNextButton(); - if (YearView != null) - { - SetMonthButtonsForYearMode(); - } + if (YearView != null) + { + SetMonthButtonsForYearMode(); } } private void SetYearModeHeaderButton() @@ -660,7 +651,6 @@ namespace Avalonia.Controls.Primitives childButton.IsCalendarButtonFocused = false; } - Debug.Assert(Owner.DisplayDateInternal != null, "The Owner Calendar's DisplayDateInternal should not be null!"); childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0); if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0) @@ -685,7 +675,6 @@ namespace Avalonia.Controls.Primitives if (Owner != null) { - Debug.Assert(Owner.SelectedYear != null, "The owning Calendar's selected year should not be null!"); selectedYear = Owner.SelectedYear; _currentMonth = (DateTime)Owner.SelectedMonth; } @@ -695,19 +684,16 @@ namespace Avalonia.Controls.Primitives selectedYear = DateTime.Today; } - if (_currentMonth != null) - { - int decade = DateTimeHelper.DecadeOfDate(selectedYear); - int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear); + int decade = DateTimeHelper.DecadeOfDate(selectedYear); + int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear); - SetDecadeModeHeaderButton(decade, decadeEnd); - SetDecadeModePreviousButton(decade); - SetDecadeModeNextButton(decadeEnd); + SetDecadeModeHeaderButton(decade, decadeEnd); + SetDecadeModePreviousButton(decade); + SetDecadeModeNextButton(decadeEnd); - if (YearView != null) - { - SetYearButtons(decade, decadeEnd); - } + if (YearView != null) + { + SetYearButtons(decade, decadeEnd); } } internal void UpdateYearViewSelection(CalendarButton calendarButton) @@ -822,22 +808,15 @@ namespace Avalonia.Controls.Primitives { if (Owner.DisplayMode == CalendarMode.Month) { - if (Owner.DisplayDate != null) - { - d = Owner.DisplayDateInternal; - Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1); - } + d = Owner.DisplayDateInternal; + Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1); Owner.DisplayMode = CalendarMode.Year; } else { Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!"); - - if (Owner.SelectedMonth != null) - { - d = Owner.SelectedMonth; - Owner.SelectedYear = new DateTime(d.Year, d.Month, 1); - } + d = Owner.SelectedMonth; + Owner.SelectedYear = new DateTime(d.Year, d.Month, 1); Owner.DisplayMode = CalendarMode.Decade; } } diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs index c7c35718e0..f4bc2528ba 100644 --- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs @@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives } else { - if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item)) + if (DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item)) { removedItems.Add(this[index]); base.SetItem(index, item); diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 083182a370..524362fcf9 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Styling; @@ -104,7 +105,6 @@ namespace Avalonia.Controls private static readonly HashSet _loadedQueue = new HashSet(); private static readonly HashSet _loadedProcessingQueue = new HashSet(); - private bool _isAttachedToVisualTree = false; private bool _isLoaded = false; private DataTemplates? _dataTemplates; private IControl? _focusAdorner; @@ -347,7 +347,7 @@ namespace Avalonia.Controls internal void OnLoadedCore() { if (_isLoaded == false && - _isAttachedToVisualTree) + ((ILogical)this).IsAttachedToLogicalTree) { _isLoaded = true; OnLoaded(); @@ -395,7 +395,6 @@ namespace Avalonia.Controls protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTreeCore(e); - _isAttachedToVisualTree = true; InitializeIfNeeded(); @@ -406,7 +405,6 @@ namespace Avalonia.Controls protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); - _isAttachedToVisualTree = false; OnUnloadedCore(); } diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs index ae920dac7e..82e75d9c34 100644 --- a/src/Avalonia.Controls/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Avalonia.Data; using Avalonia.Data.Converters; namespace Avalonia.Controls.Converters; @@ -15,13 +14,17 @@ public class StringFormatConverter : IMultiValueConverter { public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { - try + if (values[0] is string format) { - return string.Format((string)values[0]!, values.Skip(1).ToArray()); - } - catch (Exception e) - { - return new BindingNotification(e, BindingErrorType.Error); + try + { + return string.Format(format, values.Skip(1).ToArray()); + } + catch + { + return AvaloniaProperty.UnsetValue; + } } + return AvaloniaProperty.UnsetValue; } } diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs index ddd4e57a40..ddaed6bb0d 100644 --- a/src/Avalonia.Controls/DesktopApplicationExtensions.cs +++ b/src/Avalonia.Controls/DesktopApplicationExtensions.cs @@ -8,8 +8,6 @@ namespace Avalonia.Controls { public static class DesktopApplicationExtensions { - [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)] - public static void Run(this Application app) => throw new NotSupportedException(); /// /// On desktop-style platforms runs the application's main loop until closable is closed diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs deleted file mode 100644 index ee27aa7ec1..0000000000 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Avalonia.Controls.Shapes; -using Avalonia.Media; -using Avalonia.Metadata; - -namespace Avalonia.Controls -{ - [Obsolete("Use Image control with DrawingImage source")] - public class DrawingPresenter : Control - { - static DrawingPresenter() - { - AffectsMeasure(DrawingProperty); - AffectsRender(DrawingProperty); - } - - public static readonly StyledProperty DrawingProperty = - AvaloniaProperty.Register(nameof(Drawing)); - - public static readonly StyledProperty StretchProperty = - AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); - - [Content] - public Drawing Drawing - { - get => GetValue(DrawingProperty); - set => SetValue(DrawingProperty, value); - } - - public Stretch Stretch - { - get => GetValue(StretchProperty); - set => SetValue(StretchProperty, value); - } - - private Matrix _transform = Matrix.Identity; - - protected override Size MeasureOverride(Size availableSize) - { - if (Drawing == null) return new Size(); - - var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch); - - _transform = transform; - - return size; - } - - public override void Render(DrawingContext context) - { - if (Drawing != null) - { - using (context.PushPreTransform(_transform)) - using (context.PushClip(new Rect(Bounds.Size))) - { - Drawing.Draw(context); - } - } - } - } -} diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index d7022b80ce..0e2f8e7d0c 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -180,7 +180,7 @@ namespace Avalonia.Controls return "Auto"; } - string s = _value.ToString(); + string s = _value.ToString(CultureInfo.InvariantCulture); return IsStar ? s + "*" : s; } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 79285bb86b..80b5259a53 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -157,7 +158,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs index 9eb3b140f6..ef14d0b477 100644 --- a/src/Avalonia.Controls/LoggingExtensions.cs +++ b/src/Avalonia.Controls/LoggingExtensions.cs @@ -1,21 +1,10 @@ -using System; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Logging; namespace Avalonia { public static class LoggingExtensions { - [Obsolete("Use LogToTrace")] - public static T LogToDebug( - this T builder, - LogEventLevel level = LogEventLevel.Warning, - params string[] areas) - where T : AppBuilderBase, new() - { - return LogToTrace(builder, level, areas); - } - /// /// Logs Avalonia events to the sink. /// diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 265e7119eb..d22fdb2f84 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -186,13 +186,6 @@ namespace Avalonia.Controls /// public event EventHandler? Click; - [Obsolete("Use Click event.")] - public event EventHandler Clicked - { - add => Click += value; - remove => Click -= value; - } - void INativeMenuItemExporterEventsImplBridge.RaiseClicked() { Click?.Invoke(this, new EventArgs()); diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs index 49b36e714d..97278130b1 100644 --- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs +++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs @@ -1,16 +1,7 @@ -using System; - -namespace Avalonia.Controls +namespace Avalonia.Controls { - - [Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")] - public class NativeMenuItemSeperator : NativeMenuItemSeparator - { - } - public class NativeMenuItemSeparator : NativeMenuItemBase { - [Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)] - public string Header => "-"; + } } diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 2449f4c15c..630276767a 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -102,15 +102,12 @@ namespace Avalonia.Controls.Notifications Content = content }; - if (notification != null) + notificationControl.NotificationClosed += (sender, args) => { - notificationControl.NotificationClosed += (sender, args) => - { - notification.OnClose?.Invoke(); + notification?.OnClose?.Invoke(); - _items?.Remove(sender); - }; - } + _items?.Remove(sender); + }; notificationControl.PointerPressed += (sender, args) => { diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 705e68e3ea..dc2b2cd7cc 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -45,14 +45,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(ClipValueToMinMax), updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b); - /// - /// Defines the property. - /// - [Obsolete] - public static readonly DirectProperty CultureInfoProperty = - AvaloniaProperty.RegisterDirect(nameof(CultureInfo), o => o.CultureInfo, - (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture); - /// /// Defines the property. /// @@ -187,21 +179,6 @@ namespace Avalonia.Controls set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); } } - /// - /// Gets or sets the current CultureInfo. - /// - [Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")] - public CultureInfo? CultureInfo - { - get { return _cultureInfo; } - set - { - SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); - //Set and Raise the NumberFormatProperty when CultureInfo is changed. - SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat); - } - } - /// /// Gets or sets the current NumberFormatInfo /// @@ -335,9 +312,6 @@ namespace Avalonia.Controls /// static NumericUpDown() { -#pragma warning disable CS0612 // Type or member is obsolete - CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged); -#pragma warning restore CS0612 // Type or member is obsolete NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged); FormatStringProperty.Changed.Subscribe(FormatStringChanged); IncrementProperty.Changed.Subscribe(IncrementChanged); @@ -416,19 +390,6 @@ namespace Avalonia.Controls } } - /// - /// Called when the property value changed. - /// - /// The old value. - /// The new value. - protected virtual void OnCultureInfoChanged(CultureInfo? oldValue, CultureInfo? newValue) - { - if (IsInitialized) - { - SyncTextAndValueProperties(false, null); - } - } - /// /// Called when the property value changed. /// @@ -729,20 +690,6 @@ namespace Avalonia.Controls } } - /// - /// Called when the property value changed. - /// - /// The event args. - private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is NumericUpDown upDown) - { - var oldValue = (CultureInfo?)e.OldValue; - var newValue = (CultureInfo?)e.NewValue; - upDown.OnCultureInfoChanged(oldValue, newValue); - } - } - /// /// Called when the property value changed. /// diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 630d2d8efb..e1f6db9c60 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls.Platform _priority = priority; _interval = interval; _tick = tick; - _timer = new Timer(OnTimer, null, interval, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(OnTimer, null, interval, Timeout.InfiniteTimeSpan); _handle = GCHandle.Alloc(_timer); } @@ -57,7 +57,7 @@ namespace Avalonia.Controls.Platform if (_timer == null) return; _tick(); - _timer?.Change(_interval, TimeSpan.FromMilliseconds(-1)); + _timer?.Change(_interval, Timeout.InfiniteTimeSpan); }); } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 39a512a773..4db2ec6d50 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -187,7 +187,7 @@ namespace Avalonia.Controls.Presenters break; case NotifyCollectionChangedAction.Remove: - if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) || + if (e.OldStartingIndex < NextIndex || panel.Children.Count > ItemCount) { RecycleContainersOnRemove(); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3573ad9aaa..f71a0c6f48 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -22,9 +22,7 @@ namespace Avalonia.Controls.Primitives /// /// Displays a popup window. /// -#pragma warning disable CS0612 // Type or member is obsolete - public class Popup : Control, IVisualTreeHost, IPopupHostProvider -#pragma warning restore CS0612 // Type or member is obsolete + public class Popup : Control, IPopupHostProvider { public static readonly StyledProperty WindowManagerAddShadowHintProperty = AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), false); @@ -90,14 +88,6 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementTargetProperty = AvaloniaProperty.Register(nameof(PlacementTarget)); -#pragma warning disable 618 - /// - /// Defines the property. - /// - public static readonly StyledProperty ObeyScreenEdgesProperty = - AvaloniaProperty.Register(nameof(ObeyScreenEdges), true); -#pragma warning restore 618 - public static readonly StyledProperty OverlayDismissEventPassThroughProperty = AvaloniaProperty.Register(nameof(OverlayDismissEventPassThrough)); @@ -125,17 +115,6 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty VerticalOffsetProperty = AvaloniaProperty.Register(nameof(VerticalOffset)); - /// - /// Defines the property. - /// - [Obsolete("Use IsLightDismissEnabledProperty")] - public static readonly DirectProperty StaysOpenProperty = - AvaloniaProperty.RegisterDirect( - nameof(StaysOpen), - o => o.StaysOpen, - (o, v) => o.StaysOpen = v, - true); - /// /// Defines the property. /// @@ -301,13 +280,6 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementTargetProperty, value); } } - [Obsolete("This property has no effect")] - public bool ObeyScreenEdges - { - get => GetValue(ObeyScreenEdgesProperty); - set => SetValue(ObeyScreenEdgesProperty, value); - } - /// /// Gets or sets a value indicating whether the event that closes the popup is passed /// through to the parent window. @@ -352,17 +324,6 @@ namespace Avalonia.Controls.Primitives set { SetValue(VerticalOffsetProperty, value); } } - /// - /// Gets or sets a value indicating whether the popup should stay open when the popup is - /// pressed or loses focus. - /// - [Obsolete("Use IsLightDismissEnabled")] - public bool StaysOpen - { - get => !IsLightDismissEnabled; - set => IsLightDismissEnabled = !value; - } - /// /// Gets or sets whether this popup appears on top of all other windows /// @@ -372,11 +333,6 @@ namespace Avalonia.Controls.Primitives set { SetValue(TopmostProperty, value); } } - /// - /// Gets the root of the popup window. - /// - IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot; - IPopupHost? IPopupHostProvider.PopupHost => Host; event Action? IPopupHostProvider.PopupHostChanged diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index dc52cc3ae2..2441da8920 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -297,9 +297,6 @@ namespace Avalonia.Controls.Primitives var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); -#pragma warning disable CS0618 // Type or member is obsolete - OnTemplateApplied(e); -#pragma warning restore CS0618 // Type or member is obsolete RaiseEvent(e); } @@ -378,15 +375,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Called when the control's template is applied. - /// - /// The event args. - [Obsolete("Use OnApplyTemplate")] - protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e) - { - } - /// /// Called when the property changes. /// diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 91114628ee..1e406157d7 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -251,11 +251,9 @@ namespace Avalonia.Controls TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% -#pragma warning disable CS0618 // Type or member is obsolete // Remove these properties when we switch to fluent as default and removed the old one. IndeterminateStartingOffset = -dim; IndeterminateEndingOffset = dim; -#pragma warning restore CS0618 // Type or member is obsolete var padding = Padding; var rectangle = new RectangleGeometry( diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 1125f0eb0e..ab8e064028 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -36,7 +36,10 @@ namespace Avalonia.Controls foreach (Layoutable child in Children) { if (child == null) + { continue; + } + var node = _childGraph.AddNode(child); node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); @@ -51,17 +54,18 @@ namespace Avalonia.Controls node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); - } + _childGraph.Measure(availableSize); _childGraph.Reset(false); - var calcWidth = Width.IsNaN() && HorizontalAlignment != HorizontalAlignment.Stretch; - var calcHeight = Height.IsNaN() && VerticalAlignment != VerticalAlignment.Stretch; + var calcWidth = Width.IsNaN() && (HorizontalAlignment != HorizontalAlignment.Stretch); + var calcHeight = Height.IsNaN() && (VerticalAlignment != VerticalAlignment.Stretch); var boundingSize = _childGraph.GetBoundingSize(calcWidth, calcHeight); _childGraph.Reset(); _childGraph.Measure(boundingSize); + return boundingSize; } @@ -171,6 +175,7 @@ namespace Avalonia.Controls prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width); prevNode.HorizontalOffsetFlag = true; } + if (node.VerticalOffsetFlag) { prevNode.VerticalOffsetFlag = true; @@ -186,6 +191,7 @@ namespace Avalonia.Controls prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); prevNode.VerticalOffsetFlag = true; } + if (node.HorizontalOffsetFlag) { prevNode.HorizontalOffsetFlag = true; @@ -269,16 +275,16 @@ namespace Avalonia.Controls MeasureChild(node); continue; } - + if (node.OutgoingNodes.All(item => item.Measured)) { MeasureChild(node); continue; } - + if (!set.Add(node.Element)) throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - + Measure(node.OutgoingNodes, set); if (!node.Measured) @@ -507,8 +513,11 @@ namespace Avalonia.Controls boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height)); } - boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width); - boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height); + var availableWidth = double.IsInfinity(AvailableSize.Width) ? boundingSize.Width : AvailableSize.Width; + var availableHeight = double.IsInfinity(AvailableSize.Height) ? boundingSize.Height : AvailableSize.Height; + + boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : availableWidth); + boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : availableHeight); return boundingSize; } } diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index a88e0fd3d8..27578ddc78 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -77,10 +77,8 @@ namespace Avalonia.Controls.Remote _bitmap.PixelSize.Height != _lastFrame.Height) { _bitmap?.Dispose(); -#pragma warning disable CS0618 // Type or member is obsolete _bitmap = new WriteableBitmap(new PixelSize(_lastFrame.Width, _lastFrame.Height), new Vector(96, 96), fmt); -#pragma warning restore CS0618 // Type or member is obsolete } using (var l = _bitmap.Lock()) { diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 1f8abbc30d..3c902fa16a 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -381,9 +381,7 @@ namespace Avalonia.Controls var hit = TextLayout.HitTestPoint(point); var index = hit.TextPosition; -#pragma warning disable CS0618 // Type or member is obsolete switch (e.ClickCount) -#pragma warning restore CS0618 // Type or member is obsolete { case 1: if (clickToSelect) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index 5ebb321f9b..de3814f215 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -1,8 +1,12 @@ using System; using Avalonia.Media; +using Avalonia.Utilities; namespace Avalonia.Controls.Shapes { + /// + /// Represents a circular or elliptical arc (a segment of a curve). + /// public class Arc : Shape { /// @@ -19,8 +23,12 @@ namespace Avalonia.Controls.Shapes static Arc() { - StrokeThicknessProperty.OverrideDefaultValue(1); - AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty); + StrokeThicknessProperty.OverrideDefaultValue(1.0d); + AffectsGeometry( + BoundsProperty, + StrokeThicknessProperty, + StartAngleProperty, + SweepAngleProperty); } /// @@ -42,10 +50,11 @@ namespace Avalonia.Controls.Shapes set => SetValue(SweepAngleProperty, value); } + /// protected override Geometry CreateDefiningGeometry() { - var angle1 = DegreesToRad(StartAngle); - var angle2 = angle1 + DegreesToRad(SweepAngle); + var angle1 = MathUtilities.Deg2Rad(StartAngle); + var angle2 = angle1 + MathUtilities.Deg2Rad(SweepAngle); var startAngle = Math.Min(angle1, angle2); var sweepAngle = Math.Max(angle1, angle2); @@ -80,24 +89,25 @@ namespace Avalonia.Controls.Shapes var arcGeometry = new StreamGeometry(); - using (var ctx = arcGeometry.Open()) + using (StreamGeometryContext context = arcGeometry.Open()) { - ctx.BeginFigure(startPoint, false); - ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, + context.BeginFigure(startPoint, false); + context.ArcTo( + endPoint, + new Size(radiusX, radiusY), + rotationAngle: angleGap, + isLargeArc: angleGap >= Math.PI, SweepDirection.Clockwise); - ctx.EndFigure(false); + context.EndFigure(false); } return arcGeometry; } } - static double DegreesToRad(double inAngle) => - inAngle * Math.PI / 180; + private static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); - static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); - - static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => + private static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); } } diff --git a/src/Avalonia.Controls/Shapes/Sector.cs b/src/Avalonia.Controls/Shapes/Sector.cs new file mode 100644 index 0000000000..a9b7e8939b --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Sector.cs @@ -0,0 +1,97 @@ +using System; +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Shapes +{ + /// + /// Represents a circular or elliptical sector (a pie-shaped closed region of a circle or ellipse). + /// + public class Sector : Shape + { + /// + /// Defines the property. + /// + public static readonly StyledProperty StartAngleProperty = + AvaloniaProperty.Register(nameof(StartAngle), 0.0d); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SweepAngleProperty = + AvaloniaProperty.Register(nameof(SweepAngle), 0.0d); + + /// + /// Gets or sets the angle at which the sector's arc starts, in degrees. + /// + public double StartAngle + { + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); + } + + /// + /// Gets or sets the angle, in degrees, added to the defining where the sector's arc ends. + /// A positive value is clockwise, negative is counter-clockwise. + /// + public double SweepAngle + { + get => GetValue(SweepAngleProperty); + set => SetValue(SweepAngleProperty, value); + } + + static Sector() + { + StrokeThicknessProperty.OverrideDefaultValue(1.0d); + AffectsGeometry( + BoundsProperty, + StrokeThicknessProperty, + StartAngleProperty, + SweepAngleProperty); + } + + /// + protected override Geometry? CreateDefiningGeometry() + { + Rect rect = new Rect(Bounds.Size); + Rect deflatedRect = rect.Deflate(StrokeThickness * 0.5d); + + if (SweepAngle >= 360.0d || SweepAngle <= -360.0d) + { + return new EllipseGeometry(deflatedRect); + } + + if (SweepAngle == 0.0d) + { + return new StreamGeometry(); + } + + (double startAngle, double endAngle) = MathUtilities.GetMinMaxFromDelta( + MathUtilities.Deg2Rad(StartAngle), + MathUtilities.Deg2Rad(SweepAngle)); + + Point centre = new Point(rect.Width * 0.5d, rect.Height * 0.5d); + double radiusX = deflatedRect.Width * 0.5d; + double radiusY = deflatedRect.Height * 0.5d; + Point startCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, startAngle); + Point endCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, endAngle); + Size size = new Size(radiusX, radiusY); + + var streamGeometry = new StreamGeometry(); + using (StreamGeometryContext context = streamGeometry.Open()) + { + context.BeginFigure(startCurvePoint, false); + context.ArcTo( + endCurvePoint, + size, + rotationAngle: 0.0d, + isLargeArc: Math.Abs(SweepAngle) > 180.0d, + SweepDirection.Clockwise); + context.LineTo(centre); + context.EndFigure(true); + } + + return streamGeometry; + } + } +} diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs index f3fb4d9a6d..ee41b94b5a 100644 --- a/src/Avalonia.Controls/SystemDialog.cs +++ b/src/Avalonia.Controls/SystemDialog.cs @@ -32,13 +32,6 @@ namespace Avalonia.Controls [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] public abstract class FileSystemDialog : SystemDialog { - [Obsolete("Use Directory")] - public string? InitialDirectory - { - get => Directory; - set => Directory = value; - } - /// /// Gets or sets the initial directory that will be displayed when the file system dialog /// is opened. @@ -87,7 +80,7 @@ namespace Avalonia.Controls DefaultExtension = DefaultExtension, FileTypeChoices = Filters?.Select(f => new FilePickerFileType(f.Name!) { Patterns = f.Extensions.Select(e => $"*.{e}").ToArray() }).ToArray(), Title = Title, - SuggestedStartLocation = InitialDirectory is { } directory + SuggestedStartLocation = Directory is { } directory ? new BclStorageFolder(new System.IO.DirectoryInfo(directory)) : null, ShowOverwritePrompt = ShowOverwritePrompt @@ -129,7 +122,7 @@ namespace Avalonia.Controls AllowMultiple = AllowMultiple, FileTypeFilter = Filters?.Select(f => new FilePickerFileType(f.Name!) { Patterns = f.Extensions.Select(e => $"*.{e}").ToArray() }).ToArray(), Title = Title, - SuggestedStartLocation = InitialDirectory is { } directory + SuggestedStartLocation = Directory is { } directory ? new BclStorageFolder(new System.IO.DirectoryInfo(directory)) : null }; @@ -142,13 +135,6 @@ namespace Avalonia.Controls [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] public class OpenFolderDialog : FileSystemDialog { - [Obsolete("Use Directory")] - public string? DefaultDirectory - { - get => Directory; - set => Directory = value; - } - /// /// Shows the open folder dialog. /// @@ -170,7 +156,7 @@ namespace Avalonia.Controls return new FolderPickerOpenOptions { Title = Title, - SuggestedStartLocation = InitialDirectory is { } directory + SuggestedStartLocation = Directory is { } directory ? new BclStorageFolder(new System.IO.DirectoryInfo(directory)) : null }; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 20b11b3060..77880773b6 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1165,9 +1165,7 @@ namespace Avalonia.Controls SetAndRaise(CaretIndexProperty, ref _caretIndex, index); -#pragma warning disable CS0618 // Type or member is obsolete switch (e.ClickCount) -#pragma warning restore CS0618 // Type or member is obsolete { case 1: if (clickToSelect) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1c1df84431..47fc9d7988 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -404,9 +404,6 @@ namespace Avalonia.Controls LayoutManager?.Dispose(); } - [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] - protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); - /// /// Handles a resize notification from . /// diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 451e234653..70b21b7248 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -84,13 +84,19 @@ public class TransitioningContentControl : ContentControl _lastTransitionCts?.Cancel(); _lastTransitionCts = new CancellationTokenSource(); + var localToken = _lastTransitionCts.Token; if (PageTransition != null) - await PageTransition.Start(this, null, true, _lastTransitionCts.Token); + await PageTransition.Start(this, null, true, localToken); + + if (localToken.IsCancellationRequested) + { + return; + } CurrentContent = content; if (PageTransition != null) - await PageTransition.Start(null, this, true, _lastTransitionCts.Token); + await PageTransition.Start(null, this, true, localToken); } } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b2a188a2ea..7359f3cade 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -529,7 +529,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9b4bb51aca..1f3b10c0cc 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -79,16 +79,6 @@ namespace Avalonia.Controls public static readonly StyledProperty SizeToContentProperty = AvaloniaProperty.Register(nameof(SizeToContent)); - /// - /// Enables or disables system window decorations (title bar, buttons, etc) - /// - [Obsolete("Use SystemDecorationsProperty instead")] - public static readonly DirectProperty HasSystemDecorationsProperty = - AvaloniaProperty.RegisterDirect( - nameof(HasSystemDecorations), - o => o.HasSystemDecorations, - (o, v) => o.HasSystemDecorations = v); - /// /// Defines the property. /// @@ -289,25 +279,6 @@ namespace Avalonia.Controls set { SetValue(TitleProperty, value); } } - /// - /// Enables or disables system window decorations (title bar, buttons, etc) - /// - [Obsolete("Use SystemDecorations instead")] - public bool HasSystemDecorations - { - get => SystemDecorations == SystemDecorations.Full; - set - { - var oldValue = HasSystemDecorations; - - if (oldValue != value) - { - SystemDecorations = value ? SystemDecorations.Full : SystemDecorations.None; - RaisePropertyChanged(HasSystemDecorationsProperty, oldValue, value); - } - } - } - /// /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border). /// @@ -985,9 +956,6 @@ namespace Avalonia.Controls Owner = null; } - [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] - protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); - /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { @@ -1033,19 +1001,9 @@ namespace Avalonia.Controls base.OnPropertyChanged(change); if (change.Property == SystemDecorationsProperty) { - var (typedOldValue, typedNewValue) = change.GetOldAndNewValue(); + var (_, typedNewValue) = change.GetOldAndNewValue(); PlatformImpl?.SetSystemDecorations(typedNewValue); - - var o = typedOldValue == SystemDecorations.Full; - var n = typedNewValue == SystemDecorations.Full; - - if (o != n) - { -#pragma warning disable CS0618 // Type or member is obsolete - RaisePropertyChanged(HasSystemDecorationsProperty, o, n); -#pragma warning restore CS0618 // Type or member is obsolete - } } } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cb68c1f6e1..89483cd566 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -94,9 +94,6 @@ namespace Avalonia.Controls public Screens Screens { get; private set; } - [Obsolete("No longer used. Always returns false.")] - protected bool AutoSizing => false; - /// /// Gets or sets the owner of the window. /// @@ -169,9 +166,6 @@ namespace Avalonia.Controls } } - [Obsolete("No longer used. Has no effect.")] - protected IDisposable BeginAutoSizing() => Disposable.Empty; - /// /// Ensures that the window is initialized. /// @@ -226,9 +220,6 @@ namespace Avalonia.Controls } } - [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] - protected override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); - /// /// Handles a resize notification from . /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs index fb57058ae9..4153d2d38c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -3,6 +3,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Platform.Storage; +using Avalonia.Platform.Storage.FileIO; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Diagnostics.Screenshots @@ -59,24 +61,25 @@ namespace Avalonia.Diagnostics.Screenshots protected async override Task GetStream(IControl control) { Stream? output = default; - var result = await new SaveFileDialog() + var result = await GetWindow(control).StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { + SuggestedStartLocation = new BclStorageFolder(new DirectoryInfo(ScreenshotsRoot)), Title = Title, - Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } }, - Directory = ScreenshotsRoot, - }.ShowAsync(GetWindow(control)); - if (!string.IsNullOrWhiteSpace(result)) + FileTypeChoices = new FilePickerFileType[] { new FilePickerFileType("PNG") { Patterns = new string[] { "*.png" } } } + }); + + if (result!=null && !string.IsNullOrWhiteSpace(result.Name)) { - var foldler = Path.GetDirectoryName(result); + var folder = Path.GetDirectoryName(result.Name); // Directory information for path, or null if path denotes a root directory or is // null. Returns System.String.Empty if path does not contain directory information. - if (!string.IsNullOrWhiteSpace(foldler)) + if (!string.IsNullOrWhiteSpace(folder)) { - if (!Directory.Exists(foldler)) + if (!Directory.Exists(folder)) { - Directory.CreateDirectory(foldler); + Directory.CreateDirectory(folder); } - output = new FileStream(result, FileMode.Create); + output = new FileStream(result.Name, FileMode.Create); } } return output; diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml index a1d06f800f..65df347ee2 100644 --- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml +++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml @@ -76,7 +76,11 @@ - + + + + + diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs index 199a4d6620..ee2ab42d6f 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooser.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs @@ -36,9 +36,8 @@ namespace Avalonia.Dialogs if (_quickLinksRoot != null) { var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control); -#pragma warning disable CS0618 // Type or member is obsolete + if (e.ClickCount == 2 || isQuickLink) -#pragma warning restore CS0618 // Type or member is obsolete { if (model.ItemType == ManagedFileChooserItemType.File) { diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 5576368240..cb23c6c336 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -416,6 +416,11 @@ namespace Avalonia.Headless } + public object GetFeature(Type t) + { + return null; + } + public void DrawLine(IPen pen, Point p1, Point p2) { } diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs index e42a7b1a71..b233b46dd0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs @@ -36,35 +36,35 @@ namespace Avalonia.Headless public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { - var cancelled = false; - var enqueued = false; - var l = new object(); - var timer = new Timer(_ => + if (interval.TotalMilliseconds < 10) + interval = TimeSpan.FromMilliseconds(10); + + var stopped = false; + Timer timer = null; + timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + Dispatcher.UIThread.Post(() => { - if (cancelled || enqueued) - return; - enqueued = true; - Dispatcher.UIThread.Post(() => + try { - lock (l) - { - enqueued = false; - if (cancelled) - return; - tick(); - } - }, priority); - } - }, null, interval, interval); + tick(); + } + finally + { + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); + return Disposable.Create(() => { - lock (l) - { - timer.Dispose(); - cancelled = true; - } + stopped = true; + timer.Dispose(); }); } diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 68442c1fd3..279e7e750d 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -34,7 +34,7 @@ namespace Avalonia.OpenGL.Controls _attachment.Present(); } - context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds); + context.DrawImage(_bitmap, new Rect(_bitmap.Size), new Rect(Bounds.Size)); base.Render(context); } @@ -84,6 +84,7 @@ namespace Avalonia.OpenGL.Controls using (_context.MakeCurrent()) { var gl = _context.GlInterface; + gl.ActiveTexture(GL_TEXTURE0); gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0); gl.DeleteFramebuffer(_fb); @@ -146,6 +147,13 @@ namespace Avalonia.OpenGL.Controls return false; } + if (_context == null) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: unable to create additional OpenGL context."); + return false; + } + GlVersion = _context.Version; try { diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs index 5c713804e9..9f69b4ee6e 100644 --- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs +++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs @@ -2,6 +2,7 @@ using System; using System.Reactive.Linq; using Avalonia.VisualTree; using Avalonia.Controls; +using Avalonia.Interactivity; using ReactiveUI; namespace Avalonia.ReactiveUI @@ -25,27 +26,28 @@ namespace Avalonia.ReactiveUI public IObservable GetActivationForView(IActivatableView view) { if (!(view is IVisual visual)) return Observable.Return(false); - if (view is WindowBase window) return GetActivationForWindowBase(window); + if (view is Control control) return GetActivationForControl(control); return GetActivationForVisual(visual); } /// - /// Listens to Opened and Closed events for Avalonia windows. + /// Listens to Loaded and Unloaded + /// events for Avalonia Control. /// - private IObservable GetActivationForWindowBase(WindowBase window) + private IObservable GetActivationForControl(Control control) { - var windowLoaded = Observable - .FromEventPattern( - x => window.Opened += x, - x => window.Opened -= x) + var controlLoaded = Observable + .FromEventPattern( + x => control.Loaded += x, + x => control.Loaded -= x) .Select(args => true); - var windowUnloaded = Observable - .FromEventPattern( - x => window.Closed += x, - x => window.Closed -= x) + var controlUnloaded = Observable + .FromEventPattern( + x => control.Unloaded += x, + x => control.Unloaded -= x) .Select(args => false); - return windowLoaded - .Merge(windowUnloaded) + return controlLoaded + .Merge(controlUnloaded) .DistinctUntilChanged(); } diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 9269dc70f8..2d848d4cd7 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI /// ReactiveUI routing documentation website for more info. /// /// - public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger + public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable { /// /// for the property. @@ -64,6 +64,12 @@ namespace Avalonia.ReactiveUI public static readonly StyledProperty ViewContractProperty = AvaloniaProperty.Register(nameof(ViewContract)); + /// + /// for the property. + /// + public static readonly StyledProperty DefaultContentProperty = + ViewModelViewHost.DefaultContentProperty.AddOwner(); + /// /// Initializes a new instance of the class. /// @@ -106,11 +112,22 @@ namespace Avalonia.ReactiveUI set => SetValue(ViewContractProperty, value); } + /// + /// Gets or sets the content displayed whenever there is no page currently routed. + /// + public object? DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + /// /// Gets or sets the ReactiveUI view locator used by this router. /// public IViewLocator? ViewLocator { get; set; } + Type IStyleable.StyleKey => typeof(TransitioningContentControl); + /// /// Invoked when ReactiveUI router navigates to a view model. /// diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs deleted file mode 100644 index d26e90b2da..0000000000 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Threading; - -using Avalonia.Animation; -using Avalonia.Controls; -using Avalonia.Styling; - -namespace Avalonia.ReactiveUI -{ - /// - /// A ContentControl that animates the transition when its content is changed. - /// - [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")] - public class TransitioningContentControl : ContentControl, IStyleable - { - /// - /// for the property. - /// - public static readonly StyledProperty PageTransitionProperty = - AvaloniaProperty.Register(nameof(PageTransition), - new CrossFade(TimeSpan.FromSeconds(0.5))); - - /// - /// for the property. - /// - public static readonly StyledProperty DefaultContentProperty = - AvaloniaProperty.Register(nameof(DefaultContent)); - - private CancellationTokenSource? _lastTransitionCts; - - /// - /// Gets or sets the animation played when content appears and disappears. - /// - public IPageTransition? PageTransition - { - get => GetValue(PageTransitionProperty); - set => SetValue(PageTransitionProperty, value); - } - - /// - /// Gets or sets the content displayed whenever there is no page currently routed. - /// - public object? DefaultContent - { - get => GetValue(DefaultContentProperty); - set => SetValue(DefaultContentProperty, value); - } - - /// - /// Gets or sets the content with animation. - /// - public new object? Content - { - get => base.Content; - set => UpdateContentWithTransition(value); - } - - /// - /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia default theme. - /// - Type IStyleable.StyleKey => typeof(ContentControl); - - /// - /// Updates the content with transitions. - /// - /// New content to set. - private async void UpdateContentWithTransition(object? content) - { - _lastTransitionCts?.Cancel(); - _lastTransitionCts = new CancellationTokenSource(); - - if (PageTransition != null) - await PageTransition.Start(this, null, true, _lastTransitionCts.Token); - base.Content = content; - if (PageTransition != null) - await PageTransition.Start(null, this, true, _lastTransitionCts.Token); - } - } -} diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 16dee00ebc..0750fef067 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -1,5 +1,8 @@ using System; using System.Reactive.Disposables; + +using Avalonia.Controls; +using Avalonia.Styling; using ReactiveUI; using Splat; @@ -10,7 +13,7 @@ namespace Avalonia.ReactiveUI /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// - public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger + public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable { /// /// for the property. @@ -24,6 +27,12 @@ namespace Avalonia.ReactiveUI public static readonly StyledProperty ViewContractProperty = AvaloniaProperty.Register(nameof(ViewContract)); + /// + /// for the property. + /// + public static readonly StyledProperty DefaultContentProperty = + AvaloniaProperty.Register(nameof(DefaultContent)); + /// /// Initializes a new instance of the class. /// @@ -55,11 +64,22 @@ namespace Avalonia.ReactiveUI set => SetValue(ViewContractProperty, value); } + /// + /// Gets or sets the content displayed whenever there is no page currently routed. + /// + public object? DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + /// /// Gets or sets the view locator. /// public IViewLocator? ViewLocator { get; set; } + Type IStyleable.StyleKey => typeof(TransitioningContentControl); + /// /// Invoked when ReactiveUI router navigates to a view model. /// diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index 855dc5363e..aa55065f6d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -41,13 +41,31 @@ M0,9 L10,0 20,9 19,10 10,2 1,10 z M0,1 L10,10 20,1 19,0 10,8 1,0 z - - + - + + + + + + + + + @@ -83,7 +101,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}" @@ -99,7 +116,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}" diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index 55f4893057..b8ade96b6f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -138,7 +138,11 @@ - + + + + + @@ -231,7 +235,11 @@ - + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml index 43fc8c7a34..724c317beb 100644 --- a/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml @@ -118,7 +118,7 @@ + IsLightDismissEnabled="False"> _namesToAtoms = new Dictionary(); private readonly Dictionary _atomsToNames = new Dictionary(); public X11Atoms(IntPtr display) { _display = display; + PopulateAtoms(display); + } - // make sure this array stays in sync with the statements below - - var fields = typeof(X11Atoms).GetFields() - .Where(f => f.FieldType == typeof(IntPtr) && (IntPtr)f.GetValue(this) == IntPtr.Zero).ToArray(); - var atomNames = fields.Select(f => f.Name).ToArray(); - - IntPtr[] atoms = new IntPtr [atomNames.Length]; - ; - - XInternAtoms(display, atomNames, atomNames.Length, true, atoms); - - for (var c = 0; c < fields.Length; c++) + private void InitAtom(ref IntPtr field, string name, IntPtr value) + { + if (value != IntPtr.Zero) { - _namesToAtoms[fields[c].Name] = atoms[c]; - _atomsToNames[atoms[c]] = fields[c].Name; - fields[c].SetValue(this, atoms[c]); + field = value; + _namesToAtoms[name] = value; + _atomsToNames[value] = name; } } diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 60f035cd7e..6041b53a62 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -5,13 +5,14 @@ using System.Runtime.InteropServices; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; using Avalonia.Platform; +using Avalonia.SourceGenerator; using Avalonia.Utilities; #nullable enable namespace Avalonia.X11 { - class X11CursorFactory : ICursorFactory + partial class X11CursorFactory : ICursorFactory { private static readonly byte[] NullCursorData = new byte[] { 0 }; @@ -48,11 +49,14 @@ namespace Avalonia.X11 {StandardCursorType.TopRightCorner, CursorFontShape.XC_top_right_corner}, }; + [GenerateEnumValueList] + private static partial CursorFontShape[] GetAllCursorShapes(); + public X11CursorFactory(IntPtr display) { _display = display; _nullCursor = GetNullCursor(display); - _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() + _cursors = GetAllCursorShapes() .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); } diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 0357fdd94e..ffac3c55e2 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -68,9 +68,7 @@ namespace Avalonia.X11 public void Save(Stream outputStream) { using (var wr = -#pragma warning disable CS0618 // Type or member is obsolete new WriteableBitmap(new PixelSize(_width, _height), new Vector(96, 96), PixelFormat.Bgra8888)) -#pragma warning restore CS0618 // Type or member is obsolete { using (var fb = wr.Lock()) { diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index cc9737668d..81c2a69aac 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -278,7 +278,8 @@ namespace Avalonia "llvmpipe" }; - public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name; + + public string WmClass { get; set; } /// /// Enables multitouch support. The default value is true. @@ -287,6 +288,18 @@ namespace Avalonia /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. /// public bool? EnableMultiTouch { get; set; } = true; + + public X11PlatformOptions() + { + try + { + WmClass = Assembly.GetEntryAssembly()?.GetName()?.Name; + } + catch + { + // + } + } } public static class AvaloniaX11PlatformExtensions { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 094cd59025..f325e6e2d6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -60,6 +60,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertAfter( new XDataTypeTransformer()); + InsertBefore( + new AvaloniaXamlIlDeferredResourceTransformer() + ); + // After everything else InsertBefore( new AddNameScopeRegistration(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs new file mode 100644 index 0000000000..c29dd94886 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) + return node; + + if (!ShouldBeDeferred(pa.Values[1])) + return node; + + var types = context.GetAvaloniaTypes(); + + if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), + }; + } + else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary)) + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd), + }; + } + + return node; + } + + private static bool ShouldBeDeferred(IXamlAstValueNode node) + { + // XAML compiler is currently strict about value types, allowing them to be created only through converters. + // At the moment it should be safe to not defer structs. + return !node.Type.GetClrType().IsValueType; + } + + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable + { + private readonly IXamlMethod _getter; + private readonly IXamlMethod _adder; + + public AdderSetter(IXamlMethod getter, IXamlMethod adder) + { + _getter = getter; + _adder = adder; + TargetType = getter.DeclaringType; + Parameters = adder.ParametersWithThis().Skip(1).ToList(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; + } + + public IXamlType TargetType { get; } + + public PropertySetterBinderParameters BinderParameters { get; } + + public IReadOnlyList Parameters { get; } + + public void Emit(IXamlILEmitter emitter) + { + var locals = new Stack(); + // Save all "setter" parameters + for (var c = Parameters.Count - 1; c >= 0; c--) + { + var loc = emitter.LocalsPool.GetLocal(Parameters[c]); + locals.Push(loc); + emitter.Stloc(loc.Local); + } + + emitter.EmitCall(_getter); + while (locals.Count>0) + using (var loc = locals.Pop()) + emitter.Ldloc(loc.Local); + emitter.EmitCall(_adder, true); + } + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.EmitCall(_getter); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_adder, true); + } + + public bool Equals(AdderSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter.Equals(other._getter) && _adder.Equals(other._adder); + } + + public override bool Equals(object obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 6da95be1c1..ceaec972f6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -75,17 +75,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { Getter = setterType.Methods.First(m => m.Name == "get_Value"); var method = setterType.Methods.First(m => m.Name == "set_Value"); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType, targetType.AcceptsNull())); } - class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter + sealed class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _method; private readonly IXamlType _type; public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters(); + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } public void Emit(IXamlILEmitter codegen) { @@ -94,13 +94,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers codegen.EmitCall(_method, true); } - public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type) + public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type, bool allowNull) { _method = method; _type = type; Parameters = new[] {type}; TargetType = method.ThisOrFirstParameter(); + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } + + private bool Equals(XamlIlDirectCallPropertySetter other) + => Equals(_method, other._method) && Equals(_type, other._type); + + public override bool Equals(object obj) + => Equals(obj as XamlIlDirectCallPropertySetter); + + public override int GetHashCode() + => (_method.GetHashCode() * 397) ^ _type.GetHashCode(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 9d9f6a08ab..dbfbe0e070 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -98,6 +98,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextDecorations { get; } public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } + public IXamlType IResourceDictionary { get; } + public IXamlType ResourceDictionary { get; } + public IXamlMethod ResourceDictionaryDeferredAdd { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -218,6 +221,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); + IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); + ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); + ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, + cfg.TypeSystem.GetType("System.Func`2").MakeGenericType( + cfg.TypeSystem.GetType("System.IServiceProvider"), + XamlIlTypes.Object)); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 5c7a80e680..6c9d510ba0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -206,38 +206,64 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field)); } - abstract class AvaloniaPropertyCustomSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + abstract class AvaloniaPropertyCustomSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { - protected AvaloniaXamlIlWellKnownTypes Types; - protected IXamlField AvaloniaProperty; + protected readonly AvaloniaXamlIlWellKnownTypes Types; + protected readonly IXamlField AvaloniaProperty; - public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types, + protected AvaloniaPropertyCustomSetter( + AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) + IXamlField avaloniaProperty, + bool allowNull) { Types = types; AvaloniaProperty = avaloniaProperty; TargetType = declaringType; + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowXNull = false - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; set; } - public abstract void Emit(IXamlILEmitter codegen); + + public abstract void Emit(IXamlILEmitter emitter); + + public abstract void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments); + + public bool Equals(AvaloniaPropertyCustomSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return GetType() == other.GetType() && AvaloniaProperty.Equals(other.AvaloniaProperty); + } + + public override bool Equals(object obj) + => Equals(obj as AvaloniaPropertyCustomSetter); + + public override int GetHashCode() + => AvaloniaProperty.GetHashCode(); } class BindingSetter : AvaloniaPropertyCustomSetter { public BindingSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.IBinding}; + Parameters = new[] { types.IBinding }; } public override void Emit(IXamlILEmitter emitter) @@ -246,10 +272,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions emitter .Stloc(bloc.Local) .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -257,7 +298,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public BindingWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { Parameters = new[] { types.BindingPriority, types.IBinding }; } @@ -265,15 +306,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public override void Emit(IXamlILEmitter emitter) { using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) - using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter .Stloc(bloc.Local) - .Stloc(priorityLocal.Local) + .Pop() // ignore priority .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -281,7 +336,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public SetValueWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty, IXamlType propertyType) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, propertyType.AcceptsNull()) { Parameters = new[] { types.BindingPriority, propertyType }; } @@ -295,9 +350,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions - value */ - var method = Types.AvaloniaObjectSetStyledPropertyValue - .MakeGenericMethod(new[] { Parameters[1] }); - using (var valueLocal = emitter.LocalsPool.GetLocal(Parameters[1])) using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter @@ -305,25 +357,57 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions .Stloc(priorityLocal.Local) .Ldsfld(AvaloniaProperty) .Ldloc(valueLocal.Local) - .Ldloc(priorityLocal.Local) - .EmitCall(method, true); + .Ldloc(priorityLocal.Local); + + EmitSetStyledPropertyValue(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitSetStyledPropertyValue(emitter); + } + + private void EmitSetStyledPropertyValue(IXamlILEmitter emitter) + { + var method = Types.AvaloniaObjectSetStyledPropertyValue.MakeGenericMethod(new[] { Parameters[1] }); + emitter.EmitCall(method, true); } } class UnsetValueSetter : AvaloniaPropertyCustomSetter { public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.UnsetValueType}; + Parameters = new[] { types.UnsetValueType }; } public override void Emit(IXamlILEmitter codegen) { + codegen.Pop(); + EmitSetValue(codegen); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + EmitSetValue(emitter); + } + + private void EmitSetValue(IXamlILEmitter emitter) + { + // Ignore the instance and load one from the static field to avoid extra local variable var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue"); - codegen - // Ignore the instance and load one from the static field to avoid extra local variable - .Pop() + + emitter .Ldsfld(AvaloniaProperty) .Ldsfld(unsetValue) .Ldc_I4(0) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index a4e6be2d14..c1c0594ec2 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit a4e6be2d1407abec4f35fcb208848830ce513ead +Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index add97a660b..d462a2210e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -39,6 +39,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions targetType = setter.Property.PropertyType; } + var previousWasControlTheme = false; + // Look upwards though the ambient context for IResourceNodes // which might be able to give us the resource. foreach (var parent in stack.Parents) @@ -47,6 +49,21 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { return ColorToBrushConverter.Convert(value, targetType); } + + // HACK: Temporary fix for #8678. Hard-coded to only work for the DevTools main + // window as we don't want 3rd parties to start relying on this hack. + // + // We need to implement compile-time merging of resource dictionaries and this + // hack can be removed. + if (previousWasControlTheme && + parent is ResourceDictionary hack && + hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" && + hack.Owner.TryGetResource(ResourceKey, out value)) + { + return ColorToBrushConverter.Convert(value, targetType); + } + + previousWasControlTheme = parent is ControlTheme; } if (provideTarget.TargetObject is IControl target && @@ -69,3 +86,4 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions } } } + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index a0e7fd7dcf..168b9835dd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -158,9 +158,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime string.Join(",", lst.Select(e => $"`{e.ClrAssemblyName}:{e.ClrNamespace}.{name}`"))); } } - - [Obsolete("Don't use", true)] - public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider(null); // Don't emit debug symbols for this code so debugger will be forced to step into XAML instead #line hidden diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 265af3c5da..4a3538fff2 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -19,6 +19,7 @@ namespace Avalonia.Data private bool _isSetterValue; private IStyledElement _target = default!; private Type? _targetType; + private bool _hasProducedValue; public TemplateBinding() { @@ -143,10 +144,12 @@ namespace Avalonia.Data } PublishNext(value); + _hasProducedValue = true; } - else + else if (_hasProducedValue) { PublishNext(AvaloniaProperty.UnsetValue); + _hasProducedValue = false; } } diff --git a/src/Shared/SourceGeneratorAttributes.cs b/src/Shared/SourceGeneratorAttributes.cs index ac7c82c469..3f00fbef57 100644 --- a/src/Shared/SourceGeneratorAttributes.cs +++ b/src/Shared/SourceGeneratorAttributes.cs @@ -38,4 +38,12 @@ namespace Avalonia.SourceGenerator } } + + internal class GenerateEnumValueDictionaryAttribute : Attribute + { + } + + internal class GenerateEnumValueListAttribute : Attribute + { + } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 8293769138..99ab60d1ac 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,6 +10,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; +using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -17,7 +18,7 @@ namespace Avalonia.Skia /// /// Skia based drawing context. /// - internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport + internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private IDisposable[] _disposables; private readonly Vector _dpi; @@ -38,7 +39,8 @@ namespace Avalonia.Skia private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); private static SKShader s_acrylicNoiseShader; - private readonly ISkiaGpuRenderSession _session; + private readonly ISkiaGpuRenderSession _session; + private bool _leased = false; /// /// Context create info. @@ -83,6 +85,47 @@ namespace Avalonia.Skia public ISkiaGpuRenderSession CurrentSession; } + class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature + { + private readonly DrawingContextImpl _context; + + public SkiaLeaseFeature(DrawingContextImpl context) + { + _context = context; + } + + public ISkiaSharpApiLease Lease() + { + _context.CheckLease(); + return new ApiLease(_context); + } + + class ApiLease : ISkiaSharpApiLease + { + private DrawingContextImpl _context; + private readonly SKMatrix _revertTransform; + + public ApiLease(DrawingContextImpl context) + { + _revertTransform = context.Canvas.TotalMatrix; + _context = context; + _context._leased = true; + } + + public SKCanvas SkCanvas => _context.Canvas; + public GRContext GrContext => _context.GrContext; + public SKSurface SkSurface => _context.Surface; + public double CurrentOpacity => _context._currentOpacity; + + public void Dispose() + { + _context.Canvas.SetMatrix(_revertTransform); + _context._leased = false; + _context = null; + } + } + } + /// /// Create new drawing context. /// @@ -123,20 +166,23 @@ namespace Avalonia.Skia public SKCanvas Canvas { get; } public SKSurface Surface { get; } - SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas; - SKSurface ISkiaDrawingContextImpl.SkSurface => Surface; - GRContext ISkiaDrawingContextImpl.GrContext => _grContext; - double ISkiaDrawingContextImpl.CurrentOpacity => _currentOpacity; - + private void CheckLease() + { + if (_leased) + throw new InvalidOperationException("The underlying graphics API is currently leased"); + } + /// public void Clear(Color color) { + CheckLease(); Canvas.Clear(color.ToSKColor()); } /// public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { + CheckLease(); var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); @@ -157,6 +203,7 @@ namespace Avalonia.Skia /// public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { + CheckLease(); PushOpacityMask(opacityMask, opacityMaskRect); DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); @@ -165,6 +212,7 @@ namespace Avalonia.Skia /// public void DrawLine(IPen pen, Point p1, Point p2) { + CheckLease(); using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) @@ -177,6 +225,7 @@ namespace Avalonia.Skia /// public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { + CheckLease(); var impl = (GeometryImpl) geometry; var size = geometry.Bounds.Size; @@ -260,6 +309,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; @@ -296,6 +346,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); // Arbitrary chosen values // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192) @@ -421,7 +472,8 @@ namespace Avalonia.Skia { if (rect.Height <= 0 || rect.Width <= 0) return; - + CheckLease(); + var rc = rect.ToSKRect(); if (brush != null) @@ -447,6 +499,7 @@ namespace Avalonia.Skia /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { + CheckLease(); using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -459,18 +512,21 @@ namespace Avalonia.Skia /// public IDrawingContextLayerImpl CreateLayer(Size size) { + CheckLease(); return CreateRenderTarget(size, true); } /// public void PushClip(Rect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRect(clip.ToSKRect()); } public void PushClip(RoundedRect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); } @@ -478,12 +534,14 @@ namespace Avalonia.Skia /// public void PopClip() { + CheckLease(); Canvas.Restore(); } /// public void PushOpacity(double opacity) { + CheckLease(); _opacityStack.Push(_currentOpacity); _currentOpacity *= opacity; } @@ -491,6 +549,7 @@ namespace Avalonia.Skia /// public void PopOpacity() { + CheckLease(); _currentOpacity = _opacityStack.Pop(); } @@ -499,6 +558,7 @@ namespace Avalonia.Skia { if(_disposed) return; + CheckLease(); try { if (_grContext != null) @@ -523,6 +583,7 @@ namespace Avalonia.Skia /// public void PushGeometryClip(IGeometryImpl clip) { + CheckLease(); Canvas.Save(); Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true); } @@ -530,12 +591,14 @@ namespace Avalonia.Skia /// public void PopGeometryClip() { + CheckLease(); Canvas.Restore(); } /// public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) { + CheckLease(); _blendingModeStack.Push(_currentBlendingMode); _currentBlendingMode = blendingMode; } @@ -543,14 +606,20 @@ namespace Avalonia.Skia /// public void PopBitmapBlendMode() { + CheckLease(); _currentBlendingMode = _blendingModeStack.Pop(); } - public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public void Custom(ICustomDrawOperation custom) + { + CheckLease(); + custom.Render(this); + } /// public void PushOpacityMask(IBrush mask, Rect bounds) { + CheckLease(); // TODO: This should be disposed var paint = new SKPaint(); @@ -561,6 +630,7 @@ namespace Avalonia.Skia /// public void PopOpacityMask() { + CheckLease(); using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn }) { Canvas.SaveLayer(paint); @@ -580,6 +650,7 @@ namespace Avalonia.Skia get { return _currentTransform; } set { + CheckLease(); if (_currentTransform == value) return; @@ -596,6 +667,14 @@ namespace Avalonia.Skia } } + [CanBeNull] + public object GetFeature(Type t) + { + if (t == typeof(ISkiaSharpApiLeaseFeature)) + return new SkiaLeaseFeature(this); + return null; + } + /// /// Configure paint wrapper for using gradient brush. /// diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index a4617bb4d5..d700d4848e 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia new GRGlTextureInfo( GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(), (uint)_surface.InternalFormat))) - using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, + using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888)) { // Again, silently ignore, if something went wrong it's not our fault @@ -118,7 +118,7 @@ namespace Avalonia.Skia { var gl = _context.GlInterface; - var textures = new int[2]; + Span textures = stackalloc int[2]; fixed (int* ptex = textures) gl.GenTextures(2, ptex); _texture = textures[0]; @@ -139,7 +139,6 @@ namespace Avalonia.Skia gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); gl.BindTexture(GL_TEXTURE_2D, oldTexture); - } } } @@ -161,15 +160,15 @@ namespace Avalonia.Skia gl.GetIntegerv(GL_ACTIVE_TEXTURE, out var oldActive); gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); - gl.BindTexture(GL_TEXTURE_2D, _frontBuffer); gl.ActiveTexture(GL_TEXTURE0); + gl.BindTexture(GL_TEXTURE_2D, _frontBuffer); gl.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height); gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo); - gl.BindTexture(GL_TEXTURE_2D, oldTexture); gl.ActiveTexture(oldActive); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); gl.Finish(); } @@ -192,9 +191,8 @@ namespace Avalonia.Skia if(_disposed) return; _disposed = true; - var tex = new[] { _texture, _frontBuffer }; - fixed (int* ptex = tex) - gl.DeleteTextures(2, ptex); + var ptex = stackalloc[] { _texture, _frontBuffer }; + gl.DeleteTextures(2, ptex); } } diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index d0b45b7c5d..ec33770356 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Rendering; using SkiaSharp; @@ -29,70 +28,5 @@ namespace Avalonia.Skia.Helpers return new DrawingContextImpl(createInfo); } - /// - /// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext. - /// - [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables) - { - var createInfo = new DrawingContextImpl.CreateInfo - { - GrContext = grContext, - Surface = surface, - Dpi = dpi, - DisableTextLcdRendering = false, - }; - - return new DrawingContextImpl(createInfo, disposables); - } - - /// - /// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext. - /// - [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables) - { - var createInfo = new DrawingContextImpl.CreateInfo - { - Surface = surface, - Dpi = dpi, - DisableTextLcdRendering = false, - }; - - return new DrawingContextImpl(createInfo, disposables); - } - - [Obsolete] - public static ISkiaDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null) - { - if (grContext is null) - { - var surface = SKSurface.Create( - new SKImageInfo( - (int)Math.Ceiling(size.Width), - (int)Math.Ceiling(size.Height), - SKImageInfo.PlatformColorType, - SKAlphaType.Premul)); - - return WrapSkiaSurface(surface, dpi, surface); - } - else - { - var surface = SKSurface.Create(grContext, false, - new SKImageInfo( - (int)Math.Ceiling(size.Width), - (int)Math.Ceiling(size.Height), - SKImageInfo.PlatformColorType, - SKAlphaType.Premul)); - - return WrapSkiaSurface(surface, grContext, dpi, surface); - } - } - - [Obsolete] - public static void DrawTo(this ISkiaDrawingContextImpl source, ISkiaDrawingContextImpl destination, SKPaint paint = null) - { - destination.SkCanvas.DrawSurface(source.SkSurface, new SKPoint(0, 0), paint); - } } } diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs deleted file mode 100644 index 1b60154d46..0000000000 --- a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Metadata; -using Avalonia.Platform; -using SkiaSharp; - -namespace Avalonia.Skia -{ - [Unstable] - public interface ISkiaDrawingContextImpl : IDrawingContextImpl - { - SKCanvas SkCanvas { get; } - GRContext GrContext { get; } - SKSurface SkSurface { get; } - double CurrentOpacity { get; } - } -} diff --git a/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs new file mode 100644 index 0000000000..b3966c0324 --- /dev/null +++ b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs @@ -0,0 +1,20 @@ +using System; +using Avalonia.Metadata; +using SkiaSharp; + +namespace Avalonia.Skia; + +[Unstable] +public interface ISkiaSharpApiLeaseFeature +{ + public ISkiaSharpApiLease Lease(); +} + +[Unstable] +public interface ISkiaSharpApiLease : IDisposable +{ + SKCanvas SkCanvas { get; } + GRContext GrContext { get; } + SKSurface SkSurface { get; } + double CurrentOpacity { get; } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a7f1d9c3e5..180ae491b3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -614,5 +614,6 @@ namespace Avalonia.Direct2D1.Media } public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public object GetFeature(Type t) => null; } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 2b8070fb04..29b4baeeb0 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -65,15 +65,6 @@ namespace Avalonia "Microsoft Basic Render" }; - /// - /// Enables multitouch support. The default value is true. - /// - /// - /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. - /// - [Obsolete("Multitouch is always enabled on supported Windows versions")] - public bool? EnableMultitouch { get; set; } = true; - /// /// Embeds popups to the window when set to true. The default value is false. /// diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6d5cba9946..0f243fcf9f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -285,11 +285,12 @@ namespace Avalonia.Win32 set { - if (IsWindowVisible(_hwnd)) + if (IsWindowVisible(_hwnd) && _lastWindowState != value) { ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated } + _lastWindowState = value; _showWindowState = value; } } diff --git a/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs new file mode 100644 index 0000000000..42612c7ce0 --- /dev/null +++ b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs @@ -0,0 +1,94 @@ +using System.IO; +using System.Linq; +using System.Text; +using Generator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace DevGenerators; + +[Generator(LanguageNames.CSharp)] +public class EnumMemberDictionaryGenerator : IIncrementalGenerator +{ + const string DictionaryAttributeFullName = "global::Avalonia.SourceGenerator.GenerateEnumValueDictionaryAttribute"; + const string ListAttributeFullName = "global::Avalonia.SourceGenerator.GenerateEnumValueListAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var allMethodsWithAttributes = context.SyntaxProvider + .CreateSyntaxProvider( + static (s, _) => s is MethodDeclarationSyntax + { + AttributeLists.Count: > 0, + } md && md.Modifiers.Any(m=>m.IsKind(SyntaxKind.PartialKeyword)), + static (context, _) => + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); + + var all = allMethodsWithAttributes + .Where(s => + s.HasAttributeWithFullyQualifiedName(DictionaryAttributeFullName) + || s.HasAttributeWithFullyQualifiedName(ListAttributeFullName) + ).Collect(); + context.RegisterSourceOutput(all, static (context, methods) => + { + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType)) + { + var classBuilder = new StringBuilder(); + if (typeGroup.Key.ContainingNamespace != null) + classBuilder + .AppendLine("using System;") + .Append("namespace ") + .Append(typeGroup.Key.ContainingNamespace) + .AppendLine(";"); + classBuilder + .Append("partial class ") + .AppendLine(typeGroup.Key.Name) + .AppendLine("{"); + + foreach (var method in typeGroup) + { + var namedReturn = method.ReturnType as INamedTypeSymbol; + var arrayReturn = method.ReturnType as IArrayTypeSymbol; + + if ((namedReturn != null && namedReturn.Arity > 0) || arrayReturn != null) + { + ITypeSymbol enumType = namedReturn != null + ? namedReturn.TypeArguments.Last() + : arrayReturn!.ElementType; + + var isDic = method.HasAttributeWithFullyQualifiedName(DictionaryAttributeFullName); + + classBuilder + .Pad(1) + .Append("private static partial " + method.ReturnType + " " + method.Name + "()") + .AppendLine().Pad(4).Append(" => new ").Append(method.ReturnType).AppendLine("{"); + foreach (var member in enumType.GetMembers()) + { + if (member.Name == ".ctor") + continue; + + if (isDic) + classBuilder.Pad(2) + .Append("{\"") + .Append(member.Name) + .Append("\", ") + .Append(member.ToString()) + .AppendLine("},"); + else + classBuilder.Pad(2).Append(member.ToString()).AppendLine(","); + } + + classBuilder.Pad(1).AppendLine("};"); + } + } + classBuilder.AppendLine("}"); + + context.AddSource(typeGroup.Key.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString()); + } + }); + + + } + +} \ No newline at end of file diff --git a/src/tools/DevGenerators/X11AtomsGenerator.cs b/src/tools/DevGenerators/X11AtomsGenerator.cs new file mode 100644 index 0000000000..daf003c4c4 --- /dev/null +++ b/src/tools/DevGenerators/X11AtomsGenerator.cs @@ -0,0 +1,73 @@ +using System.IO; +using System.Linq; +using System.Text; +using Generator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace DevGenerators; + +[Generator(LanguageNames.CSharp)] +public class X11AtomsGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var x11AtomsClasses = context.SyntaxProvider + .CreateSyntaxProvider( + static (s, _) => s is ClassDeclarationSyntax + { + Identifier.Text: "X11Atoms" + }, + static (context, _) => + (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); + + var all = x11AtomsClasses.Collect(); + context.RegisterSourceOutput(all, static (context, classes) => + { + foreach (var cl in classes) + { + var classBuilder = new StringBuilder(); + if (cl.ContainingNamespace != null) + classBuilder + .AppendLine("using System;") + .AppendLine("using static Avalonia.X11.XLib;") + .Append("namespace ") + .Append(cl.ContainingNamespace) + .AppendLine(";"); + classBuilder + .Append("partial class ") + .AppendLine(cl.Name) + .AppendLine("{"); + + var fields = cl.GetMembers().OfType() + .Where(f => f.Type.Name == "IntPtr" + && f.DeclaredAccessibility == Accessibility.Public).ToList(); + + classBuilder.Pad(1).AppendLine("private void PopulateAtoms(IntPtr display)").Pad(1).AppendLine("{"); + classBuilder.Pad(2).Append("var atoms = new IntPtr[").Append(fields.Count).AppendLine("];"); + classBuilder.Pad(2).Append("var atomNames = new string[").Append(fields.Count).AppendLine("] {"); + + + for (int c = 0; c < fields.Count; c++) + classBuilder.Pad(3).Append("\"").Append(fields[c].Name).AppendLine("\","); + classBuilder.Pad(2).AppendLine("};"); + + classBuilder.Pad(2).AppendLine("XInternAtoms(display, atomNames, atomNames.Length, true, atoms);"); + + for (int c = 0; c < fields.Count; c++) + classBuilder.Pad(2).Append("InitAtom(ref ").Append(fields[c].Name).Append(", \"") + .Append(fields[c].Name).Append("\", atoms[").Append(c).AppendLine("]);"); + + + classBuilder.Pad(1).AppendLine("}"); + classBuilder.AppendLine("}"); + + context.AddSource(cl.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString()); + } + }); + + + } + +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs deleted file mode 100644 index b1b2d3d8f2..0000000000 --- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System.Linq; -using Avalonia.Collections; -using Xunit; - -namespace Avalonia.Base.UnitTests.Collections -{ - public class AvaloniaListExtenionsTests - { -#pragma warning disable CS0618 // Type or member is obsolete - [Fact] - public void CreateDerivedList_Creates_Initial_Items() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - - var target = source.CreateDerivedList(x => new Wrapper(x)); - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Add() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.Add(4); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Insert() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.Insert(1, 4); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Remove() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.Remove(2); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_RemoveRange() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.RemoveRange(1, 2); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Move() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.Move(2, 0); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Theory] - [InlineData(0, 2, 3)] - [InlineData(0, 2, 4)] - [InlineData(0, 2, 5)] - [InlineData(0, 4, 4)] - [InlineData(1, 2, 0)] - [InlineData(1, 2, 4)] - [InlineData(1, 2, 5)] - [InlineData(1, 4, 0)] - [InlineData(2, 2, 0)] - [InlineData(2, 2, 1)] - [InlineData(2, 2, 3)] - [InlineData(2, 2, 4)] - [InlineData(2, 2, 5)] - [InlineData(4, 2, 0)] - [InlineData(4, 2, 1)] - [InlineData(4, 2, 3)] - [InlineData(5, 1, 0)] - [InlineData(5, 1, 3)] - public void CreateDerivedList_Handles_MoveRange(int oldIndex, int count, int newIndex) - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3, 4, 5 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.MoveRange(oldIndex, count, newIndex); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Replace() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source[1] = 4; - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } - - [Fact] - public void CreateDerivedList_Handles_Clear() - { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); - var target = source.CreateDerivedList(x => new Wrapper(x)); - - source.Clear(); - - var result = target.Select(x => x.Value).ToList(); - - Assert.Equal(source, result); - } -#pragma warning restore CS0618 // Type or member is obsolete - - - private class Wrapper - { - public Wrapper(int value) - { - Value = value; - } - - public int Value { get; } - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/WeakSubscriptionManagerTests.cs b/tests/Avalonia.Base.UnitTests/WeakSubscriptionManagerTests.cs deleted file mode 100644 index 7b2cac2819..0000000000 --- a/tests/Avalonia.Base.UnitTests/WeakSubscriptionManagerTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Utilities; -using Xunit; - -namespace Avalonia.Base.UnitTests -{ - public class WeakSubscriptionManagerTests - { - class EventSource - { - public event EventHandler Event; - - public void Fire() - { - Event?.Invoke(this, new EventArgs()); - } - } - - class Subscriber : IWeakSubscriber - { - private readonly Action _onEvent; - - public Subscriber(Action onEvent) - { - _onEvent = onEvent; - } - - public void OnEvent(object sender, EventArgs ev) - { - _onEvent?.Invoke(); - } - } - - [Fact] - public void EventShoudBePassedToSubscriber() - { - bool handled = false; - var subscriber = new Subscriber(() => handled = true); - var source = new EventSource(); - WeakSubscriptionManager.Subscribe(source, "Event", subscriber); - source.Fire(); - Assert.True(handled); - } - - - [Fact] - public void EventHandlerShouldNotBeKeptAlive() - { - bool handled = false; - var source = new EventSource(); - AddSubscriber(source, "Event", () => handled = true); - for (int c = 0; c < 10; c++) - { - GC.Collect(); - GC.Collect(3, GCCollectionMode.Forced, true); - } - source.Fire(); - Assert.False(handled); - } - - private void AddSubscriber(EventSource source, string name, Action func) - { - WeakSubscriptionManager.Subscribe(source, name, new Subscriber(func)); - } - } -} diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs index 6fb2a5ac24..8436881122 100644 --- a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using System; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; @@ -99,5 +100,7 @@ namespace Avalonia.Benchmarks public void Custom(ICustomDrawOperation custom) { } + + public object GetFeature(Type t) => null; } } diff --git a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs index 8c00726e05..aab282a54f 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace Avalonia.Controls.UnitTests @@ -100,5 +102,23 @@ namespace Avalonia.Controls.UnitTests }, result); } + + [Theory] + [InlineData(1.2d, GridUnitType.Pixel, "1.2")] + [InlineData(1.2d, GridUnitType.Star, "1.2*")] + [InlineData(1.2d, GridUnitType.Auto, "Auto")] + public async void ToString_AllCulture_Should_Pass(double d, GridUnitType type, string result) + { + List cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures).ToList(); + GridLength length = new GridLength(d, type); + foreach(var culture in cultureInfos) + { + await Task.Run(() => + { + CultureInfo.CurrentCulture = culture; + Assert.Equal(result, length.ToString()); + }); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index afa153a593..f6d96edb99 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -9,7 +9,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Styling; -using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -19,7 +18,7 @@ namespace Avalonia.Controls.UnitTests public class ListBoxTests { private MouseTestHelper _mouse = new MouseTestHelper(); - + [Fact] public void Should_Use_ItemTemplate_To_Create_Item_Content() { @@ -433,6 +432,47 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new AvaloniaList(Enumerable.Range(1, 30).Select(v => v.ToString())); + + var wnd = new Window() { Width = 100, Height = 100, IsVisible = true }; + + var target = new ListBox() + { + AutoScrollToSelectedItem = true, + Height = 100, + Width = 50, + VirtualizationMode = ItemVirtualizationMode.Simple, + ItemTemplate = new FuncDataTemplate((c, _) => new Border() { Height = 10 }), + Items = items, + }; + wnd.Content = target; + + var lm = wnd.LayoutManager; + + lm.ExecuteInitialLayoutPass(); + + //select last / scroll to last item + target.SelectedItem = items.Last(); + + lm.ExecuteLayoutPass(); + + //remove the first item (in non realized area of the listbox) + items.Remove("1"); + lm.ExecuteLayoutPass(); + + Assert.Equal("30", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 1).DataContext); + Assert.Equal("29", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 2).DataContext); + Assert.Equal("28", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 3).DataContext); + Assert.Equal("27", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 4).DataContext); + Assert.Equal("26", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 5).DataContext); + } + } + [Fact] public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control() { @@ -656,7 +696,6 @@ namespace Avalonia.Controls.UnitTests public string Value { get; } } - [Fact] public void SelectedItem_Validation() { @@ -670,11 +709,11 @@ namespace Avalonia.Controls.UnitTests }; Prepare(target); - + var exception = new System.InvalidCastException("failed validation"); var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); target.Bind(ComboBox.SelectedItemProperty, textObservable); - + Assert.True(DataValidationErrors.GetHasErrors(target)); Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 8f795104bf..bf516748cc 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -5,10 +5,12 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -60,104 +62,123 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); - - Assert.Equal(0, target.SelectedIndex); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + _mouse.Click(target.Presenter.Panel.Children[0]); + + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle, - }; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle, + }; - ApplyTemplate(target); - target.SelectedIndex = 0; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(-1, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); - target.SelectedIndex = 1; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 1; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/LoadedTests.cs b/tests/Avalonia.Controls.UnitTests/LoadedTests.cs new file mode 100644 index 0000000000..aaf0dce30e --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/LoadedTests.cs @@ -0,0 +1,71 @@ +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests; + +public class LoadedTests +{ + [Fact] + public void Window_Loads_And_Unloads() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + int loadedCount = 0, unloadedCount = 0; + var target = new Window(); + + target.Loaded += (_, _) => loadedCount++; + target.Unloaded += (_, _) => unloadedCount++; + + Assert.Equal(0, loadedCount); + Assert.Equal(0, unloadedCount); + + target.Show(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + Assert.True(target.IsLoaded); + + Assert.Equal(1, loadedCount); + Assert.Equal(0, unloadedCount); + + target.Close(); + + Assert.Equal(1, loadedCount); + Assert.Equal(1, unloadedCount); + Assert.False(target.IsLoaded); + } + } + + [Fact] + public void Control_Loads_And_Unloads() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + int loadedCount = 0, unloadedCount = 0; + var window = new Window(); + window.Show(); + + var target = new Button(); + + target.Loaded += (_, _) => loadedCount++; + target.Unloaded += (_, _) => unloadedCount++; + + Assert.Equal(0, loadedCount); + Assert.Equal(0, unloadedCount); + + window.Content = target; + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + Assert.True(target.IsLoaded); + + Assert.Equal(1, loadedCount); + Assert.Equal(0, unloadedCount); + + window.Content = null; + + Assert.Equal(1, loadedCount); + Assert.Equal(1, unloadedCount); + Assert.False(target.IsLoaded); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 4b6b6a1182..330cbfd7b9 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -14,6 +14,7 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Markup.Data; using Avalonia.Platform; @@ -1115,42 +1116,48 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[1]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[1]); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Equal( - KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), - panel.Children[1]); + Assert.Equal( + KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), + panel.Children[1]); + } } [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = items, - }; + var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - Prepare(target); + var target = new ListBox + { + Template = Template(), + Items = items, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); - _helper.Down(target.Presenter.Panel.Children[1]); + _helper.Down(target.Presenter.Panel.Children[1]); - items.RemoveAt(1); + items.RemoveAt(1); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + } } [Fact] @@ -1230,31 +1237,37 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(3, target.SelectedIndex); + Assert.Equal(3, target.SelectedIndex); + } } [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 6b26d76371..5d2f4e2a64 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -10,8 +10,10 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -701,261 +703,290 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + } } [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - VerifyAdded("Baz"); + VerifyAdded("Baz"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - VerifyAdded("Qux"); + VerifyAdded("Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - VerifyRemoved("Bar"); + VerifyRemoved("Bar"); + } } [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); - Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); - - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - - Assert.Equal(2, target.SelectedIndex); - Assert.Equal("Baz", target.SelectedItem); - Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); + + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + + Assert.Equal(2, target.SelectedIndex); + Assert.Equal("Baz", target.SelectedItem); + Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + } } [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + } } [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + } } [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(params string[] selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(selection, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(params string[] selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(selection, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); - VerifyAdded("Baz" ,"Qux"); + VerifyAdded("Baz", "Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - VerifyRemoved("Qux"); + VerifyRemoved("Qux"); + } } [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - Assert.Equal(new[] { "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + } } [Fact] @@ -1158,70 +1189,79 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); - - Assert.Equal(3, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - - Assert.Equal(1, target.SelectedItems.Count); - Assert.Equal(new[] { "Foo", }, target.SelectedItems); - Assert.Equal(new[] { 0 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); + + Assert.Equal(3, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + + Assert.Equal(1, target.SelectedItems.Count); + Assert.Equal(new[] { "Foo", }, target.SelectedItems); + Assert.Equal(new[] { 0 }, SelectedContainers(target)); + } } [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); - _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); + } } [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); - - Assert.Equal(2, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); + + Assert.Equal(2, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] @@ -1253,41 +1293,47 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 6b2f05c923..b3a75dafa7 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -138,5 +138,34 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, -20, 20, 20), target.Children[1].Bounds); } + + [Theory] + [InlineData(100, 100, 100, 100)] + [InlineData(100, double.PositiveInfinity, 100, 40)] + [InlineData(double.PositiveInfinity, 100, 20, 100)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity, 20, 40)] + public void StretchedPanel_Measures_Correctly(double availableWidth, double availableHeight, double desiredWidth, double desiredHeight) + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Stretch, + HorizontalAlignment = Layout.HorizontalAlignment.Stretch, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetBelow(rect2, rect1); + target.Measure(new Size(availableWidth, availableHeight)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(desiredWidth, target.DesiredSize.Width); + Assert.Equal(desiredHeight, target.DesiredSize.Height); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index d784caf2db..9cf21423a3 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -18,6 +18,7 @@ using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using JetBrains.Annotations; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -885,28 +886,31 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var tree = CreateTestTreeData(); - var target = new TreeView + using (UnitTestApplication.Start()) { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + var visualRoot = new TestRoot(); + visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - target.SelectAll(); + CreateNodeDataTemplate(target); + ApplyTemplates(target); + target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + target.SelectAll(); - AssertChildrenSelected(target, tree[0]); - Assert.Equal(5, target.SelectedItems.Count); + AssertChildrenSelected(target, tree[0]); + Assert.Equal(5, target.SelectedItems.Count); - _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(5, target.SelectedItems.Count); + Assert.Equal(5, target.SelectedItems.Count); + } } [Fact] diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 2b10c302bc..382306ac83 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; using Xunit; @@ -55,6 +57,43 @@ namespace Avalonia.IntegrationTests.Appium } } } + + [PlatformFact(TestPlatforms.Windows)] + public void OnWindows_Docked_Windows_Retain_Size_Position_When_Restored() + { + using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) + { + var windowState = _session.FindElementByAccessibilityId("WindowState"); + + Assert.Equal("Normal", windowState.GetComboBoxValue()); + + + var window = _session.FindElements(By.XPath("//Window")).First(); + + new Actions(_session) + .KeyDown(Keys.Meta) + .SendKeys(Keys.Left) + .KeyUp(Keys.Meta) + .Perform(); + + var original = GetWindowInfo(); + + windowState.Click(); + _session.FindElementByName("Minimized").SendClick(); + + new Actions(_session) + .KeyDown(Keys.Alt) + .SendKeys(Keys.Tab) + .KeyUp(Keys.Alt) + .Perform(); + + var current = GetWindowInfo(); + + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + + } + } [Theory] @@ -92,7 +131,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(clientSize.Width >= current.ScreenRect.Width); Assert.True(clientSize.Height >= current.ScreenRect.Height); - windowState.Click(); + windowState.SendClick(); + _session.FindElementByName("Normal").SendClick(); current = GetWindowInfo(); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index ecbdd5bade..4e5344dd25 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -211,6 +211,35 @@ namespace Avalonia.IntegrationTests.Appium } } + [PlatformFact(TestPlatforms.MacOS)] + public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + // We don't use dispose to close the window here, because it seems that hiding and re-showing a window + // causes Appium to think it's a different window. + OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual); + + var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton"); + + hideButton.Click(); + + var windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + mainWindow.Click(); + + windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + _session.FindElementByAccessibilityId("RestoreAll").Click(); + + // Close the window manually. + secondaryWindow = FindWindow(_session, "SecondaryWindow"); + secondaryWindow.GetChromeButtons().close.Click(); + } + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); diff --git a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs index d9ea3e374c..979dbec674 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Avalonia.Controls; @@ -217,6 +218,37 @@ namespace Avalonia.Markup.UnitTests.Data } } + [Fact] + public void Should_Not_Pass_UnsetValue_To_MultiBinding_During_ApplyTemplate() + { + var converter = new MultiConverter(); + var source = new Button + { + Content = "foo", + Template = new FuncControlTemplate