From 806193a43daad1846b5c9a18db49a6075f7904e3 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 30 Jan 2026 09:57:39 +0000 Subject: [PATCH 01/32] Remove NativeMenuItemToggleType and NativeMenuBar.EnableMenuItemClickForwarding (#20577) * Remove NativeMenuItemToggleType * Remove NativeMenuBar.EnableMenuItemClickForwarding * Update API suppressions --- api/Avalonia.nupkg.xml | 60 +++++++++++++++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 2 +- src/Avalonia.Controls/NativeMenuBar.cs | 27 --------- .../NativeMenuBarPresenter.cs | 4 +- src/Avalonia.Controls/NativeMenuItem.cs | 14 +---- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 6 +- src/Avalonia.Native/IAvnMenuItem.cs | 2 +- 7 files changed, 69 insertions(+), 46 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 3858eaa6ff..275b25e01e 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -31,6 +31,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.NativeMenuItemToggleType + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable @@ -73,6 +79,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.NativeMenuItemToggleType + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable @@ -283,6 +295,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.NativeMenuItem.ToggleTypeProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -367,6 +391,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.NativeMenuBar.SetEnableMenuItemClickForwarding(Avalonia.Controls.MenuItem,System.Boolean) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.NativeMenuItem.get_ToggleType + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) @@ -649,6 +685,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.NativeMenuBar.EnableMenuItemClickForwardingProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.NativeMenuItem.ToggleTypeProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -733,6 +781,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.NativeMenuBar.SetEnableMenuItemClickForwarding(Avalonia.Controls.MenuItem,System.Boolean) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.NativeMenuItem.get_ToggleType + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index be552201c0..894c052d6c 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -37,7 +37,7 @@ namespace IntegrationTestApp { Header = (string?)page.Name, ToolTip = $"Tip:{(string?)page.Name}", - ToggleType = NativeMenuItemToggleType.Radio, + ToggleType = MenuItemToggleType.Radio }; menuItem.Click += (_, _) => diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index 118c2291cc..2f271ef34c 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -3,8 +3,6 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Interactivity; -using Avalonia.Metadata; using Avalonia.Reactive; using Avalonia.VisualTree; @@ -13,25 +11,11 @@ namespace Avalonia.Controls [TemplatePart("PART_NativeMenuPresenter", typeof(MenuBase))] public class NativeMenuBar : TemplatedControl { - [Unstable("To be removed in 12.0, NativeMenuBar now has a default template")] // TODO12 - public static readonly AttachedProperty EnableMenuItemClickForwardingProperty = - AvaloniaProperty.RegisterAttached( - "EnableMenuItemClickForwarding"); - private MenuBase? _menu; private IDisposable? _subscriptions; static NativeMenuBar() { - EnableMenuItemClickForwardingProperty.Changed.Subscribe(args => - { - var item = (MenuItem)args.Sender; - if (args.NewValue.GetValueOrDefault()) - item.Click += OnMenuItemClick; - else - item.Click -= OnMenuItemClick; - }); - // TODO12 Ideally we should make NativeMenuBar inherit MenuBase directly, but it would be a breaking change for 11.x. // Changing default template while keeping old StyleKeyOverride => Menu isn't a breaking change. TemplateProperty.OverrideDefaultValue(new FuncControlTemplate((_, ns) => new NativeMenuBarPresenter @@ -77,17 +61,6 @@ namespace Avalonia.Controls _subscriptions = null; } - [Unstable("To be removed in 12.0, NativeMenuBar now has a default template.")] // TODO12 - public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) - { - menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); - } - - private static void OnMenuItemClick(object? sender, RoutedEventArgs e) - { - (((MenuItem)sender!).DataContext as INativeMenuItemExporterEventsImplBridge)?.RaiseClicked(); - } - private void SubscribeToToplevel(TopLevel topLevel, MenuBase menu) { _subscriptions?.Dispose(); diff --git a/src/Avalonia.Controls/NativeMenuBarPresenter.cs b/src/Avalonia.Controls/NativeMenuBarPresenter.cs index 286f80d699..c0e790c466 100644 --- a/src/Avalonia.Controls/NativeMenuBarPresenter.cs +++ b/src/Avalonia.Controls/NativeMenuBarPresenter.cs @@ -32,9 +32,7 @@ internal class NativeMenuBarPresenter : Menu [!MenuItem.CommandParameterProperty] = nativeItem.GetObservable(NativeMenuItem.CommandParameterProperty).ToBinding(), [!MenuItem.InputGestureProperty] = nativeItem.GetObservable(NativeMenuItem.GestureProperty).ToBinding(), - [!MenuItem.ToggleTypeProperty] = nativeItem.GetObservable(NativeMenuItem.ToggleTypeProperty) - // TODO12 remove NativeMenuItemToggleType - .Select(v => (MenuItemToggleType)v).ToBinding(), + [!MenuItem.ToggleTypeProperty] = nativeItem.GetObservable(NativeMenuItem.ToggleTypeProperty).ToBinding(), [!ToolTip.TipProperty] = nativeItem.GetObservable(NativeMenuItem.ToolTipProperty).ToBinding(), }; diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 6c093ff1f6..b0b6bb611f 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -119,11 +119,11 @@ namespace Avalonia.Controls } /// - public static readonly StyledProperty ToggleTypeProperty = - AvaloniaProperty.Register(nameof(ToggleType)); + public static readonly StyledProperty ToggleTypeProperty = + AvaloniaProperty.Register(nameof(ToggleType)); /// - public NativeMenuItemToggleType ToggleType + public MenuItemToggleType ToggleType { get => GetValue(ToggleTypeProperty); set => SetValue(ToggleTypeProperty, value); @@ -232,12 +232,4 @@ namespace Avalonia.Controls } } } - - // TODO12: remove this enum and use MenuItemToggleType only - public enum NativeMenuItemToggleType - { - None = MenuItemToggleType.None, - CheckBox = MenuItemToggleType.CheckBox, - Radio = MenuItemToggleType.Radio - } } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index b020aaec34..78595d7bb0 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -260,13 +260,13 @@ namespace Avalonia.FreeDesktop if (name == "toggle-type") { - if (item.ToggleType == NativeMenuItemToggleType.CheckBox) + if (item.ToggleType == MenuItemToggleType.CheckBox) return VariantValue.String("checkmark"); - if (item.ToggleType == NativeMenuItemToggleType.Radio) + if (item.ToggleType == MenuItemToggleType.Radio) return VariantValue.String("radio"); } - if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None) + if (name == "toggle-state" && item.ToggleType != MenuItemToggleType.None) return VariantValue.Int32(item.IsChecked ? 1 : 0); if (name == "icon-data") diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index fd851122f2..f9b3e2d2d6 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -43,7 +43,7 @@ namespace Avalonia.Native.Interop.Impl private void UpdateIsVisible(bool isVisible) => SetIsVisible(isVisible.AsComBool()); private void UpdateIsChecked(bool isChecked) => SetIsChecked(isChecked.AsComBool()); - private void UpdateToggleType(NativeMenuItemToggleType toggleType) + private void UpdateToggleType(MenuItemToggleType toggleType) { SetToggleType((AvnMenuItemToggleType)toggleType); } From 54832179448739b2bcb26dab6872e4070bfe1bab Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 30 Jan 2026 15:32:10 +0000 Subject: [PATCH 02/32] Merge IResourceHost2 into IResourceHost (#20576) * Merge IResourceHost2 into IResourceHost * Update API suppressions --- api/Avalonia.nupkg.xml | 48 +++++++++++++++++++ src/Avalonia.Base/Controls/IResourceHost.cs | 8 ---- .../Controls/ResourceDictionary.cs | 4 +- .../Controls/ResourceNodeExtensions.cs | 22 +++------ .../Controls/ResourceProvider.cs | 6 +-- .../Controls/ResourcesChangedEventArgs.cs | 28 ++++++++--- .../Controls/ResourcesChangedHelper.cs | 45 ----------------- .../Controls/ResourcesChangedToken.cs | 11 ----- src/Avalonia.Base/StyledElement.cs | 39 ++++++--------- src/Avalonia.Base/Styling/StyleBase.cs | 2 +- src/Avalonia.Controls/Application.cs | 16 +------ .../Primitives/TemplatedControl.cs | 6 +-- .../Primitives/VisualLayerManager.cs | 6 +-- src/Avalonia.Controls/TopLevel.cs | 23 ++------- .../Accents/SystemAccentColors.cs | 2 +- .../FluentTheme.xaml.cs | 2 +- .../Data/DynamicResourceExpression.cs | 6 +-- 17 files changed, 109 insertions(+), 165 deletions(-) delete mode 100644 src/Avalonia.Base/Controls/ResourcesChangedHelper.cs delete mode 100644 src/Avalonia.Base/Controls/ResourcesChangedToken.cs diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 275b25e01e..15e956bdc6 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -97,6 +97,12 @@ baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Controls.ResourcesChangedEventArgs.Empty + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -109,6 +115,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -487,6 +499,12 @@ baseline/Avalonia/lib/net6.0/Avalonia.Dialogs.dll current/Avalonia/lib/net6.0/Avalonia.Dialogs.dll + + CP0002 + F:Avalonia.Controls.ResourcesChangedEventArgs.Empty + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -499,6 +517,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -1201,6 +1225,18 @@ baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll + + CP0007 + T:Avalonia.Controls.ResourcesChangedEventArgs + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0007 + T:Avalonia.Controls.ResourcesChangedEventArgs + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.StreamGeometryContext @@ -1225,12 +1261,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0009 + T:Avalonia.Controls.ResourcesChangedEventArgs + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0009 + T:Avalonia.Controls.ResourcesChangedEventArgs + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen diff --git a/src/Avalonia.Base/Controls/IResourceHost.cs b/src/Avalonia.Base/Controls/IResourceHost.cs index 361e3562ec..286f0e36ef 100644 --- a/src/Avalonia.Base/Controls/IResourceHost.cs +++ b/src/Avalonia.Base/Controls/IResourceHost.cs @@ -29,12 +29,4 @@ namespace Avalonia.Controls /// void NotifyHostedResourcesChanged(ResourcesChangedEventArgs e); } - - // TODO12: merge with IResourceHost - internal interface IResourceHost2 : IResourceHost - { - event EventHandler ResourcesChanged2; - - void NotifyHostedResourcesChanged(ResourcesChangedToken token); - } } diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index e0944a5708..f8f6267e7b 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -358,7 +358,7 @@ namespace Avalonia.Controls if (hasResources) { - owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } @@ -385,7 +385,7 @@ namespace Avalonia.Controls if (hasResources) { - owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 36cc00d245..07c1c8d654 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -158,7 +158,7 @@ namespace Avalonia.Controls protected override void Initialize() { - _target.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2); + _target.ResourcesChanged += ResourcesChanged; if (_target is IThemeVariantHost themeVariantHost) { @@ -168,7 +168,7 @@ namespace Avalonia.Controls protected override void Deinitialize() { - _target.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2); + _target.ResourcesChanged -= ResourcesChanged; if (_target is IThemeVariantHost themeVariantHost) { @@ -186,11 +186,6 @@ namespace Avalonia.Controls PublishNext(GetValue()); } - private void ResourcesChanged2(object? sender, ResourcesChangedToken token) - { - PublishNext(GetValue()); - } - private void ActualThemeVariantChanged(object? sender, EventArgs e) { PublishNext(GetValue()); @@ -230,7 +225,7 @@ namespace Avalonia.Controls _target.OwnerChanged += OwnerChanged; _owner = _target.Owner; - _owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2); + _owner?.ResourcesChanged += ResourcesChanged; if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost) { @@ -242,7 +237,7 @@ namespace Avalonia.Controls { _target.OwnerChanged -= OwnerChanged; - _owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2); + _owner?.ResourcesChanged -= ResourcesChanged; if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost) { @@ -270,7 +265,7 @@ namespace Avalonia.Controls private void OwnerChanged(object? sender, EventArgs e) { - _owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2); + _owner?.ResourcesChanged -= ResourcesChanged; if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost) { @@ -279,7 +274,7 @@ namespace Avalonia.Controls _owner = _target.Owner; - _owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2); + _owner?.ResourcesChanged += ResourcesChanged; if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2) { @@ -299,11 +294,6 @@ namespace Avalonia.Controls PublishNext(); } - private void ResourcesChanged2(object? sender, ResourcesChangedToken token) - { - PublishNext(); - } - private object? GetValue() { var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant; diff --git a/src/Avalonia.Base/Controls/ResourceProvider.cs b/src/Avalonia.Base/Controls/ResourceProvider.cs index f0ab42a4a8..f73f11e713 100644 --- a/src/Avalonia.Base/Controls/ResourceProvider.cs +++ b/src/Avalonia.Base/Controls/ResourceProvider.cs @@ -45,7 +45,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider protected void RaiseResourcesChanged() { - Owner?.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } /// @@ -57,7 +57,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider { if (HasResources) { - owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } @@ -70,7 +70,7 @@ public abstract class ResourceProvider : AvaloniaObject, IResourceProvider { if (HasResources) { - owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } diff --git a/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs b/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs index a60c95a2e0..f78910b003 100644 --- a/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs +++ b/src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs @@ -1,10 +1,24 @@ -using System; +using System.Threading; -namespace Avalonia.Controls +namespace Avalonia.Controls; + +/// +/// Represents the event arguments of . +/// The identifies the changes. +/// +/// The sequence number used to identify the changes. +/// +/// For performance reasons, this type is a struct. +/// Avoid using a default instance of this type or its default constructor, call instead. +/// +public readonly record struct ResourcesChangedEventArgs(int SequenceNumber) { - // TODO12: change this to be a struct, remove ResourcesChangedToken - public class ResourcesChangedEventArgs : EventArgs - { - public static new readonly ResourcesChangedEventArgs Empty = new ResourcesChangedEventArgs(); - } + private static int s_lastSequenceNumber; + + /// + /// Creates a new instance of with an auto-incremented sequence number. + /// + /// + public static ResourcesChangedEventArgs Create() + => new(Interlocked.Increment(ref s_lastSequenceNumber)); } diff --git a/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs b/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs deleted file mode 100644 index 8ad121ad3a..0000000000 --- a/src/Avalonia.Base/Controls/ResourcesChangedHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Avalonia.LogicalTree; - -namespace Avalonia.Controls; - -internal static class ResourcesChangedHelper -{ - internal static void NotifyHostedResourcesChanged(this IResourceHost host, ResourcesChangedToken token) - { - if (host is IResourceHost2 host2) - host2.NotifyHostedResourcesChanged(token); - else - host.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); - } - - internal static void NotifyResourcesChanged(this ILogical logical, ResourcesChangedToken token) - { - if (logical is StyledElement styledElement) - styledElement.NotifyResourcesChanged(token); - else - logical.NotifyResourcesChanged(ResourcesChangedEventArgs.Empty); - } - - internal static void SubscribeToResourcesChanged( - this IResourceHost host, - EventHandler handler, - EventHandler handler2) - { - if (host is IResourceHost2 host2) - host2.ResourcesChanged2 += handler2; - else - host.ResourcesChanged += handler; - } - - internal static void UnsubscribeFromResourcesChanged( - this IResourceHost host, - EventHandler handler, - EventHandler handler2) - { - if (host is IResourceHost2 host2) - host2.ResourcesChanged2 -= handler2; - else - host.ResourcesChanged -= handler; - } -} diff --git a/src/Avalonia.Base/Controls/ResourcesChangedToken.cs b/src/Avalonia.Base/Controls/ResourcesChangedToken.cs deleted file mode 100644 index 8de692e729..0000000000 --- a/src/Avalonia.Base/Controls/ResourcesChangedToken.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading; - -namespace Avalonia.Controls; - -internal record struct ResourcesChangedToken(int SequenceNumber) -{ - private static int s_lastSequenceNumber; - - public static ResourcesChangedToken Create() - => new(Interlocked.Increment(ref s_lastSequenceNumber)); -} diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index b7f6262d9f..711d17d014 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -29,7 +29,7 @@ namespace Avalonia IDataContextProvider, ILogical, IThemeVariantHost, - IResourceHost2, + IResourceHost, IStyleHost, ISetLogicalParent, ISetInheritanceParent, @@ -94,8 +94,7 @@ namespace Avalonia private AvaloniaObject? _templatedParent; private bool _dataContextUpdating; private ControlTheme? _implicitTheme; - private EventHandler? _resourcesChanged2; - private ResourcesChangedToken _lastResourcesChangedToken; + private ResourcesChangedEventArgs _lastResourcesChangedEventArgs; /// /// Initializes static members of the class. @@ -150,12 +149,6 @@ namespace Avalonia /// public event EventHandler? ResourcesChanged; - event EventHandler? IResourceHost2.ResourcesChanged2 - { - add => _resourcesChanged2 += value; - remove => _resourcesChanged2 -= value; - } - /// public event EventHandler? ActualThemeVariantChanged; @@ -437,14 +430,11 @@ namespace Avalonia /// void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) - => NotifyResourcesChanged(ResourcesChangedToken.Create()); + => NotifyResourcesChanged(e); /// void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e) - => NotifyResourcesChanged(ResourcesChangedToken.Create()); - - void IResourceHost2.NotifyHostedResourcesChanged(ResourcesChangedToken token) - => NotifyResourcesChanged(token); + => NotifyResourcesChanged(e); /// public bool TryGetResource(object key, ThemeVariant? theme, out object? value) @@ -499,7 +489,7 @@ namespace Avalonia // non-rooted control beacuse it's unlikely that dynamic resources need to be // correct until the control is added to the tree, and it causes a *lot* of // notifications. - NotifyResourcesChanged(ResourcesChangedToken.Create()); + NotifyResourcesChanged(ResourcesChangedEventArgs.Create()); } RaisePropertyChanged(ParentProperty, old, Parent); @@ -552,8 +542,8 @@ namespace Avalonia /// /// Notifies child controls that a change has been made to resources that apply to them. /// - /// The change token. - internal virtual void NotifyChildResourcesChanged(ResourcesChangedToken token) + /// The change token. + internal virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { if (_logicalChildren is object) { @@ -563,7 +553,7 @@ namespace Avalonia { for (var i = 0; i < count; ++i) { - _logicalChildren[i].NotifyResourcesChanged(token); + _logicalChildren[i].NotifyResourcesChanged(e); } } } @@ -895,7 +885,7 @@ namespace Avalonia ReevaluateImplicitTheme(); ApplyStyling(); - NotifyResourcesChanged(ResourcesChangedToken.Create(), propagate: false); + NotifyResourcesChanged(ResourcesChangedEventArgs.Create(), propagate: false); OnAttachedToLogicalTree(e); AttachedToLogicalTree?.Invoke(this, e); @@ -999,23 +989,22 @@ namespace Avalonia } internal void NotifyResourcesChanged( - ResourcesChangedToken token, + ResourcesChangedEventArgs e, bool propagate = true) { // We already got a notification for this element, ignore. - if (token.Equals(_lastResourcesChangedToken)) + if (e.Equals(_lastResourcesChangedEventArgs)) { return; } - _lastResourcesChangedToken = token; + _lastResourcesChangedEventArgs = e; - _resourcesChanged2?.Invoke(this, token); - ResourcesChanged?.Invoke(this, ResourcesChangedEventArgs.Empty); + ResourcesChanged?.Invoke(this, e); if (propagate) { - NotifyChildResourcesChanged(token); + NotifyChildResourcesChanged(e); } } diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index 038fc7c5c3..7dbdb22e9c 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -54,7 +54,7 @@ namespace Avalonia.Styling if (hadResources || _resources.HasResources) { - Owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create()); + Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 0bad04bb23..ff04499c42 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -30,7 +30,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IResourceHost2, IApplicationPlatformEvents, IOptionalFeatureProvider + public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IResourceHost, IApplicationPlatformEvents, IOptionalFeatureProvider { /// /// The application-global data templates. @@ -43,7 +43,6 @@ namespace Avalonia private Action>? _stylesRemoved; private IApplicationLifetime? _applicationLifetime; private bool _setupCompleted; - private EventHandler? _resourcesChanged2; /// /// Defines the property. @@ -62,12 +61,6 @@ namespace Avalonia /// public event EventHandler? ResourcesChanged; - event EventHandler? IResourceHost2.ResourcesChanged2 - { - add => _resourcesChanged2 += value; - remove => _resourcesChanged2 -= value; - } - [Obsolete("Use Application.Current.TryGetFeature() instead.")] public event EventHandler? UrlsOpened; @@ -239,16 +232,9 @@ namespace Avalonia void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e) { - _resourcesChanged2?.Invoke(this, ResourcesChangedToken.Create()); ResourcesChanged?.Invoke(this, e); } - void IResourceHost2.NotifyHostedResourcesChanged(ResourcesChangedToken token) - { - _resourcesChanged2?.Invoke(this, token); - ResourcesChanged?.Invoke(this, ResourcesChangedEventArgs.Empty); - } - void IStyleHost.StylesAdded(IReadOnlyList styles) { _stylesAdded?.Invoke(styles); diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 7444e187be..d8ecfa99e8 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -362,7 +362,7 @@ namespace Avalonia.Controls.Primitives } /// - internal sealed override void NotifyChildResourcesChanged(ResourcesChangedToken token) + internal sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { var count = VisualChildren.Count; @@ -370,11 +370,11 @@ namespace Avalonia.Controls.Primitives { if (VisualChildren[i] is ILogical logical) { - logical.NotifyResourcesChanged(token); + logical.NotifyResourcesChanged(e); } } - base.NotifyChildResourcesChanged(token); + base.NotifyChildResourcesChanged(e); } /// diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 2e1b9a59fb..91aa6b9bd0 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -118,12 +118,12 @@ namespace Avalonia.Controls.Primitives } /// - internal override void NotifyChildResourcesChanged(ResourcesChangedToken token) + internal override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { foreach (var l in _layers) - l.NotifyResourcesChanged(token); + l.NotifyResourcesChanged(e); - base.NotifyChildResourcesChanged(token); + base.NotifyChildResourcesChanged(e); } /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 78c2868b88..8a631fe075 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -121,12 +121,6 @@ namespace Avalonia.Controls (s, h) => s.ResourcesChanged -= h ); - private static readonly WeakEvent - ResourcesChanged2WeakEvent = WeakEvent.Register( - (s, h) => s.ResourcesChanged2 += h, - (s, h) => s.ResourcesChanged2 -= h - ); - private readonly IInputManager? _inputManager; private readonly IToolTipService? _tooltipService; private readonly IAccessKeyHandler? _accessKeyHandler; @@ -144,7 +138,6 @@ namespace Avalonia.Controls private ILayoutManager? _layoutManager; private Border? _transparencyFallbackBorder; private TargetWeakEventSubscriber? _resourcesChangesSubscriber; - private TargetWeakEventSubscriber? _resourcesChangesSubscriber2; private IStorageProvider? _storageProvider; private Screens? _screens; private LayoutDiagnosticBridge? _layoutDiagnosticBridge; @@ -270,25 +263,15 @@ namespace Avalonia.Controls var stylingParent = ((IStyleHost)this).StylingParent; - if (stylingParent is IResourceHost2 applicationResources2) + if (stylingParent is IResourceHost applicationResources2) { - _resourcesChangesSubscriber2 = new TargetWeakEventSubscriber( + _resourcesChangesSubscriber = new TargetWeakEventSubscriber( this, static (target, _, _, token) => { target.NotifyResourcesChanged(token); }); - ResourcesChanged2WeakEvent.Subscribe(applicationResources2, _resourcesChangesSubscriber2); - } - else if (stylingParent is IResourceHost applicationResources) - { - _resourcesChangesSubscriber = new TargetWeakEventSubscriber( - this, static (target, _, _, _) => - { - target.NotifyResourcesChanged(ResourcesChangedToken.Create()); - }); - - ResourcesChangedWeakEvent.Subscribe(applicationResources, _resourcesChangesSubscriber); + ResourcesChangedWeakEvent.Subscribe(applicationResources2, _resourcesChangesSubscriber); } impl.LostFocus += PlatformImpl_LostFocus; diff --git a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs index 95f135a40a..920c66ed1a 100644 --- a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs +++ b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs @@ -154,6 +154,6 @@ internal sealed class SystemAccentColors : ResourceProvider private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e) { _invalidateColors = true; - Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index 53cd36215c..fc03021743 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -63,7 +63,7 @@ namespace Avalonia.Themes.Fluent if (change.Property == DensityStyleProperty) { - Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Create()); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs b/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs index e65282b36b..fbe09fa0de 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs @@ -84,8 +84,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) => PublishValue(); - private void ResourcesChanged2(object? sender, ResourcesChangedToken token) => PublishValue(); - private void ActualThemeVariantChanged(object? sender, EventArgs e) { if (!IsRunning) @@ -134,7 +132,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { if (host is not null) { - host.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2); + host.ResourcesChanged += ResourcesChanged; if (!_overrideThemeVariant && _host is IThemeVariantHost themeVariantHost) { @@ -148,7 +146,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { if (host is not null) { - host.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2); + host.ResourcesChanged -= ResourcesChanged; if (!_overrideThemeVariant && _host is IThemeVariantHost themeVariantHost) themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged; From 0715676f22af7e4d00824ca0c7cd4cee31181439 Mon Sep 17 00:00:00 2001 From: Daniel Lerch <36048059+daniel-lerch@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:15:27 +0100 Subject: [PATCH 03/32] Raise IAvaloniaActivity.Activated on new intent (#20564) Co-authored-by: Emmanuel Hansen --- .../Avalonia.Android/AvaloniaActivity.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index 6841f5ba92..60345e0a50 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -119,22 +119,14 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity activatableLifetime.CurrentIntendActivity = this; } - if (Intent?.Data is { } androidUri - && androidUri.IsAbsolute - && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri)) - { - if (uri.Scheme == Uri.UriSchemeFile) - { - if (AndroidStorageItem.CreateItem(this, androidUri) is { } item) - { - _onActivated?.Invoke(this, new FileActivatedEventArgs(new [] { item })); - } - } - else - { - _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri)); - } - } + HandleIntent(Intent); + } + + protected override void OnNewIntent(Intent? intent) + { + base.OnNewIntent(intent); + + HandleIntent(intent); } protected override void OnStop() @@ -218,6 +210,26 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity _view = new AvaloniaView(this) { Content = initialContent }; } + private void HandleIntent(Intent? intent) + { + if (intent?.Data is { } androidUri + && androidUri.IsAbsolute + && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri)) + { + if (uri.Scheme == Uri.UriSchemeFile || uri.Scheme == "content") + { + if (AndroidStorageItem.CreateItem(this, androidUri) is { } item) + { + _onActivated?.Invoke(this, new FileActivatedEventArgs(new[] { item })); + } + } + else + { + _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri)); + } + } + } + public void OnBackInvoked() { var eventArgs = new AndroidBackRequestedEventArgs(); From 7d9c8eeb7ba5da48e4ce06ce3efc24ceb3e248fc Mon Sep 17 00:00:00 2001 From: Thad House Date: Mon, 2 Feb 2026 12:07:00 -0800 Subject: [PATCH 04/32] Fix warnings and index bugs in ControlCatalog sample (#20590) --- samples/ControlCatalog.Desktop/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index c4c42a7311..2037b85695 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -45,7 +45,7 @@ namespace ControlCatalog.Desktop double GetScaling() { var idx = Array.IndexOf(args, "--scaling"); - if (idx != 0 && args.Length > idx + 1 && + if (idx >= 0 && args.Length > idx + 1 && double.TryParse(args[idx + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out var scaling)) return scaling; return 1; @@ -114,7 +114,7 @@ namespace ControlCatalog.Desktop { builder.With(new Win32PlatformOptions() { - CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain } + CompositionMode = [Win32CompositionMode.LowLatencyDxgiSwapChain] }); return builder.StartWithClassicDesktopLifetime(args); } @@ -151,14 +151,14 @@ namespace ControlCatalog.Desktop .WithDeveloperTools() .AfterSetup(builder => { - EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin() + EmbedSample.Implementation = OperatingSystem.IsWindows() ? new EmbedSampleWin() : OperatingSystem.IsMacOS() ? new EmbedSampleMac() : OperatingSystem.IsLinux() ? new EmbedSampleGtk() : null; }) .LogToTrace(); - static void SilenceConsole() + private static void SilenceConsole() { new Thread(() => { From 92190e6ee15cb5cccdcfa19940afee453a501498 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 2 Feb 2026 21:57:27 +0000 Subject: [PATCH 05/32] Improve error message for invalid surface size (#20591) --- src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 46be609ec0..7473563d13 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -125,6 +125,14 @@ namespace Avalonia.Skia _currentFramebufferAddress = framebuffer.Address; + // A surface with a width/height of 0 is invalid and can't be created + if (desiredImageInfo.Width <= 0 || desiredImageInfo.Height <= 0) + { + throw new ArgumentException( + $"Unable to create a surface with size {desiredImageInfo.Width}x{desiredImageInfo.Height}", + nameof(desiredImageInfo)); + } + var surface = SKSurface.Create(desiredImageInfo, _currentFramebufferAddress, framebuffer.RowBytes, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); From babab4f9cb7b639f3379ee6c0352cd2b2797449a Mon Sep 17 00:00:00 2001 From: Thad House Date: Tue, 3 Feb 2026 01:01:29 -0800 Subject: [PATCH 06/32] Add screen orientation support for Linux DRM (#20154) * On Frame-Buffer-Orientation: Add screen orientation support for Linux frame buffer and DRM applications. This commit introduces support for screen orientation across various components by adding a new `Orientation` property of type `SurfaceOrientation` in `DrmOutputOptions` and `FbDevOutputOptions`. The `IScreenInfoProvider` interface is updated to inherit from `ISurfaceOrientation`, ensuring consistent orientation management. Key changes include: - Enhanced `FramebufferToplevelImpl` to handle orientation in size calculations. - Updated `LibInputBackend` for device input coordinate rotation based on screen orientation. - Implemented `ISurfaceOrientation` in `DrmOutput` and `FbdevOutput` classes. - Modified `DrawingContextImpl` to support canvas rotation based on orientation. - Improved `FramebufferRenderTarget` and `PixelFormatConversionShim` to respect framebuffer orientation during surface creation and pixel format conversions. - Streamlined rendering methods to ensure drawing operations align with the current surface orientation. - Removed redundant code related to framebuffer handling. * Fix review comments, add ability to test * Some formatting * Should be working now * Remove fbdev changes Theres really no point to do this in fbdev. * Better method of rotating and transforming the canvas * Remove breaking changes * Switch to using a 2nd frame buffer in the rotation case * Fix review comments * Incorrect variable * Fix sample --------- Co-authored-by: davidw --- samples/ControlCatalog.Desktop/Program.cs | 32 ++- .../Platform/AndroidScreens.cs | 19 +- .../Platform/ISurfaceOrientation.cs | 6 + .../Platform/SurfaceOrientation.cs | 9 + src/Avalonia.OpenGL/GlConsts.cs | 2 +- src/Avalonia.OpenGL/GlInterface.cs | 2 + .../DrmOutputOptions.cs | 9 +- .../FramebufferToplevelImpl.cs | 4 +- .../Input/LibInput/LibInputBackend.cs | 19 +- .../LibInput/LibInputNativeUnsafeMethods.cs | 3 + .../LinuxFramebufferPlatform.cs | 5 +- .../Output/DrmOutput.cs | 196 +++++++++++++++++- 12 files changed, 276 insertions(+), 30 deletions(-) create mode 100644 src/Avalonia.Base/Platform/ISurfaceOrientation.cs create mode 100644 src/Avalonia.Base/Platform/SurfaceOrientation.cs diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 2037b85695..819072db18 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -8,8 +8,10 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Headless; +using Avalonia.LinuxFramebuffer; using Avalonia.LinuxFramebuffer.Output; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.Vulkan; @@ -19,16 +21,9 @@ namespace ControlCatalog.Desktop { static class Program { - private static bool s_useFramebuffer; - [STAThread] static int Main(string[] args) { - if (args.Contains("--fbdev")) - { - s_useFramebuffer = true; - } - if (args.Contains("--wait-for-attach")) { Console.WriteLine("Attach debugger and use 'Set next statement'"); @@ -50,7 +45,22 @@ namespace ControlCatalog.Desktop return scaling; return 1; } - if (s_useFramebuffer) + SurfaceOrientation GetOrientation() + { + var idx = Array.IndexOf(args, "--orientation"); + if (idx >= 0 && args.Length > idx + 1 && + Enum.TryParse(args[idx + 1], true, out var orientation)) + return orientation; + return SurfaceOrientation.Rotation0; + } + string? GetCard() + { + var idx = Array.IndexOf(args, "--card"); + if (idx >= 0 && args.Length > idx + 1) + return args[idx + 1]; + return null; + } + if (args.Contains("--fbdev")) { SilenceConsole(); return builder.StartLinuxFbDev(args, new FbDevOutputOptions() @@ -108,7 +118,11 @@ namespace ControlCatalog.Desktop else if (args.Contains("--drm")) { SilenceConsole(); - return builder.StartLinuxDrm(args, scaling: GetScaling()); + return builder.StartLinuxDrm(args, card: GetCard(), options: new DrmOutputOptions() + { + Scaling = GetScaling(), + Orientation = GetOrientation(), + }); } else if (args.Contains("--dxgi")) { diff --git a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs index 3f1f9bf44e..9b0f1bee38 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs @@ -10,6 +10,7 @@ using AndroidX.Window.Layout; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Platform; using AndroidOrientation = global::Android.Content.Res.Orientation; +using AndroidRotation = global::Android.Views.SurfaceOrientation; namespace Avalonia.Android.Platform; @@ -53,7 +54,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl var orientation = displayContext.Resources?.Configuration?.Orientation; if (orientation == AndroidOrientation.Square) naturalOrientation = ScreenOrientation.None; - else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180) + else if (rotation is AndroidRotation.Rotation0 or AndroidRotation.Rotation180) naturalOrientation = orientation == AndroidOrientation.Landscape ? ScreenOrientation.Landscape : ScreenOrientation.Portrait; @@ -73,14 +74,14 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl CurrentOrientation = (display.Rotation, naturalOrientation) switch { (_, ScreenOrientation.None) => ScreenOrientation.None, - (SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape, - (SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait, - (SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped, - (SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped, - (SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait, - (SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape, - (SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped, - (SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped, + (AndroidRotation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape, + (AndroidRotation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait, + (AndroidRotation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped, + (AndroidRotation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped, + (AndroidRotation.Rotation0, _) => ScreenOrientation.Portrait, + (AndroidRotation.Rotation90, _) => ScreenOrientation.Landscape, + (AndroidRotation.Rotation180, _) => ScreenOrientation.PortraitFlipped, + (AndroidRotation.Rotation270, _) => ScreenOrientation.LandscapeFlipped, _ => ScreenOrientation.Portrait }; } diff --git a/src/Avalonia.Base/Platform/ISurfaceOrientation.cs b/src/Avalonia.Base/Platform/ISurfaceOrientation.cs new file mode 100644 index 0000000000..2f4f2d3241 --- /dev/null +++ b/src/Avalonia.Base/Platform/ISurfaceOrientation.cs @@ -0,0 +1,6 @@ +namespace Avalonia.Platform; + +internal interface ISurfaceOrientation +{ + SurfaceOrientation Orientation { get; } +} diff --git a/src/Avalonia.Base/Platform/SurfaceOrientation.cs b/src/Avalonia.Base/Platform/SurfaceOrientation.cs new file mode 100644 index 0000000000..71835fb6ff --- /dev/null +++ b/src/Avalonia.Base/Platform/SurfaceOrientation.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Platform; + +public enum SurfaceOrientation +{ + Rotation0, + Rotation90, + Rotation180, + Rotation270, +} diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs index 55be3da5f0..c1e1df0639 100644 --- a/src/Avalonia.OpenGL/GlConsts.cs +++ b/src/Avalonia.OpenGL/GlConsts.cs @@ -20,7 +20,7 @@ namespace Avalonia.OpenGL // public const int GL_LINE_STRIP = 0x0003; public const int GL_TRIANGLES = 0x0004; // public const int GL_TRIANGLE_STRIP = 0x0005; -// public const int GL_TRIANGLE_FAN = 0x0006; + public const int GL_TRIANGLE_FAN = 0x0006; // public const int GL_QUADS = 0x0007; // public const int GL_QUAD_STRIP = 0x0008; // public const int GL_POLYGON = 0x0009; diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 12a5ef733e..63d265c41e 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -343,6 +343,8 @@ namespace Avalonia.OpenGL [GetProcAddress("glUniform1f")] public partial void Uniform1f(int location, float falue); + [GetProcAddress("glUniform1i")] + public partial void Uniform1i(int location, int value); [GetProcAddress("glUniformMatrix4fv")] public partial void UniformMatrix4fv(int location, int count, bool transpose, void* value); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index f733fe4d72..42a8ed4f0d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer { @@ -12,7 +13,13 @@ namespace Avalonia.LinuxFramebuffer /// Default: 1.0 /// public double Scaling { get; set; } = 1.0; - + + /// + /// The orientation of the screen relative to the frame buffer memory orientation + /// Default: Normal + /// + public SurfaceOrientation Orientation { get; set; } = SurfaceOrientation.Rotation0; + /// /// If true an two cycle buffer swapping is processed at init. /// Default: True diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 2a086b353b..edd0b1ef72 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -11,7 +11,7 @@ using Avalonia.Rendering.Composition; namespace Avalonia.LinuxFramebuffer { - class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider + class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider, ISurfaceOrientation { private readonly IOutputBackend _outputBackend; private readonly IInputBackend _inputBackend; @@ -81,5 +81,7 @@ using Avalonia.Rendering.Composition; public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public object? TryGetFeature(Type featureType) => null; + + SurfaceOrientation ISurfaceOrientation.Orientation => _outputBackend is ISurfaceOrientation surfaceOrientation ? surfaceOrientation.Orientation : SurfaceOrientation.Rotation0; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index abff7f0936..671508a943 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,8 +1,10 @@ using System; using System.IO; +using System.Linq; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Platform; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { @@ -30,9 +32,22 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput private unsafe void InputThread(IntPtr ctx, LibInputBackendOptions options) { var fd = libinput_get_fd(ctx); + IntPtr[] devices = [.. options.Events!.Select(f => libinput_path_add_device(ctx, f)).Where(d => d != IntPtr.Zero)]; + var screenOrientation = _screen is ISurfaceOrientation surfaceOrientation ? surfaceOrientation.Orientation : SurfaceOrientation.Rotation0; + + float[] matrix = screenOrientation switch + { + SurfaceOrientation.Rotation90 => [0, 1, 0, -1, 0, 1], + SurfaceOrientation.Rotation180 => [-1, 0, 1, 0, -1, 1], + SurfaceOrientation.Rotation270 => [0, -1, 1, 1, 0, 0], + _ => [1, 0, 0, 0, 1, 0], // Normal + }; + + foreach (var device in devices) + { + libinput_device_config_calibration_set_matrix(device, matrix); + } - foreach (var f in options.Events!) - libinput_path_add_device(ctx, f); while (true) { IntPtr ev; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs index c027440708..fbb11905f9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs @@ -55,6 +55,9 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput [DllImport(LibInput)] public extern static IntPtr libinput_path_remove_device(IntPtr device); + [DllImport(LibInput)] + public extern static int libinput_device_config_calibration_set_matrix(IntPtr device, float[] matrix); + [DllImport(LibInput)] public extern static int libinput_get_fd(IntPtr ctx); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 92ec8b475d..0d7d74e01c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -142,7 +142,10 @@ namespace Avalonia.LinuxFramebuffer { get { - EnsureTopLevel(); + if (_topLevel == null) + { + EnsureTopLevel(); + } return _topLevel; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 12ab5ad821..ddd3b6e49f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -13,11 +13,13 @@ using static Avalonia.LinuxFramebuffer.Output.LibDrm; namespace Avalonia.LinuxFramebuffer.Output { - public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface + public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface, ISurfaceOrientation { private DrmOutputOptions _outputOptions = new(); private DrmCard _card; - public PixelSize PixelSize => _mode.Resolution; + public PixelSize PixelSize => Orientation == SurfaceOrientation.Rotation0 || Orientation == SurfaceOrientation.Rotation180 + ? new PixelSize(_mode.Resolution.Width, _mode.Resolution.Height) + : new PixelSize(_mode.Resolution.Height, _mode.Resolution.Width); public double Scaling { @@ -25,6 +27,12 @@ namespace Avalonia.LinuxFramebuffer.Output set => _outputOptions.Scaling = value; } + public SurfaceOrientation Orientation + { + get => _outputOptions.Orientation; + set => _outputOptions.Orientation = value; + } + class SharedContextGraphics : IPlatformGraphics { private readonly IPlatformGraphicsContext _context; @@ -113,6 +121,12 @@ namespace Avalonia.LinuxFramebuffer.Output private IntPtr _currentBo; private IntPtr _gbmTargetSurface; private uint _crtcId; + private int _rotationFbo; + private int _rotationTexture; + private PixelSize _rotatedSize; + private int _rotationProgram; + private int _rotationVbo; + private int _rotationVao; void FbDestroyCallback(IntPtr bo, IntPtr userData) { @@ -157,7 +171,6 @@ namespace Avalonia.LinuxFramebuffer.Output return fbHandle; } - [MemberNotNull(nameof(_card))] [MemberNotNull(nameof(PlatformGraphics))] [MemberNotNull(nameof(FbDestroyDelegate))] @@ -236,6 +249,129 @@ namespace Avalonia.LinuxFramebuffer.Output _mode = mode; _currentBo = bo; + + // Initialize FBO for rotation if needed + var needsRotation = _outputOptions.Orientation != SurfaceOrientation.Rotation0; + if (needsRotation) + { + // For 90/270 rotation, swap width and height + _rotatedSize = (_outputOptions.Orientation == SurfaceOrientation.Rotation90 || + _outputOptions.Orientation == SurfaceOrientation.Rotation270) + ? new PixelSize(modeInfo.Resolution.Height, modeInfo.Resolution.Width) + : modeInfo.Resolution; + + using (_deferredContext.MakeCurrent(_eglSurface)) + { + var gl = _deferredContext.GlInterface; + _rotationFbo = gl.GenFramebuffer(); + _rotationTexture = gl.GenTexture(); + + gl.BindTexture(GlConsts.GL_TEXTURE_2D, _rotationTexture); + gl.TexImage2D(GlConsts.GL_TEXTURE_2D, 0, GlConsts.GL_RGBA, _rotatedSize.Width, _rotatedSize.Height, 0, + GlConsts.GL_RGBA, GlConsts.GL_UNSIGNED_BYTE, IntPtr.Zero); + gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MIN_FILTER, GlConsts.GL_LINEAR); + gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MAG_FILTER, GlConsts.GL_LINEAR); + + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _rotationFbo); + gl.FramebufferTexture2D(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, + GlConsts.GL_TEXTURE_2D, _rotationTexture, 0); + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + + // Create shader program for textured quad + const string vertexShader = @" + attribute vec2 aPos; + attribute vec2 aTexCoord; + varying vec2 vTexCoord; + void main() { + gl_Position = vec4(aPos, 0.0, 1.0); + vTexCoord = aTexCoord; + }"; + + const string fragmentShader = @" + precision mediump float; + varying vec2 vTexCoord; + uniform sampler2D uTexture; + void main() { + gl_FragColor = texture2D(uTexture, vTexCoord); + }"; + + var vs = gl.CreateShader(GlConsts.GL_VERTEX_SHADER); + gl.ShaderSourceString(vs, vertexShader); + gl.CompileShader(vs); + + var fs = gl.CreateShader(GlConsts.GL_FRAGMENT_SHADER); + gl.ShaderSourceString(fs, fragmentShader); + gl.CompileShader(fs); + + _rotationProgram = gl.CreateProgram(); + gl.AttachShader(_rotationProgram, vs); + gl.AttachShader(_rotationProgram, fs); + gl.LinkProgram(_rotationProgram); + gl.DeleteShader(vs); + gl.DeleteShader(fs); + + // Create VBO with quad vertices - texture coords depend on rotation + // Format: x, y, u, v + float[] vertices = _outputOptions.Orientation switch + { + SurfaceOrientation.Rotation90 => new float[] { + // 90° clockwise rotation + -1.0f, -1.0f, 1.0f, 0.0f, // Bottom-left -> Bottom-right of texture + 1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right -> Top-right of texture + 1.0f, 1.0f, 0.0f, 1.0f, // Top-right -> Top-left of texture + -1.0f, 1.0f, 0.0f, 0.0f // Top-left -> Bottom-left of texture + }, + SurfaceOrientation.Rotation180 => new float[] { + // 180° rotation + -1.0f, -1.0f, 1.0f, 1.0f, // Bottom-left -> Top-right of texture + 1.0f, -1.0f, 0.0f, 1.0f, // Bottom-right -> Top-left of texture + 1.0f, 1.0f, 0.0f, 0.0f, // Top-right -> Bottom-left of texture + -1.0f, 1.0f, 1.0f, 0.0f // Top-left -> Bottom-right of texture + }, + SurfaceOrientation.Rotation270 => new float[] { + // 270° clockwise (90° counter-clockwise) rotation + -1.0f, -1.0f, 0.0f, 1.0f, // Bottom-left -> Top-left of texture + 1.0f, -1.0f, 0.0f, 0.0f, // Bottom-right -> Bottom-left of texture + 1.0f, 1.0f, 1.0f, 0.0f, // Top-right -> Bottom-right of texture + -1.0f, 1.0f, 1.0f, 1.0f // Top-left -> Top-right of texture + }, + _ => new float[] { + // No rotation (shouldn't reach here but fallback) + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f + } + }; + + _rotationVbo = gl.GenBuffer(); + _rotationVao = gl.GenVertexArray(); + + gl.BindVertexArray(_rotationVao); + gl.BindBuffer(GlConsts.GL_ARRAY_BUFFER, _rotationVbo); + + fixed (float* ptr = vertices) + { + gl.BufferData(GlConsts.GL_ARRAY_BUFFER, new IntPtr(vertices.Length * sizeof(float)), + new IntPtr(ptr), GlConsts.GL_STATIC_DRAW); + } + + var posAttrib = gl.GetAttribLocationString(_rotationProgram, "aPos"); + gl.EnableVertexAttribArray(posAttrib); + gl.VertexAttribPointer(posAttrib, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), IntPtr.Zero); + + var texAttrib = gl.GetAttribLocationString(_rotationProgram, "aTexCoord"); + gl.EnableVertexAttribArray(texAttrib); + gl.VertexAttribPointer(texAttrib, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), new IntPtr(2 * sizeof(float))); + + gl.BindVertexArray(0); + } + } + else + { + // No rotation needed + _rotatedSize = modeInfo.Resolution; + } if (_outputOptions.EnableInitialBufferSwapping) { @@ -288,7 +424,39 @@ namespace Avalonia.LinuxFramebuffer.Output public void Dispose() { - _parent._deferredContext.GlInterface.Flush(); + var gl = _parent._deferredContext.GlInterface; + + if (_parent._outputOptions.Orientation != SurfaceOrientation.Rotation0) + { + // Rotation enabled - blit from FBO to screen + // Unbind FBO to render to default framebuffer + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + gl.Viewport(0, 0, _parent._mode.Resolution.Width, _parent._mode.Resolution.Height); + + // Clear the screen + gl.ClearColor(0, 0, 0, 1); + gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT); + + // Use the shader program + gl.UseProgram(_parent._rotationProgram); + + // Bind the FBO texture + gl.ActiveTexture(GlConsts.GL_TEXTURE0); + gl.BindTexture(GlConsts.GL_TEXTURE_2D, _parent._rotationTexture); + + // Set texture uniform (texture unit 0) + var texLoc = gl.GetUniformLocationString(_parent._rotationProgram, "uTexture"); + gl.Uniform1i(texLoc, 0); + + // Draw the rotated quad + gl.BindVertexArray(_parent._rotationVao); + gl.DrawArrays(GlConsts.GL_TRIANGLE_FAN, 0, 4); + gl.BindVertexArray(0); + + gl.UseProgram(0); + } + + gl.Flush(); _parent._eglSurface.SwapBuffers(); var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface); @@ -333,7 +501,7 @@ namespace Avalonia.LinuxFramebuffer.Output public IGlContext Context => _parent._deferredContext; - public PixelSize Size => _parent._mode.Resolution; + public PixelSize Size => _parent._rotatedSize; public double Scaling => _parent.Scaling; @@ -342,7 +510,23 @@ namespace Avalonia.LinuxFramebuffer.Output public IGlPlatformSurfaceRenderingSession BeginDraw() { - return new RenderSession(_parent, _parent._deferredContext.MakeCurrent(_parent._eglSurface)); + var clearContext = _parent._deferredContext.MakeCurrent(_parent._eglSurface); + var gl = _parent._deferredContext.GlInterface; + + if (_parent._outputOptions.Orientation != SurfaceOrientation.Rotation0) + { + // Bind FBO for rendering when rotation is enabled + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _parent._rotationFbo); + gl.Viewport(0, 0, _parent._rotatedSize.Width, _parent._rotatedSize.Height); + } + else + { + // Render directly to screen when no rotation + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + gl.Viewport(0, 0, _parent._mode.Resolution.Width, _parent._mode.Resolution.Height); + } + + return new RenderSession(_parent, clearContext); } } From d670efd162f2c978d1a88b0fee33308a6586a582 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 03:07:23 +0000 Subject: [PATCH 07/32] Make IPopupHost a private API (#20597) * Make IPopupHost a private API * Update API suppressions --- api/Avalonia.nupkg.xml | 36 +++++++++++++++++++ .../Diagnostics/ObsoletionMessages.cs | 12 ------- .../Primitives/IPopupHost.cs | 5 +-- .../Primitives/OverlayPopupHost.cs | 18 ++-------- .../PopupPositioning/IPopupPositioner.cs | 1 - .../PopupPositioning/PopupPositionRequest.cs | 1 - src/Avalonia.Controls/Primitives/PopupRoot.cs | 11 ------ 7 files changed, 39 insertions(+), 45 deletions(-) delete mode 100644 src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 15e956bdc6..272f5d71c7 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -415,6 +415,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.OverlayPopupHost.CreatePopupHost(Avalonia.Visual,Avalonia.IAvaloniaDependencyResolver) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.PopupRoot.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) @@ -817,6 +835,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.OverlayPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.OverlayPopupHost.CreatePopupHost(Avalonia.Visual,Avalonia.IAvaloniaDependencyResolver) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.PopupRoot.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) diff --git a/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs b/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs deleted file mode 100644 index bfada91abe..0000000000 --- a/src/Avalonia.Base/Diagnostics/ObsoletionMessages.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Diagnostics; - -internal static class ObsoletionMessages -{ - public const string MayBeRemovedInAvalonia12 = "This API may be removed in Avalonia 12. If you depend on this API, please open an issue with details of your use-case."; -} diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index c4f992363e..7ed87682a6 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -1,11 +1,9 @@ using System; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Diagnostics; using Avalonia.Input; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -17,8 +15,7 @@ namespace Avalonia.Controls.Primitives /// () or an which is created /// on an . /// - [NotClientImplementable] - [Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] + [PrivateApi] public interface IPopupHost : IDisposable, IFocusScope { /// diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 3e4fee20b7..d52e0e4d98 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -121,16 +121,6 @@ namespace Avalonia.Controls.Primitives // Nothing to do here: overlay popups are implemented inside the window. } - [Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] - public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, - PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, - PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, - Rect? rect = null) - { - ((IPopupHost)this).ConfigurePosition(new PopupPositionRequest(target, placement, offset, anchor, gravity, - constraintAdjustment, rect, null)); - } - /// void IPopupHost.ConfigurePosition(PopupPositionRequest positionRequest) { @@ -191,12 +181,8 @@ namespace Avalonia.Controls.Primitives double IManagedPopupPositionerPopup.Scaling => 1; - // TODO12: mark PrivateAPI or internal. - [Unstable("PopupHost is considered an internal API. Use Popup or any Popup-based controls (Flyout, Tooltip) instead.")] - public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver) - => CreatePopupHost(target, dependencyResolver, false); - - internal static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer) + [PrivateApi] + public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer) { if (!shouldUseOverlayLayer) { diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index a0b853f2dc..82dead1ed5 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -66,7 +66,6 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// requirement that a popup must intersect with or be at least partially adjacent to its parent /// surface. /// - [Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] public record struct PopupPositionerParameters { private PopupGravity _gravity; diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/PopupPositionRequest.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/PopupPositionRequest.cs index 35e15e3057..ab1002cb9b 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/PopupPositionRequest.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/PopupPositionRequest.cs @@ -4,7 +4,6 @@ using Avalonia.Metadata; namespace Avalonia.Controls.Primitives.PopupPositioning; [PrivateApi] -[Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] public class PopupPositionRequest { internal PopupPositionRequest(Visual target, PlacementMode placement) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index f88ed0b93b..6dc4627f0c 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -136,17 +136,6 @@ namespace Avalonia.Controls.Primitives } } - [Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)] - public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, - PopupAnchor anchor = PopupAnchor.None, - PopupGravity gravity = PopupGravity.None, - PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, - Rect? rect = null) - { - ((IPopupHost)this).ConfigurePosition(new PopupPositionRequest(target, placement, offset, anchor, gravity, - constraintAdjustment, rect, null)); - } - void IPopupHost.ConfigurePosition(PopupPositionRequest request) { _popupPositionRequest = request; From 313bceea90b59e9c1ef163aa3378113eb542313a Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 03:08:24 +0000 Subject: [PATCH 08/32] Remove AutoCompleteBox.BindingEvaluator (#20596) * Remove AutoCompleteBox.BindingEvaluator * Fix AutoCompleteBoxPage sample * Update API suppressions --- api/Avalonia.nupkg.xml | 12 ++ .../Pages/AutoCompleteBoxPage.xaml.cs | 20 +-- .../AutoCompleteBox.Properties.cs | 26 ++- .../AutoCompleteBox/AutoCompleteBox.cs | 153 +++--------------- 4 files changed, 45 insertions(+), 166 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 272f5d71c7..1f49b5fa24 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -31,6 +31,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1 + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.NativeMenuItemToggleType @@ -79,6 +85,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1 + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.NativeMenuItemToggleType diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index d9e64ecee0..47b6eb7f4a 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -7,30 +7,12 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.LogicalTree; +using ControlCatalog.Models; namespace ControlCatalog.Pages { public partial class AutoCompleteBoxPage : UserControl { - public class StateData - { - public string Name { get; private set; } - public string Abbreviation { get; private set; } - public string Capital { get; private set; } - - public StateData(string name, string abbreviatoin, string capital) - { - Name = name; - Abbreviation = abbreviatoin; - Capital = capital; - } - - public override string ToString() - { - return Name; - } - } - private static StateData[] BuildAllStates() { return new StateData[] diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs index c6eab7901e..45566f186b 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs @@ -191,6 +191,12 @@ namespace Avalonia.Controls public static readonly StyledProperty InnerRightContentProperty = TextBox.InnerRightContentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty ValueMemberBindingProperty = + AvaloniaProperty.Register(nameof(ValueMemberBinding)); + /// /// Gets or sets the caret index /// @@ -311,26 +317,16 @@ namespace Avalonia.Controls } /// - /// Gets or sets the that - /// is used to get the values for display in the text portion of - /// the - /// control. + /// Gets or sets the that is used to get the values for display in the text portion + /// of the control. /// - /// The object used - /// when binding to a collection property. + /// The object used when binding to a collection property. [AssignBinding] [InheritDataTypeFromItems(nameof(ItemsSource))] public BindingBase? ValueMemberBinding { - get => _valueBindingEvaluator?.ValueBinding; - set - { - if (ValueMemberBinding != value) - { - _valueBindingEvaluator = new BindingEvaluator(value); - OnValueMemberBindingChanged(value); - } - } + get => GetValue(ValueMemberBindingProperty); + set => SetValue(ValueMemberBindingProperty, value); } /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index ffa95285e5..880fd0ac8a 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -189,7 +189,7 @@ namespace Avalonia.Controls /// /// A control that can provide updated string values from a binding. /// - private BindingEvaluator? _valueBindingEvaluator; + private BindingEvaluator? _valueMemberBindingEvaluator; /// /// A weak subscription for the collection changed event. @@ -439,6 +439,7 @@ namespace Avalonia.Controls if (!_settingItemTemplateFromValueMemberBinding) _itemTemplateIsFromValueMemberBinding = false; } + private void OnValueMemberBindingChanged(BindingBase? value) { if (_itemTemplateIsFromValueMemberBinding) @@ -460,6 +461,16 @@ namespace Avalonia.Controls } } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ValueMemberBindingProperty) + { + OnValueMemberBindingChanged(change.GetNewValue()); + } + } + static AutoCompleteBox() { FocusableProperty.OverrideDefaultValue(true); @@ -1181,25 +1192,6 @@ namespace Avalonia.Controls } } - /// - /// Formats an Item for text comparisons based on Converter - /// and ConverterCulture properties. - /// - /// The object to format. - /// A value indicating whether to clear - /// the data context after the lookup is performed. - /// Formatted Value. - private string? FormatValue(object? value, bool clearDataContext) - { - string? result = FormatValue(value); - if (clearDataContext && _valueBindingEvaluator != null) - { - _valueBindingEvaluator.ClearDataContext(); - } - - return result; - } - /// /// Converts the specified object to a string by using the /// and @@ -1215,9 +1207,13 @@ namespace Avalonia.Controls /// protected virtual string? FormatValue(object? value) { - if (_valueBindingEvaluator != null) + if (ValueMemberBinding is { } valueMemberBinding) { - return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty; + _valueMemberBindingEvaluator ??= new(); + _valueMemberBindingEvaluator.UpdateBinding(valueMemberBinding); + var result = _valueMemberBindingEvaluator.Evaluate(value) ?? string.Empty; + _valueMemberBindingEvaluator.ClearDataContext(); + return result; } return value == null ? String.Empty : value.ToString(); @@ -1451,9 +1447,6 @@ namespace Avalonia.Controls _view?.Clear(); _view?.AddRange(_newViewItems); - - // Clear the evaluator to discard a reference to the last item - _valueBindingEvaluator?.ClearDataContext(); } finally { @@ -1638,7 +1631,7 @@ namespace Avalonia.Controls if (top != null) { newSelectedItem = top; - string? topString = FormatValue(top, true); + string? topString = FormatValue(top); // Only replace partially when the two words being the same int minLength = Math.Min(topString?.Length ?? 0, Text?.Length ?? 0); @@ -1744,7 +1737,7 @@ namespace Avalonia.Controls } else if (TextSelector != null) { - text = TextSelector(SearchText, FormatValue(newItem, true)); + text = TextSelector(SearchText, FormatValue(newItem)); } else if (ItemSelector != null) { @@ -1752,7 +1745,7 @@ namespace Avalonia.Controls } else { - text = FormatValue(newItem, true); + text = FormatValue(newItem); } // Update the Text property and the TextBox values @@ -2020,109 +2013,5 @@ namespace Avalonia.Controls return string.Equals(value, text, StringComparison.Ordinal); } } - - // TODO12: Remove, this shouldn't be part of the public API. Use our internal BindingEvaluator instead. - /// - /// A framework element that permits a binding to be evaluated in a new data - /// context leaf node. - /// - /// The type of dynamic binding to return. - public class BindingEvaluator : Control - { - /// - /// Gets or sets the string value binding used by the control. - /// - private BindingBase? _binding; - - /// - /// Identifies the Value dependency property. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002:AvaloniaProperty objects should not be owned by a generic type", - Justification = "This property is not supposed to be used from XAML.")] - public static readonly StyledProperty ValueProperty = - AvaloniaProperty.Register, T>(nameof(Value)); - - /// - /// Gets or sets the data item value. - /// - public T Value - { - get => GetValue(ValueProperty); - set => SetValue(ValueProperty, value); - } - - /// - /// Gets or sets the value binding. - /// - public BindingBase? ValueBinding - { - get => _binding; - set - { - _binding = value; - if (value is not null) - Bind(ValueProperty, value); - } - } - - /// - /// Initializes a new instance of the BindingEvaluator class. - /// - public BindingEvaluator() - { } - - /// - /// Initializes a new instance of the BindingEvaluator class, - /// setting the initial binding to the provided parameter. - /// - /// The initial string value binding. - public BindingEvaluator(BindingBase? binding) - : this() - { - ValueBinding = binding; - } - - /// - /// Clears the data context so that the control does not keep a - /// reference to the last-looked up item. - /// - public void ClearDataContext() - { - DataContext = null; - } - - /// - /// Updates the data context of the framework element and returns the - /// updated binding value. - /// - /// The object to use as the data context. - /// If set to true, this parameter will - /// clear the data context immediately after retrieving the value. - /// Returns the evaluated T value of the bound dependency - /// property. - public T GetDynamicValue(object o, bool clearDataContext) - { - DataContext = o; - T value = Value; - if (clearDataContext) - { - DataContext = null; - } - return value; - } - - /// - /// Updates the data context of the framework element and returns the - /// updated binding value. - /// - /// The object to use as the data context. - /// Returns the evaluated T value of the bound dependency - /// property. - public T GetDynamicValue(object? o) - { - DataContext = o; - return Value; - } - } } } From 0fee13f821e406b808f77b8157a1f4f5c8ee2176 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 03:14:51 +0000 Subject: [PATCH 09/32] Add IsAlive property to IRef (#20593) --- src/Avalonia.Base/Media/Imaging/Bitmap.cs | 4 +--- src/Avalonia.Base/Utilities/Ref.cs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index 9c0c20d170..63ec02737f 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -291,9 +291,7 @@ namespace Avalonia.Media.Imaging { get { - // TODO12: We should probably make PlatformImpl to be nullable or make it possible to check - // and fix IRef in general (right now Item is not nullable while it internally is) - if (PlatformImpl.Item == null!) + if (!PlatformImpl.IsAlive) return null; return PlatformImpl; } diff --git a/src/Avalonia.Base/Utilities/Ref.cs b/src/Avalonia.Base/Utilities/Ref.cs index 0b6abe5189..49a7a42710 100644 --- a/src/Avalonia.Base/Utilities/Ref.cs +++ b/src/Avalonia.Base/Utilities/Ref.cs @@ -28,6 +28,10 @@ namespace Avalonia.Utilities /// A reference to the value as the new type but sharing the refcount. IRef CloneAs() where TResult : class; + /// + /// Gets whether the reference still tracks a valid item. + /// + bool IsAlive { get; } /// /// The current refcount of the object tracked in this reference. For debugging/unit test use only. @@ -172,6 +176,8 @@ namespace Avalonia.Utilities return new Ref(item, counter); } + public bool IsAlive => _item is not null; + public int RefCount => _counter?.RefCount ?? throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); } } From 656d69194023f7c17a8e74f800402bec442915a3 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 03:14:54 +0000 Subject: [PATCH 10/32] Move ConstructorArgumentAttribute and remove BindingMode from ReflectionBinding constructor (#20589) * Move ConstructorArgumentAttribute to Avalonia.Base assembly * Move IOptionalFeatureProvider to Avalonia namespace * Remove BindingMode from ReflectionBinding constructor * Update API suppressions --- api/Avalonia.Skia.nupkg.xml | 28 ++ api/Avalonia.nupkg.xml | 264 ++++++++++++++++++ src/Avalonia.Base/Data/CompiledBinding.cs | 2 + src/Avalonia.Base/Data/ReflectionBinding.cs | 13 +- src/Avalonia.Base/Data/TemplateBinding.cs | 1 + .../IOptionalFeatureProvider.cs | 3 +- .../Metadata/ConstructorArgumentAttribute.cs | 17 ++ .../DynamicResourceExtension.cs | 2 + .../ReflectionBindingExtension.cs | 7 - .../RelativeSourceExtension.cs | 1 + src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs | 10 - src/Markup/Avalonia.Markup/Data/Binding.cs | 2 - .../AvaloniaObjectTests_Binding.cs | 46 ++- .../Styling/SetterTests.cs | 3 +- .../Avalonia.Benchmarks/Data/Binding_Setup.cs | 2 +- .../Data/Binding_Values.cs | 2 +- .../TextBoxTests_DataValidation.cs | 8 +- .../Data/BindingTests.cs | 8 +- .../Data/BindingTests_Delay.cs | 19 +- .../Data/BindingTests_Self.cs | 3 +- 20 files changed, 385 insertions(+), 56 deletions(-) create mode 100644 api/Avalonia.Skia.nupkg.xml rename src/Avalonia.Base/{Platform => }/IOptionalFeatureProvider.cs (92%) create mode 100644 src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs diff --git a/api/Avalonia.Skia.nupkg.xml b/api/Avalonia.Skia.nupkg.xml new file mode 100644 index 0000000000..eda59fc334 --- /dev/null +++ b/api/Avalonia.Skia.nupkg.xml @@ -0,0 +1,28 @@ + + + + + CP0008 + T:Avalonia.Skia.ISkiaGpu + baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + + + CP0008 + T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext + baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + + + CP0008 + T:Avalonia.Skia.ISkiaGpu + baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + + + CP0008 + T:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext + baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + + diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 1f49b5fa24..5a424cb8ee 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -19,12 +19,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IOptionalFeatureProvider + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Platform.IReadableBitmapWithAlphaImpl baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.OptionalFeatureProviderExtensions + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -49,6 +61,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0001 + T:Avalonia.Markup.Xaml.ConstructorArgumentAttribute + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -73,12 +91,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IOptionalFeatureProvider + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Platform.IReadableBitmapWithAlphaImpl baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.OptionalFeatureProviderExtensions + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -103,6 +133,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0001 + T:Avalonia.Markup.Xaml.ConstructorArgumentAttribute + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -133,6 +169,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -499,6 +541,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Data.Binding.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll + CP0002 F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache @@ -553,6 +607,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -925,6 +985,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll + + CP0002 + M:Avalonia.Data.Binding.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.dll + + + CP0002 + M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.#ctor(System.String,Avalonia.Data.BindingMode) + baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll + CP0002 F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache @@ -1285,30 +1357,222 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Media.ImmediateDrawingContext + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.StreamGeometryContext baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IPlatformGraphicsContext + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0008 + T:Avalonia.Platform.IPlatformGraphicsWithFeatures + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0008 + T:Avalonia.Platform.IPlatformRenderInterfaceContext + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0008 T:Avalonia.Platform.IWriteableBitmapImpl baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Application + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IPopupImpl + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.ITopLevelImpl + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IWindowBaseImpl + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IWindowImpl + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Metal.IMetalDevice + baseline/Avalonia/lib/net10.0/Avalonia.Metal.dll + current/Avalonia/lib/net10.0/Avalonia.Metal.dll + + + CP0008 + T:Avalonia.OpenGL.Egl.EglContext + baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + + + CP0008 + T:Avalonia.OpenGL.IGlContext + baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + + + CP0008 + T:Avalonia.Vulkan.IVulkanDevice + baseline/Avalonia/lib/net10.0/Avalonia.Vulkan.dll + current/Avalonia/lib/net10.0/Avalonia.Vulkan.dll + + + CP0008 + T:Avalonia.Vulkan.IVulkanPlatformGraphicsContext + baseline/Avalonia/lib/net10.0/Avalonia.Vulkan.dll + current/Avalonia/lib/net10.0/Avalonia.Vulkan.dll + + + CP0008 + T:Avalonia.Media.ImmediateDrawingContext + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.StreamGeometryContext baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IPlatformGraphicsContext + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0008 + T:Avalonia.Platform.IPlatformGraphicsWithFeatures + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0008 + T:Avalonia.Platform.IPlatformRenderInterfaceContext + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0008 T:Avalonia.Platform.IWriteableBitmapImpl baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Application + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IPopupImpl + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.ITopLevelImpl + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IWindowBaseImpl + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Platform.IWindowImpl + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0008 + T:Avalonia.Metal.IMetalDevice + baseline/Avalonia/lib/net8.0/Avalonia.Metal.dll + current/Avalonia/lib/net8.0/Avalonia.Metal.dll + + + CP0008 + T:Avalonia.OpenGL.Egl.EglContext + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + + + CP0008 + T:Avalonia.OpenGL.IGlContext + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + + + CP0008 + T:Avalonia.Vulkan.IVulkanDevice + baseline/Avalonia/lib/net8.0/Avalonia.Vulkan.dll + current/Avalonia/lib/net8.0/Avalonia.Vulkan.dll + + + CP0008 + T:Avalonia.Vulkan.IVulkanPlatformGraphicsContext + baseline/Avalonia/lib/net8.0/Avalonia.Vulkan.dll + current/Avalonia/lib/net8.0/Avalonia.Vulkan.dll + CP0009 T:Avalonia.Controls.ResourcesChangedEventArgs diff --git a/src/Avalonia.Base/Data/CompiledBinding.cs b/src/Avalonia.Base/Data/CompiledBinding.cs index 764b04957e..952a5cddc4 100644 --- a/src/Avalonia.Base/Data/CompiledBinding.cs +++ b/src/Avalonia.Base/Data/CompiledBinding.cs @@ -7,6 +7,7 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.Parsers; +using Avalonia.Metadata; namespace Avalonia.Data; @@ -70,6 +71,7 @@ public class CompiledBinding : BindingBase /// /// Gets or sets the binding path. /// + [ConstructorArgument("path")] public CompiledBindingPath? Path { get; set; } /// diff --git a/src/Avalonia.Base/Data/ReflectionBinding.cs b/src/Avalonia.Base/Data/ReflectionBinding.cs index 1209d47a2e..299f6317b4 100644 --- a/src/Avalonia.Base/Data/ReflectionBinding.cs +++ b/src/Avalonia.Base/Data/ReflectionBinding.cs @@ -8,6 +8,7 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.Parsers; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Data @@ -34,17 +35,6 @@ namespace Avalonia.Data { Path = path; } - - /// - /// Initializes a new instance of the class. - /// - /// The binding path. - /// The binding mode. - public ReflectionBinding(string path, BindingMode mode) - { - Path = path; - Mode = mode; - } /// /// Gets or sets the amount of time, in milliseconds, to wait before updating the binding @@ -95,6 +85,7 @@ namespace Avalonia.Data /// /// Gets or sets the binding path. /// + [ConstructorArgument("path")] public string Path { get; set; } = ""; /// diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs index 428f9c4a11..55b3e74ce5 100644 --- a/src/Avalonia.Base/Data/TemplateBinding.cs +++ b/src/Avalonia.Base/Data/TemplateBinding.cs @@ -48,6 +48,7 @@ namespace Avalonia.Data /// /// Gets or sets the name of the source property on the templated parent. /// + [ConstructorArgument("property")] [InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] public AvaloniaProperty? Property { get; set; } diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/IOptionalFeatureProvider.cs similarity index 92% rename from src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs rename to src/Avalonia.Base/IOptionalFeatureProvider.cs index 27c2243791..55c3ef7984 100644 --- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs +++ b/src/Avalonia.Base/IOptionalFeatureProvider.cs @@ -1,8 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -// TODO12: move to Avalonia namespace. -namespace Avalonia.Platform; +namespace Avalonia; public interface IOptionalFeatureProvider { diff --git a/src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs b/src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs new file mode 100644 index 0000000000..5c0021655b --- /dev/null +++ b/src/Avalonia.Base/Metadata/ConstructorArgumentAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Avalonia.Metadata; + +/// +/// Indicates that a property corresponds to a named parameter in the constructor. +/// +/// The name of the parameter in the constructor. +/// This attribute is used for XAML. +[AttributeUsage(AttributeTargets.Property)] +public sealed class ConstructorArgumentAttribute(string name) : Attribute +{ + /// + /// Gets the name of the parameter in the constructor. + /// + public string Name { get; } = name; +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index f602b1a82d..8737367fb1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Xaml.XamlIl.Runtime; +using Avalonia.Metadata; using Avalonia.Styling; namespace Avalonia.Markup.Xaml.MarkupExtensions @@ -22,6 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions ResourceKey = resourceKey; } + [ConstructorArgument("resourceKey")] public object? ResourceKey { get; set; } public BindingBase ProvideValue(IServiceProvider serviceProvider) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs index 311d151ddb..f4b7864185 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs @@ -22,13 +22,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions /// The binding path. public ReflectionBindingExtension(string path) : base(path) { } - /// - /// Initializes a new instance of the class. - /// - /// The binding path. - /// The binding mode. - public ReflectionBindingExtension(string path, BindingMode mode) : base(path, mode) { } - public ReflectionBinding ProvideValue(IServiceProvider serviceProvider) { return new ReflectionBinding diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs index fce026bb1c..c59d038223 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Data; +using Avalonia.Metadata; namespace Avalonia.Markup.Xaml.MarkupExtensions { diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs index 513b18c7a7..cf3c82fdb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs @@ -32,14 +32,4 @@ namespace Avalonia.Markup.Xaml [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)] Type Resolve (string qualifiedTypeName); } - - // TODO12: Move to Avalonia.Base - [AttributeUsage(AttributeTargets.Property)] - public sealed class ConstructorArgumentAttribute : Attribute - { - public ConstructorArgumentAttribute(string name) - { - - } - } } diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 3a0f8a482e..4e624b6479 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -16,6 +16,4 @@ public class Binding : ReflectionBinding public Binding() { } public Binding(string path) : base(path) { } - - public Binding(string path, BindingMode mode) : base(path, mode) { } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 19ede2bbb3..e47dca2391 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1091,7 +1091,11 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); + target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value)) + { + Mode = BindingMode.TwoWay, + Source = source + }); target.DoubleValue = 123.4; @@ -1105,7 +1109,11 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); + target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value)) + { + Mode = BindingMode.TwoWay, + Source = source + }); Assert.False(source.SetterCalled); } @@ -1116,7 +1124,11 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); + target.Bind(Class1.DoubleValueProperty, new Binding("[0]") + { + Mode = BindingMode.TwoWay, + Source = source + }); Assert.False(source.SetterCalled); } @@ -1127,7 +1139,7 @@ namespace Avalonia.Base.UnitTests var target = new TextBlock(); target.DataContext = null; - target.Bind(TextBlock.TextProperty, new Binding("Missing", BindingMode.TwoWay)); + target.Bind(TextBlock.TextProperty, new Binding("Missing") { Mode = BindingMode.TwoWay }); } [Fact] @@ -1136,7 +1148,7 @@ namespace Avalonia.Base.UnitTests var target = new TextBlock(); target.DataContext = null; - target.Bind(TextBlock.TextProperty, new Binding("[0]", BindingMode.TwoWay)); + target.Bind(TextBlock.TextProperty, new Binding("[0]") { Mode = BindingMode.TwoWay }); } [Theory(Skip = "Will need changes to binding internals in order to pass")] @@ -1147,7 +1159,11 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - var binding = new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }; + var binding = new Binding(nameof(source.Value)) + { + Mode = BindingMode.TwoWay, + Source = source + }; target.Bind(Class1.DoubleValueProperty, binding, priority); target.SetValue(Class1.DoubleValueProperty, 123.4, priority - 1); @@ -1165,7 +1181,11 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - var binding1 = new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }; + var binding1 = new Binding(nameof(source.Value)) + { + Mode = BindingMode.TwoWay, + Source = source + }; var binding2 = new BehaviorSubject(123.4); target.Bind(Class1.DoubleValueProperty, binding1, priority); @@ -1182,7 +1202,11 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new TestTwoWayBindingViewModel(); - target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); + target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value)) + { + Mode = BindingMode.TwoWay, + Source = source + }); target.SetValue(Class1.DoubleValueProperty, 123.4, BindingPriority.Animation); // Setter should not be called because the TwoWay binding with Style priority @@ -1197,7 +1221,11 @@ namespace Avalonia.Base.UnitTests var source1 = new TestTwoWayBindingViewModel(); var source2 = new BehaviorSubject(123.4); - target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source1.Value), BindingMode.TwoWay) { Source = source1 }); + target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source1.Value)) + { + Mode = BindingMode.TwoWay, + Source = source1 + }); target.Bind(Class1.DoubleValueProperty, source2, BindingPriority.Animation); // Setter should not be called because the TwoWay binding with Style priority diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index 0551fa5bc4..7eb2550e52 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -143,8 +143,9 @@ namespace Avalonia.Base.UnitTests.Styling Classes = { "foo" }, }; - var binding = new Binding("Name", BindingMode.OneWay) + var binding = new Binding("Name") { + Mode = BindingMode.OneWay, Converter = new TestConverter(), RelativeSource = new RelativeSource(RelativeSourceMode.Self), }; diff --git a/tests/Avalonia.Benchmarks/Data/Binding_Setup.cs b/tests/Avalonia.Benchmarks/Data/Binding_Setup.cs index 84f5bec10f..e26e636580 100644 --- a/tests/Avalonia.Benchmarks/Data/Binding_Setup.cs +++ b/tests/Avalonia.Benchmarks/Data/Binding_Setup.cs @@ -31,7 +31,7 @@ public class Binding_Setup public void Setup_DataContext_Property_Binding_TwoWay() { var target = _target; - var binding = new Binding(nameof(_data.IntValue), mode: BindingMode.TwoWay); + var binding = new Binding(nameof(_data.IntValue)) { Mode = BindingMode.TwoWay }; for (var i = 0; i < 100; ++i) { diff --git a/tests/Avalonia.Benchmarks/Data/Binding_Values.cs b/tests/Avalonia.Benchmarks/Data/Binding_Values.cs index 7cf30eee4d..fa62292e90 100644 --- a/tests/Avalonia.Benchmarks/Data/Binding_Values.cs +++ b/tests/Avalonia.Benchmarks/Data/Binding_Values.cs @@ -36,7 +36,7 @@ public class Binding_Values _data.IntValue = -1; var target = _target; - var binding = new Binding(nameof(_data.IntValue), mode: BindingMode.TwoWay); + var binding = new Binding(nameof(_data.IntValue)) { Mode = BindingMode.TwoWay }; using var d = target.Bind(TestControl.IntValueProperty, binding); for (var i = 0; i < 100; ++i) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 77dad85e03..8c9a845050 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests var target = new TextBox { DataContext = new ExceptionTest(), - [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay), + [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10)) { Mode = BindingMode.TwoWay }, Template = CreateTemplate(), }; @@ -51,7 +51,7 @@ namespace Avalonia.Controls.UnitTests var target = new TextBox { DataContext = new ExceptionTest(), - [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay), + [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10)) { Mode = BindingMode.TwoWay }, Template = CreateTemplate(), }; @@ -77,7 +77,7 @@ namespace Avalonia.Controls.UnitTests var target = new TextBox { DataContext = new ExceptionTest(), - [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay), + [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10)) { Mode = BindingMode.TwoWay }, Template = CreateTemplate() }; DataValidationErrors.SetErrorConverter(target, err => "Error: " + err); @@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests var target = new TextBox { DataContext = new ExceptionTest(), - [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay), + [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10)) { Mode = BindingMode.TwoWay }, Template = CreateTemplate(), }; diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 42dc681f7c..345c4c3b6d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -544,8 +544,8 @@ namespace Avalonia.Markup.UnitTests.Data var root = new Panel { Children = { target1, target2 } }; var source = new Source { Foo = "foo" }; - using (target1.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime))) - using (target2.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneWayToSource))) + using (target1.Bind(TextBlock.TextProperty, new Binding("Foo") { Mode = BindingMode.OneTime })) + using (target2.Bind(TextBlock.TextProperty, new Binding("Foo") { Mode = BindingMode.OneWayToSource })) { root.DataContext = source; } @@ -643,8 +643,8 @@ namespace Avalonia.Markup.UnitTests.Data Children = { target1, target2 } }; - target1.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.TwoWay)); - target2.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneWayToSource)); + target1.Bind(TextBlock.TextProperty, new Binding("Foo") { Mode = BindingMode.TwoWay }); + target2.Bind(TextBlock.TextProperty, new Binding("Foo") { Mode = BindingMode.OneWayToSource }); Assert.Equal("OneWayToSource", source.Foo); diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Delay.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Delay.cs index a73fbd7bb4..da649bc184 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Delay.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Delay.cs @@ -29,7 +29,11 @@ public class BindingTests_Delay : ScopedTestBase, IDisposable _source = new BindingTests.Source { Foo = InitialFooValue }; _target = new TextBox { DataContext = _source }; - _binding = new Binding(nameof(_source.Foo), BindingMode.TwoWay) { Delay = DelayMilliseconds }; + _binding = new Binding(nameof(_source.Foo)) + { + Mode = BindingMode.TwoWay, + Delay = DelayMilliseconds + }; _bindingExpr = _target.Bind(TextBox.TextProperty, _binding); @@ -120,7 +124,12 @@ public class BindingTests_Delay : ScopedTestBase, IDisposable new TestRoot() { Child = new Panel() { Children = { _target, secondBox } } }; - _target.Bind(TextBox.TextProperty, new Binding(nameof(_source.Foo), BindingMode.TwoWay) { Delay = DelayMilliseconds, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus }); + _target.Bind(TextBox.TextProperty, new Binding(nameof(_source.Foo)) + { + Mode = BindingMode.TwoWay, + Delay = DelayMilliseconds, + UpdateSourceTrigger = UpdateSourceTrigger.LostFocus + }); Assert.True(_target.Focus()); _target.Text = "bar"; @@ -134,7 +143,11 @@ public class BindingTests_Delay : ScopedTestBase, IDisposable [Fact] public void Delayed_Binding_OneWayToSource_DataContext_Change_Should_Update_Source_Immediately() { - _target.Bind(TextBlock.TextProperty, new Binding(nameof(_source.Foo), BindingMode.OneWayToSource) { Delay = DelayMilliseconds }); + _target.Bind(TextBlock.TextProperty, new Binding(nameof(_source.Foo)) + { + Mode = BindingMode.OneWayToSource, + Delay = DelayMilliseconds + }); _target.Text = "bar"; diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs index cd1e0cd0a2..5281993f1e 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs @@ -34,8 +34,9 @@ namespace Avalonia.Markup.UnitTests.Data var target = new TextBlock { Tag = "Hello World!", - [!TextBlock.TextProperty] = new Binding("Tag", BindingMode.TwoWay) + [!TextBlock.TextProperty] = new Binding("Tag") { + Mode = BindingMode.TwoWay, RelativeSource = new RelativeSource(RelativeSourceMode.Self) }, }; From 6d45ee8cd7a1829d2911fbf86e893156d7cd7ab1 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 03:15:15 +0000 Subject: [PATCH 11/32] Make NUnit tests visible in Rider again (#20594) --- .../Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj | 3 ++- .../Avalonia.Headless.NUnit.PerTest.UnitTests.csproj | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Headless.NUnit.PerAssembly.UnitTests/Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj b/tests/Avalonia.Headless.NUnit.PerAssembly.UnitTests/Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj index a701fcefe8..2a2ad17626 100644 --- a/tests/Avalonia.Headless.NUnit.PerAssembly.UnitTests/Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj +++ b/tests/Avalonia.Headless.NUnit.PerAssembly.UnitTests/Avalonia.Headless.NUnit.PerAssembly.UnitTests.csproj @@ -15,7 +15,8 @@ - + + diff --git a/tests/Avalonia.Headless.NUnit.PerTest.UnitTests/Avalonia.Headless.NUnit.PerTest.UnitTests.csproj b/tests/Avalonia.Headless.NUnit.PerTest.UnitTests/Avalonia.Headless.NUnit.PerTest.UnitTests.csproj index a701fcefe8..2a2ad17626 100644 --- a/tests/Avalonia.Headless.NUnit.PerTest.UnitTests/Avalonia.Headless.NUnit.PerTest.UnitTests.csproj +++ b/tests/Avalonia.Headless.NUnit.PerTest.UnitTests/Avalonia.Headless.NUnit.PerTest.UnitTests.csproj @@ -15,7 +15,8 @@ - + + From 934ca7c9f5ab2b8f5ce32604e1912c1cfa36d5a8 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 10:50:15 +0000 Subject: [PATCH 12/32] Release implicit mouse capture on focus lost (#20574) * macOS: Release implicit mouse capture on focus lost * Added native modal test to EmbeddingPage --- .../Pages/EmbeddingPage.axaml | 2 + .../Pages/EmbeddingPage.axaml.cs | 69 +++++++++++++++++++ src/Avalonia.Base/Input/MouseDevice.cs | 2 + src/Avalonia.Base/Input/Pointer.cs | 4 ++ src/Avalonia.Native/TopLevelImpl.cs | 16 ++++- 5 files changed, 91 insertions(+), 2 deletions(-) diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml index 632893bcd0..4d6bc24b47 100644 --- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml +++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml @@ -16,5 +16,7 @@ + + diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs index 93855cd13d..465743afbf 100644 --- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs +++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs @@ -1,10 +1,19 @@ +using System; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Embedding; using Avalonia.Interactivity; +using IntegrationTestApp.Embedding; +using MonoMac.AppKit; +using MonoMac.CoreGraphics; +using MonoMac.ObjCRuntime; namespace IntegrationTestApp; public partial class EmbeddingPage : UserControl { + private const long NSModalResponseContinue = -1002; + public EmbeddingPage() { InitializeComponent(); @@ -19,5 +28,65 @@ public partial class EmbeddingPage : UserControl private void Reset_Click(object? sender, RoutedEventArgs e) { ResetText(); + ModalResultTextBox.Text = ""; + } + + private void RunNativeModalSession_OnClick(object? sender, RoutedEventArgs e) + { + MacHelper.EnsureInitialized(); + + var app = NSApplication.SharedApplication; + var modalWindow = CreateNativeWindow(); + var session = app.BeginModalSession(modalWindow); + + while (true) + { + if (app.RunModalSession(session) != NSModalResponseContinue) + break; + } + + app.EndModalSession(session); + } + + private NSWindow CreateNativeWindow() + { + var button = new Button + { + Name = "ButtonInModal", + Content = "Button" + }; + + AutomationProperties.SetAutomationId(button, "ButtonInModal"); + + var root = new EmbeddableControlRoot + { + Width = 200, + Height = 200, + Content = button + }; + root.Prepare(); + + var window = new NSWindow( + new CGRect(0, 0, root.Width, root.Height), + NSWindowStyle.Titled | NSWindowStyle.Closable, + NSBackingStore.Buffered, + false); + + window.Identifier = "ModalNativeWindow"; + window.WillClose += (_, _) => NSApplication.SharedApplication.StopModal(); + + button.Click += (_, _) => + { + ModalResultTextBox.Text = "Clicked"; + window.Close(); + }; + + if (root.TryGetPlatformHandle() is not { } handle) + throw new InvalidOperationException("Could not get platform handle"); + + window.ContentView = (NSView)Runtime.GetNSObject(handle.Handle)!; + root.StartRendering(); + + return window; } } diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 5a0cdad755..62105c7deb 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -34,6 +34,8 @@ namespace Avalonia.Input _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); } + internal Pointer Pointer => _pointer; + internal static TMouseDevice GetOrCreatePrimary() where TMouseDevice : MouseDevice, new() { if (_primary is TMouseDevice device) diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index f243a2e382..94643fa91e 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -77,6 +77,7 @@ namespace Avalonia.Input if (oldVisual != null) oldVisual.DetachedFromVisualTree -= OnCaptureDetached; Captured = control; + CaptureSource = source; if (source != CaptureSource.Platform) PlatformCapture(control); @@ -115,6 +116,7 @@ namespace Avalonia.Input public IInputElement? Captured { get; private set; } public PointerType Type { get; } + public bool IsPrimary { get; } /// @@ -124,6 +126,8 @@ namespace Avalonia.Input public bool IsGestureRecognitionSkipped { get; set; } + internal CaptureSource CaptureSource { get; private set; } = CaptureSource.Platform; + public void Dispose() { if (Captured != null) diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index d45e50d5c6..c707da379f 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -65,8 +65,8 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface private NativeControlHostImpl? _nativeControlHost; private PlatformBehaviorInhibition? _platformBehaviorInhibition; - private readonly MouseDevice? _mouse; - private readonly PenDevice? _pen; + private readonly MouseDevice _mouse; + private readonly PenDevice _pen; private readonly IKeyboardDevice? _keyboard; private readonly ICursorFactory? _cursorFactory; @@ -480,6 +480,18 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface void IAvnTopLevelEvents.LostFocus() { _parent.LostFocus?.Invoke(); + + // macOS doesn't have the concept of mouse capture. If we're losing the focus during an implicit capture + // (standard mouse down), we should release it to avoid mouse events going to an old window. + var mouse = _parent._mouse; + var captured = mouse.Pointer.Captured; + + if (captured is not null && + mouse.Pointer.CaptureSource == CaptureSource.Implicit && + TopLevel.GetTopLevel(captured as Visual)?.PlatformImpl == _parent) + { + mouse.PlatformCaptureLost(); + } } AvnDragDropEffects IAvnTopLevelEvents.DragEvent(AvnDragEventType type, AvnPoint position, From cac4650c98633d1d7c33199a3e1a9f69342983bb Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 5 Feb 2026 07:24:54 +0000 Subject: [PATCH 13/32] Remove obsolete members from Avalonia.Base (#20613) * Remove CubicBezierEasing * Remove CustomAnimatorBase * Remove BindingPriority.TemplatedParent * Remove CompiledBindingPath.SetRawSource * Remove StyleDiagnostics * Remove RadialGradientBrush.Radius * Remove Color.ToUint32 * Remove DrawingContext.PushPreTransform/PushPostTransform * Remove ICompositionGpuImporterObject.ImportCompeted * Remove IStyleable * Remove AvaloniaResourcesIndex.WriteResources * Remove AvaloniaObjectExtensions.Bind overload * Update API suppressions --- api/Avalonia.nupkg.xml | 264 ++++++++++++++++++ .../Animation/Animation.AnimatorRegistry.cs | 13 +- .../Animation/Easings/CubicBezierEasing.cs | 17 -- .../Animation/ICustomAnimator.cs | 30 +- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 27 -- src/Avalonia.Base/Data/BindingPriority.cs | 5 +- src/Avalonia.Base/Data/CompiledBindingPath.cs | 6 - .../Diagnostics/StyleDiagnostics.cs | 37 --- .../Diagnostics/StyleValueFrameDiagnostic.cs | 3 - .../Diagnostics/StyledElementExtensions.cs | 24 -- src/Avalonia.Base/Media/Color.cs | 7 - src/Avalonia.Base/Media/DrawingContext.cs | 9 +- .../Media/IRadialGradientBrush.cs | 2 - .../Immutable/ImmutableRadialGradientBrush.cs | 2 - .../Media/RadialGradientBrush.cs | 40 --- .../Brushes/ServerSimpleCompositionBrush.cs | 2 +- .../Composition/CompositionExternalMemory.cs | 6 - .../Composition/CompositionInterop.cs | 1 - src/Avalonia.Base/StyledElement.cs | 27 +- src/Avalonia.Base/Styling/ControlTheme.cs | 2 +- src/Avalonia.Base/Styling/IStyleable.cs | 28 -- src/Avalonia.Base/Styling/NestingSelector.cs | 4 +- .../Styling/TypeNameAndClassSelector.cs | 2 +- .../Utilities/AvaloniaResourcesIndex.cs | 8 - src/Avalonia.Controls/ItemsControl.cs | 2 +- .../AvaloniaXamlIlWellKnownTypes.cs | 6 +- .../XamlIlAvaloniaPropertyHelper.cs | 26 +- 27 files changed, 285 insertions(+), 315 deletions(-) delete mode 100644 src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs delete mode 100644 src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs delete mode 100644 src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs delete mode 100644 src/Avalonia.Base/Styling/IStyleable.cs diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 5a424cb8ee..4cf1912a2e 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1,12 +1,48 @@ + + CP0001 + T:Avalonia.Animation.CustomAnimatorBase + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Animation.CustomAnimatorBase`1 + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Animation.Easings.CubicBezierEasing + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Diagnostics.AppliedStyle + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Diagnostics.StyledElementExtensions + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Diagnostics.StyleDiagnostics + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -37,6 +73,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Styling.IStyleable + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -73,12 +115,48 @@ baseline/Avalonia/lib/net6.0/Avalonia.Base.dll current/Avalonia/lib/net6.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Animation.CustomAnimatorBase + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Animation.CustomAnimatorBase`1 + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Animation.Easings.CubicBezierEasing + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Diagnostics.AppliedStyle + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Diagnostics.StyledElementExtensions + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Diagnostics.StyleDiagnostics + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -109,6 +187,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Styling.IStyleable + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -151,6 +235,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Data.BindingPriority.TemplatedParent + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -163,12 +253,36 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Media.RadialGradientBrush.RadiusProperty + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Animation.Animation.SetAnimator(Avalonia.Animation.IAnimationSetter,Avalonia.Animation.CustomAnimatorBase) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.BindingBase,System.Object) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Data.CompiledBindingPathBuilder.SetRawSource(System.Object) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode) @@ -193,6 +307,30 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Color.ToUint32 + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushPostTransform(Avalonia.Matrix) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushPreTransform(Avalonia.Matrix) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushTransformContainer + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.DrawingImage.get_Viewbox @@ -223,6 +361,30 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Immutable.ImmutableRadialGradientBrush.get_Radius + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.IRadialGradientBrush.get_Radius + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.RadialGradientBrush.get_Radius + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.RadialGradientBrush.set_Radius(System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -331,6 +493,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}}) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -589,6 +763,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Data.BindingPriority.TemplatedParent + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -601,12 +781,36 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Media.RadialGradientBrush.RadiusProperty + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Animation.Animation.SetAnimator(Avalonia.Animation.IAnimationSetter,Avalonia.Animation.CustomAnimatorBase) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.BindingBase,System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Controls.ResourcesChangedEventArgs.#ctor baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Data.CompiledBindingPathBuilder.SetRawSource(System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Data.ReflectionBinding.#ctor(System.String,Avalonia.Data.BindingMode) @@ -631,6 +835,30 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Color.ToUint32 + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushPostTransform(Avalonia.Matrix) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushPreTransform(Avalonia.Matrix) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.DrawingContext.PushTransformContainer + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.DrawingImage.get_Viewbox @@ -661,6 +889,30 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Immutable.ImmutableRadialGradientBrush.get_Radius + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.IRadialGradientBrush.get_Radius + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.RadialGradientBrush.get_Radius + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.RadialGradientBrush.set_Radius(System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -769,6 +1021,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}}) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) diff --git a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs index b07bd686b8..da3c4cb8e6 100644 --- a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs +++ b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs @@ -7,17 +7,6 @@ namespace Avalonia.Animation; partial class Animation { - /// - /// Sets the value of the Animator attached property for a setter. - /// - /// The animation setter. - /// The property animator value. - [Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator", true)] - public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value) - { - s_animators[setter] = (value.WrapperType, value.CreateWrapper); - } - /// /// Sets the value of the Animator attached property for a setter. /// @@ -92,4 +81,4 @@ partial class Animation return null; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs b/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs deleted file mode 100644 index 2e43e97da2..0000000000 --- a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Avalonia.Animation.Easings; - -[Obsolete("Use SplineEasing instead")] -public sealed class CubicBezierEasing : IEasing -{ - private CubicBezierEasing() - { - } - - public Point ControlPoint2 { get; set; } - public Point ControlPoint1 { get; set; } - - double IEasing.Ease(double progress) - => throw new NotSupportedException(); -} diff --git a/src/Avalonia.Base/Animation/ICustomAnimator.cs b/src/Avalonia.Base/Animation/ICustomAnimator.cs index 119a6115da..7155af1223 100644 --- a/src/Avalonia.Base/Animation/ICustomAnimator.cs +++ b/src/Avalonia.Base/Animation/ICustomAnimator.cs @@ -2,34 +2,6 @@ using System; using Avalonia.Animation.Animators; namespace Avalonia.Animation; -[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)] -public abstract class CustomAnimatorBase -{ - internal abstract IAnimator CreateWrapper(); - internal abstract Type WrapperType { get; } -} - -[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator", true)] -public abstract class CustomAnimatorBase : CustomAnimatorBase -{ - public abstract T Interpolate(double progress, T oldValue, T newValue); - - internal override Type WrapperType => typeof(AnimatorWrapper); - internal override IAnimator CreateWrapper() => new AnimatorWrapper(this); - - internal class AnimatorWrapper : Animator - { - private readonly CustomAnimatorBase _parent; - - public AnimatorWrapper(CustomAnimatorBase parent) - { - _parent = parent; - } - - public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue); - } -} - public interface ICustomAnimator { internal IAnimator CreateWrapper(); @@ -55,4 +27,4 @@ public abstract class InterpolatingAnimator : ICustomAnimator public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 7c4e67b5cc..3c9c047684 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -227,33 +227,6 @@ namespace Avalonia }; } - /// - /// Binds a property on an to an . - /// - /// The object. - /// The property to bind. - /// The binding. - /// - /// An optional anchor from which to locate required context. When binding to objects that - /// are not in the logical tree, certain types of binding need an anchor into the tree in - /// order to locate named controls or resources. The parameter - /// can be used to provide this context. - /// - /// An which can be used to cancel the binding. - [Obsolete("Use AvaloniaObject.Bind(AvaloniaProperty, IBinding")] - public static IDisposable Bind( - this AvaloniaObject target, - AvaloniaProperty property, - BindingBase binding, - object? anchor = null) - { - target = target ?? throw new ArgumentNullException(nameof(target)); - property = property ?? throw new ArgumentNullException(nameof(property)); - binding = binding ?? throw new ArgumentNullException(nameof(binding)); - - return target.Bind(property, binding); - } - /// /// Gets a value. /// diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index cb7f559e0a..b817c89f7b 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -46,9 +46,6 @@ namespace Avalonia.Data /// /// The value is uninitialized. /// - Unset = int.MaxValue, - - [Obsolete("Use Template priority"), EditorBrowsable(EditorBrowsableState.Never)] - TemplatedParent = Template, + Unset = int.MaxValue } } diff --git a/src/Avalonia.Base/Data/CompiledBindingPath.cs b/src/Avalonia.Base/Data/CompiledBindingPath.cs index aea320702e..886d89df43 100644 --- a/src/Avalonia.Base/Data/CompiledBindingPath.cs +++ b/src/Avalonia.Base/Data/CompiledBindingPath.cs @@ -203,12 +203,6 @@ namespace Avalonia.Data return this; } - [Obsolete("This method doesn't do anything anymore. Use Binding.Source instead.")] - public CompiledBindingPathBuilder SetRawSource(object? rawSource) - { - return this; - } - public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray()); } diff --git a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs b/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs deleted file mode 100644 index 3db48cd69c..0000000000 --- a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Metadata; -using Avalonia.Styling; - -namespace Avalonia.Diagnostics; - -[PrivateApi] -[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")] -public class StyleDiagnostics -{ - /// - /// Currently applied styles. - /// - public IReadOnlyList AppliedStyles { get; } - - public StyleDiagnostics(IReadOnlyList appliedStyles) - { - AppliedStyles = appliedStyles; - } -} - -[PrivateApi] -[Unstable("Use StyledElementExtensions.GetValueStoreDiagnostic() instead")] -public sealed class AppliedStyle -{ - private readonly StyleInstance _instance; - - internal AppliedStyle(StyleInstance instance) - { - _instance = instance; - } - - public bool HasActivator => _instance.HasActivator; - public bool IsActive => _instance.IsActive(); - public StyleBase Style => (StyleBase)_instance.Source; -} diff --git a/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs index 2b0d6c3c64..27683dbe72 100644 --- a/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs +++ b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs @@ -39,7 +39,4 @@ internal class StyleValueFrameDiagnostic : IValueFrameDiagnostic } } } - - [Unstable("Compatibility with 11.x")] - public AppliedStyle AsAppliedStyle() => new AppliedStyle(_styleInstance); } diff --git a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs b/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs deleted file mode 100644 index 78ab52a6bc..0000000000 --- a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; -using Avalonia.Metadata; -using Avalonia.Styling; - -namespace Avalonia.Diagnostics; - -/// -/// Defines diagnostic extensions on s. -/// -[PrivateApi] -public static class StyledElementExtensions -{ - [Obsolete("Use AvaloniaObjectExtensions.GetValueStoreDiagnostic instead", true)] - public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement) - { - var diagnostics = styledElement.GetValueStore().GetStoreDiagnostic(); - return new StyleDiagnostics(diagnostics.AppliedFrames - .OfType() - .Select(f => f.AsAppliedStyle()) - .ToArray()); - } -} - diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index ba487b1e80..ae16c30320 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -472,13 +472,6 @@ namespace Avalonia.Media return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; } - /// - [Obsolete("Use Color.ToUInt32() instead."), EditorBrowsable(EditorBrowsableState.Never)] - public uint ToUint32() - { - return ToUInt32(); - } - /// /// Returns the HSL color model equivalent of this RGB color. /// diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index 0f73e4af98..df6e7d112b 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -432,15 +432,8 @@ namespace Avalonia.Media _states.Push(new RestoreState(this, RestoreState.PushedStateType.TextOptions)); return new PushedState(this); } - protected abstract void PushTextOptionsCore(TextOptions textOptions); - [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] - public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); - [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] - public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix); - [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] - public PushedState PushTransformContainer() => PushTransform(Matrix.Identity); - + protected abstract void PushTextOptionsCore(TextOptions textOptions); protected abstract void PushTransformCore(Matrix matrix); diff --git a/src/Avalonia.Base/Media/IRadialGradientBrush.cs b/src/Avalonia.Base/Media/IRadialGradientBrush.cs index 9999b8679c..e2e4136527 100644 --- a/src/Avalonia.Base/Media/IRadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/IRadialGradientBrush.cs @@ -20,8 +20,6 @@ namespace Avalonia.Media /// RelativePoint GradientOrigin { get; } - [Obsolete("Use RadiusX/RadiusY")] public double Radius { get; } - /// /// Gets the horizontal radius of the outermost circle of the radial gradient. /// diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs index 016de0d423..3b210874d9 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -85,7 +85,5 @@ namespace Avalonia.Media.Immutable /// public RelativeScalar RadiusY { get; } - - [Obsolete("Use RadiusX/RadiusY")] public double Radius => RadiusX.Scalar; } } diff --git a/src/Avalonia.Base/Media/RadialGradientBrush.cs b/src/Avalonia.Base/Media/RadialGradientBrush.cs index 89acd63980..768f0fec67 100644 --- a/src/Avalonia.Base/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/RadialGradientBrush.cs @@ -27,15 +27,6 @@ namespace Avalonia.Media AvaloniaProperty.Register( nameof(GradientOrigin), RelativePoint.Center); - - /// - /// Defines the property. - /// - [Obsolete("Use RadiusX/RadiusY, note that those properties use _relative_ values, so Radius=0.55 would become RadiusX=55% RadiusY=55%. Radius property is always relative even if the rest of the brush uses absolute values.")] - public static readonly StyledProperty RadiusProperty = - AvaloniaProperty.Register( - nameof(Radius), - 0.5); /// /// Defines the property. @@ -74,9 +65,6 @@ namespace Avalonia.Media /// Gets or sets the horizontal radius of the outermost circle of the radial /// gradient. /// -#pragma warning disable CS0618 // Type or member is obsolete - [DependsOn(nameof(Radius))] -#pragma warning restore CS0618 // Type or member is obsolete public RelativeScalar RadiusX { get { return GetValue(RadiusXProperty); } @@ -87,25 +75,11 @@ namespace Avalonia.Media /// Gets or sets the vertical radius of the outermost circle of the radial /// gradient. /// -#pragma warning disable CS0618 // Type or member is obsolete - [DependsOn(nameof(Radius))] -#pragma warning restore CS0618 // Type or member is obsolete public RelativeScalar RadiusY { get { return GetValue(RadiusYProperty); } set { SetValue(RadiusYProperty, value); } } - - /// - /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial - /// gradient. - /// - [Obsolete("Use RadiusX/RadiusY, note that those properties use _relative_ values, so Radius=0.55 would become RadiusX=55% RadiusY=55%. Radius property is always relative even if the rest of the brush uses absolute values.")] - public double Radius - { - get { return GetValue(RadiusProperty); } - set { SetValue(RadiusProperty, value); } - } /// public override IImmutableBrush ToImmutable() @@ -121,19 +95,5 @@ namespace Avalonia.Media base.SerializeChanges(c, writer); ServerCompositionSimpleRadialGradientBrush.SerializeAllChanges(writer, Center, GradientOrigin, RadiusX, RadiusY); } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { -#pragma warning disable CS0618 // Type or member is obsolete: compatibility code for Radius - if (change.IsEffectiveValueChange && change.Property == RadiusProperty) - { - var compatibilityValue = new RelativeScalar(Radius, RelativeUnit.Relative); - SetCurrentValue(RadiusXProperty, compatibilityValue); - SetCurrentValue(RadiusYProperty, compatibilityValue); - } -#pragma warning restore CS0618 // Type or member is obsolete - - base.OnPropertyChanged(change); - } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs index 653ee88cef..d15b76cca5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs +++ b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerSimpleCompositionBrush.cs @@ -49,7 +49,7 @@ namespace Avalonia.Rendering.Composition.Server partial class ServerCompositionSimpleRadialGradientBrush : IRadialGradientBrush { - public double Radius => RadiusX.Scalar; + } partial class ServerCompositionSimpleSolidColorBrush : ISolidColorBrush diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs index 17fc5c8595..2837df8642 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs @@ -101,12 +101,6 @@ public interface ICompositionGpuImportedObject : IAsyncDisposable /// Task ImportCompleted { get; } - /// - /// ImportCompleted (recommended replacement) - [Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - Task ImportCompeted { get; } - /// /// Indicates if the device context this instance is associated with is no longer available /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs index 2cf6288c02..a66ab701a1 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs @@ -87,7 +87,6 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject public Task ImportCompleted { get; } - public Task ImportCompeted => ImportCompleted; public bool IsLost => Context.IsLost; public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() => diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 711d17d014..d1b960390c 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -21,7 +21,6 @@ namespace Avalonia /// Extends an with the following features: /// /// - An inherited . - /// - Implements to allow styling to work on the styled element. /// - Implements to form part of a logical tree. /// - A collection of class strings for custom styling. /// @@ -35,10 +34,7 @@ namespace Avalonia ISetInheritanceParent, ISupportInitialize, INamed, - IAvaloniaListItemValidator, -#pragma warning disable CS0618 // Type or member is obsolete - IStyleable -#pragma warning restore CS0618 // Type or member is obsolete + IAvaloniaListItemValidator { /// /// Defines the property. @@ -330,9 +326,6 @@ namespace Avalonia bool IResourceNode.HasResources => (_resources?.HasResources ?? false) || (((IResourceNode?)_styles)?.HasResources ?? false); - /// - IAvaloniaReadOnlyList IStyleable.Classes => Classes; - /// bool IStyleHost.IsStylesInitialized => _styles != null; @@ -668,7 +661,7 @@ namespace Avalonia // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. if (_implicitTheme is null) { - var key = GetStyleKey(this); + var key = StyleKey; if (this.TryFindResource(key, out var value) && value is ControlTheme t) _implicitTheme = t; @@ -699,22 +692,6 @@ namespace Avalonia } } - /// - /// Internal getter for so that we only need to suppress the obsolete - /// warning in one place. - /// - /// The element - /// - /// is obsolete and will be removed in a future version, but for backwards - /// compatibility we need to support code which overrides . - /// - internal static Type GetStyleKey(StyledElement e) - { -#pragma warning disable CS0618 // Type or member is obsolete - return ((IStyleable)e).StyleKey; -#pragma warning restore CS0618 // Type or member is obsolete - } - private static void DataContextNotifying(AvaloniaObject o, bool updateStarted) { if (o is StyledElement element) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index fbd869a9a7..138c2f9de5 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -50,7 +50,7 @@ namespace Avalonia.Styling using var activity = Diagnostic.AttachingStyle()? .AddTag(Diagnostic.Tags.Style, this); - if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target))) + if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) { Attach(target, null, type, true); activity?.AddTag(Diagnostic.Tags.SelectorResult, SelectorMatchResult.AlwaysThisType); diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs deleted file mode 100644 index 0768669905..0000000000 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Avalonia.Collections; - -namespace Avalonia.Styling -{ - /// - /// Interface for styleable elements. - /// - [Obsolete("This interface may be removed in 12.0. Use StyledElement, or override StyledElement.StyleKeyOverride to override the StyleKey for a class.")] - public interface IStyleable : INamed - { - /// - /// Gets the list of classes for the control. - /// - IAvaloniaReadOnlyList Classes { get; } - - /// - /// Gets the type by which the control is styled. - /// - [Obsolete("Override StyledElement.StyleKeyOverride instead.")] - Type StyleKey { get; } - - /// - /// Gets the template parent of this element if the control comes from a template. - /// - AvaloniaObject? TemplatedParent { get; } - } -} diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 4f6920c224..07fc24b644 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -23,7 +23,7 @@ namespace Avalonia.Styling { if (theme.TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return theme.TargetType.IsAssignableFrom(StyledElement.GetStyleKey(control)) ? + return theme.TargetType.IsAssignableFrom(control.StyleKey) ? SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } @@ -31,7 +31,7 @@ namespace Avalonia.Styling { if (queryTheme.TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return queryTheme.TargetType.IsAssignableFrom(StyledElement.GetStyleKey(control)) ? + return queryTheme.TargetType.IsAssignableFrom(control.StyleKey) ? SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 4fba8c02c6..4f78d91a38 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -93,7 +93,7 @@ namespace Avalonia.Styling { if (TargetType != null) { - var controlType = StyledElement.GetStyleKey(control) ?? control.GetType(); + var controlType = control.StyleKey ?? control.GetType(); if (IsConcreteType) { diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index 452898937d..d84d9cf737 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -67,14 +67,6 @@ namespace Avalonia.Utilities } } - [Obsolete] - public static void WriteResources(Stream output, List<(string Path, int Size, Func Open)> resources) - { - WriteResources(output, - resources.Select(r => new AvaloniaResourcesEntry { Path = r.Path, Open = r.Open, Size = r.Size }) - .ToList()); - } - public static void WriteResources(Stream output, IReadOnlyList resources) { var entries = new List(); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 0c3e908bbf..a9cf4ca8d8 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -720,7 +720,7 @@ namespace Avalonia.Controls { var itemContainerTheme = ItemContainerTheme; - if (itemContainerTheme?.TargetType?.IsAssignableFrom(GetStyleKey(container)) == true) + if (itemContainerTheme?.TargetType?.IsAssignableFrom(container.StyleKey) == true) { // We have an ItemContainerTheme and it matches the container. Set the Theme // property, and mark the container as having had ItemContainerTheme applied. 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 df54e71108..28ea193adf 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -22,6 +22,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlMethod AvaloniaObjectSetStyledPropertyValue { get; } public IXamlType AvaloniaAttachedPropertyT { get; } public IXamlType BindingBase { get; } + public IXamlType BindingExpressionBase { get; } public IXamlType MultiBinding { get; } public IXamlMethod AvaloniaObjectBindMethod { get; } public IXamlMethod AvaloniaObjectSetValueMethod { get; } @@ -200,6 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers && m.Parameters[0].Name == "StyledProperty`1" && m.Parameters[2].Equals(BindingPriority)); BindingBase = cfg.TypeSystem.GetType("Avalonia.Data.BindingBase"); + BindingExpressionBase = cfg.TypeSystem.GetType("Avalonia.Data.BindingExpressionBase"); MultiBinding = cfg.TypeSystem.GetType("Avalonia.Data.MultiBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); @@ -215,9 +217,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute"); AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); - AvaloniaObjectBindMethod = AvaloniaObjectExtensions.GetMethod("Bind", IDisposable, false, AvaloniaObject, - AvaloniaProperty, - BindingBase, cfg.WellKnownTypes.Object); + AvaloniaObjectBindMethod = AvaloniaObject.GetMethod("Bind", BindingExpressionBase, false, AvaloniaProperty, BindingBase); UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType"); StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 442c7dd495..1a72dcd9a5 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -297,8 +297,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions emitter .Stloc(bloc.Local) .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local); - EmitAnchorAndBind(emitter); + .Ldloc(bloc.Local) + .EmitCall(Types.AvaloniaObjectBindMethod, true); } public override void EmitWithArguments( @@ -308,14 +308,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { 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); + emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -336,8 +329,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions .Stloc(bloc.Local) .Pop() // ignore priority .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local); - EmitAnchorAndBind(emitter); + .Ldloc(bloc.Local) + .EmitCall(Types.AvaloniaObjectBindMethod, true); } public override void EmitWithArguments( @@ -347,14 +340,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { 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); + emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); } } From 9f02346cd6a3814b7860d3a32ca7566c615d5c24 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 5 Feb 2026 08:23:20 +0000 Subject: [PATCH 14/32] Headless NUnit: Handle async SetUp/TearDown (#20612) * Headless NUnit: Fix TearDown not always working * Headless NUnit: Handle async SetUp/TearDown --- .../AvaloniaTestMethodCommand.cs | 130 +++++++++++------- .../NUnitReflectionHelper.cs | 86 ++++++++++++ .../AsyncSetupTests.cs | 32 +++++ .../Avalonia.Headless.UnitTests/SetupTests.cs | 45 ++++++ 4 files changed, 245 insertions(+), 48 deletions(-) create mode 100644 src/Headless/Avalonia.Headless.NUnit/NUnitReflectionHelper.cs create mode 100644 tests/Avalonia.Headless.UnitTests/AsyncSetupTests.cs create mode 100644 tests/Avalonia.Headless.UnitTests/SetupTests.cs diff --git a/src/Headless/Avalonia.Headless.NUnit/AvaloniaTestMethodCommand.cs b/src/Headless/Avalonia.Headless.NUnit/AvaloniaTestMethodCommand.cs index 96dfd701e9..515b9651e3 100644 --- a/src/Headless/Avalonia.Headless.NUnit/AvaloniaTestMethodCommand.cs +++ b/src/Headless/Avalonia.Headless.NUnit/AvaloniaTestMethodCommand.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Threading.Tasks; using Avalonia.Threading; using NUnit.Framework.Interfaces; @@ -13,28 +12,14 @@ internal class AvaloniaTestMethodCommand : TestCommand { private readonly HeadlessUnitTestSession _session; private readonly TestCommand _innerCommand; - private readonly List _beforeTest; - private readonly List _afterTest; - - // There are multiple problems with NUnit integration at the moment when we wrote this integration. - // NUnit doesn't have extensibility API for running on custom dispatcher/sync-context. - // See https://github.com/nunit/nunit/issues/2917 https://github.com/nunit/nunit/issues/2774 - // To workaround that we had to replace inner TestMethodCommand with our own implementation while keeping original hierarchy of commands. - // Which will respect proper async/await awaiting code that works with our session and can be block-awaited to fit in NUnit. - // Also, we need to push BeforeTest/AfterTest callbacks to the very same session call. - // I hope there will be a better solution without reflection, but for now that's it. - private static FieldInfo s_innerCommand = typeof(DelegatingTestCommand) - .GetField("innerCommand", BindingFlags.Instance | BindingFlags.NonPublic)!; - private static FieldInfo s_beforeTest = typeof(BeforeAndAfterTestCommand) - .GetField("BeforeTest", BindingFlags.Instance | BindingFlags.NonPublic)!; - private static FieldInfo s_afterTest = typeof(BeforeAndAfterTestCommand) - .GetField("AfterTest", BindingFlags.Instance | BindingFlags.NonPublic)!; + private readonly List> _beforeTest; + private readonly List> _afterTest; private AvaloniaTestMethodCommand( HeadlessUnitTestSession session, TestCommand innerCommand, - List beforeTest, - List afterTest) + List> beforeTest, + List> afterTest) : base(innerCommand.Test) { _session = session; @@ -45,61 +30,65 @@ internal class AvaloniaTestMethodCommand : TestCommand public static TestCommand ProcessCommand(HeadlessUnitTestSession session, TestCommand command) { - return ProcessCommand(session, command, new List(), new List()); + return ProcessCommand(session, command, [], []); } - private static TestCommand ProcessCommand(HeadlessUnitTestSession session, TestCommand command, List before, List after) + private static TestCommand ProcessCommand( + HeadlessUnitTestSession session, + TestCommand command, + List> before, + List> after) { - if (command is BeforeAndAfterTestCommand beforeAndAfterTestCommand) + var beforeAndAfterTestCommand = command as BeforeAndAfterTestCommand; + if (beforeAndAfterTestCommand is not null) { - if (s_beforeTest.GetValue(beforeAndAfterTestCommand) is Action beforeTest) - { - Action beforeAction = c => before.Add(() => beforeTest(c)); - s_beforeTest.SetValue(beforeAndAfterTestCommand, beforeAction); - } - if (s_afterTest.GetValue(beforeAndAfterTestCommand) is Action afterTest) + ref var beforeTest = ref beforeAndAfterTestCommand.BeforeTest(); + if (beforeTest is not null) { - Action afterAction = c => after.Add(() => afterTest(c)); - s_afterTest.SetValue(beforeAndAfterTestCommand, afterAction); + AddBeforeOrAfterAction(beforeTest, before); + beforeTest = _ => { }; } } - - if (command is DelegatingTestCommand delegatingTestCommand - && s_innerCommand.GetValue(delegatingTestCommand) is TestCommand inner) + + var delegatingTestCommand = command as DelegatingTestCommand; + if (delegatingTestCommand is not null) { - s_innerCommand.SetValue(delegatingTestCommand, ProcessCommand(session, inner, before, after)); + ref var innerCommand = ref delegatingTestCommand.InnerCommand(); + innerCommand = ProcessCommand(session, innerCommand, before, after); } - else if (command is TestMethodCommand methodCommand) + + if (beforeAndAfterTestCommand is not null) { - return new AvaloniaTestMethodCommand(session, methodCommand, before, after); + ref var afterTest = ref beforeAndAfterTestCommand.AfterTest(); + if (afterTest is not null) + { + AddBeforeOrAfterAction(afterTest, after); + afterTest = _ => { }; + } } + + if (delegatingTestCommand is null && command is TestMethodCommand methodCommand) + return new AvaloniaTestMethodCommand(session, methodCommand, before, after); return command; } public override TestResult Execute(TestExecutionContext context) { - return _session.DispatchCore(() => ExecuteTestMethod(context), true, default).GetAwaiter().GetResult(); + return _session.DispatchCore(() => ExecuteTestMethod(context), true, context.CancellationToken).GetAwaiter().GetResult(); } // Unfortunately, NUnit has issues with custom synchronization contexts, which means we need to add some hacks to make it work. private async Task ExecuteTestMethod(TestExecutionContext context) { - _beforeTest.ForEach(a => a()); + foreach (var beforeTest in _beforeTest) + await beforeTest(context); var testMethod = _innerCommand.Test.Method; var methodInfo = testMethod!.MethodInfo; var result = methodInfo.Invoke(context.TestObject, _innerCommand.Test.Arguments); - // Only Task, non generic ValueTask are supported in async context. No ValueTask<> nor F# tasks. - if (result is Task task) - { - await task; - } - else if (result is ValueTask valueTask) - { - await valueTask; - } + await ToTask(result); context.CurrentResult.SetResult(ResultState.Success); @@ -108,10 +97,55 @@ internal class AvaloniaTestMethodCommand : TestCommand if (context.ExecutionStatus != TestExecutionStatus.AbortRequested) { - _afterTest.ForEach(a => a()); + foreach (var afterTest in _afterTest) + await afterTest(context); + Dispatcher.UIThread.RunJobs(); } return context.CurrentResult; } + + private static void AddBeforeOrAfterAction(Action action, List> targets) + { + // We need to extract the SetUp and TearDown methods to run them asynchronously on Avalonia's synchronization context. + if (action.Target is SetUpTearDownItem setUpTearDownItem) + { + var methods = action.Method.Name switch + { + nameof(SetUpTearDownItem.RunSetUp) => setUpTearDownItem.SetUpMethods(), + nameof(SetUpTearDownItem.RunTearDown) => setUpTearDownItem.TearDownMethods(), + _ => null + }; + + if (methods is not null) + { + foreach (var method in methods) + { + targets.Add(context => + { + var result = method.Invoke(method.IsStatic ? null : context.TestObject, null); + return ToTask(result); + }); + } + + return; + } + } + + targets.Add(context => + { + action(context); + return Task.CompletedTask; + }); + } + + private static Task ToTask(object? result) + // Only Task, non generic ValueTask are supported in async context. No ValueTask<> nor F# tasks. + => result switch + { + Task task => task, + ValueTask valueTask => valueTask.AsTask(), + _ => Task.CompletedTask + }; } diff --git a/src/Headless/Avalonia.Headless.NUnit/NUnitReflectionHelper.cs b/src/Headless/Avalonia.Headless.NUnit/NUnitReflectionHelper.cs new file mode 100644 index 0000000000..cd24f49cfd --- /dev/null +++ b/src/Headless/Avalonia.Headless.NUnit/NUnitReflectionHelper.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; +using NUnit.Framework.Internal.Commands; + +namespace Avalonia.Headless.NUnit; + +/// +/// 2023-05-10, original comment from Max about NUnit 3: +/// There are multiple problems with NUnit integration at the moment when we wrote this integration. +/// NUnit doesn't have extensibility API for running on custom dispatcher/sync-context. +/// See https://github.com/nunit/nunit/issues/2917 https://github.com/nunit/nunit/issues/2774 +/// To workaround that we had to replace inner TestMethodCommand with our own implementation while keeping original hierarchy of commands. +/// Which will respect proper async/await awaiting code that works with our session and can be block-awaited to fit in NUnit. +/// Also, we need to push BeforeTest/AfterTest callbacks to the very same session call. +/// I hope there will be a better solution without reflection, but for now that's it. +/// +/// 2026-02-04: the situation hasn't changed at all with NUnit 4. +/// +internal static class NUnitReflectionHelper +{ + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionDelegatingTestCommand.InnerCommandFieldName)] + private static extern ref TestCommand DelegatingTestCommand_InnerCommand(DelegatingTestCommand instance); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionBeforeAndAfterTestCommand.BeforeTestFieldName)] + private static extern ref Action? BeforeAndAfterTestCommand_BeforeTest(BeforeAndAfterTestCommand instance); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionBeforeAndAfterTestCommand.AfterTestFieldName)] + private static extern ref Action? BeforeAndAfterTestCommand_AfterTest(BeforeAndAfterTestCommand instance); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_setUpMethods")] + private static extern ref IList SetUpTearDownItem_SetUpMethods(SetUpTearDownItem instance); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_tearDownMethods")] + private static extern ref IList SetUpTearDownItem_TearDownMethods(SetUpTearDownItem instance); + + extension(DelegatingTestCommand instance) + { + public ref TestCommand InnerCommand() + => ref DelegatingTestCommand_InnerCommand(instance); + } + + extension(BeforeAndAfterTestCommand instance) + { + public ref Action? BeforeTest() + => ref BeforeAndAfterTestCommand_BeforeTest(instance); + + public ref Action? AfterTest() + => ref BeforeAndAfterTestCommand_AfterTest(instance); + } + + extension(SetUpTearDownItem instance) + { + public ref IList SetUpMethods() + => ref SetUpTearDownItem_SetUpMethods(instance); + + public ref IList TearDownMethods() + => ref SetUpTearDownItem_TearDownMethods(instance); + } + + private sealed class ReflectionDelegatingTestCommand : DelegatingTestCommand + { + public ReflectionDelegatingTestCommand(TestCommand innerCommand) + : base(innerCommand) + { + } + + public const string InnerCommandFieldName = nameof(innerCommand); + + public override TestResult Execute(TestExecutionContext context) + => throw new NotSupportedException("Reflection-only type, this method should never be called"); + } + + private sealed class ReflectionBeforeAndAfterTestCommand : BeforeAndAfterTestCommand + { + public ReflectionBeforeAndAfterTestCommand(TestCommand innerCommand) + : base(innerCommand) + { + } + + public const string BeforeTestFieldName = nameof(BeforeTest); + public const string AfterTestFieldName = nameof(AfterTest); + } +} diff --git a/tests/Avalonia.Headless.UnitTests/AsyncSetupTests.cs b/tests/Avalonia.Headless.UnitTests/AsyncSetupTests.cs new file mode 100644 index 0000000000..3f23d661ed --- /dev/null +++ b/tests/Avalonia.Headless.UnitTests/AsyncSetupTests.cs @@ -0,0 +1,32 @@ +#if NUNIT + +using System.Threading.Tasks; + +namespace Avalonia.Headless.UnitTests; + +public class AsyncSetupTests +{ + private static int s_instanceCount; + + [SetUp] + public async Task SetUp() + { + await Task.Delay(100); + ++s_instanceCount; + } + + [AvaloniaTest, TestCase(1), TestCase(2)] + public void Async_Setup_TearDown_Should_Work(int index) + { + AssertHelper.Equal(1, s_instanceCount); + } + + [TearDown] + public async Task TearDown() + { + await Task.Delay(100); + --s_instanceCount; + } +} + +#endif diff --git a/tests/Avalonia.Headless.UnitTests/SetupTests.cs b/tests/Avalonia.Headless.UnitTests/SetupTests.cs new file mode 100644 index 0000000000..77f8d25842 --- /dev/null +++ b/tests/Avalonia.Headless.UnitTests/SetupTests.cs @@ -0,0 +1,45 @@ +using System; + +namespace Avalonia.Headless.UnitTests; + +public class SetupTests +#if XUNIT + : IDisposable +#endif +{ + private static int s_instanceCount; + +#if NUNIT + [SetUp] + public void SetUp() +#elif XUNIT + public SetupTests() +#endif + { + ++s_instanceCount; + } + +#if NUNIT + [AvaloniaTest, TestCase(1), TestCase(2)] +#elif XUNIT + [AvaloniaTheory, InlineData(1), InlineData(2)] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage( + "Usage", + "xUnit1026:Theory methods should use all of their parameters", + Justification = "Used to run the test several times")] +#endif + public void Setup_TearDown_Should_Work(int index) + { + AssertHelper.Equal(1, s_instanceCount); + } + +#if NUNIT + [TearDown] + public void TearDown() +#elif XUNIT + public void Dispose() +#endif + { + --s_instanceCount; + } +} From 5f47b4be530001b4b0318358c7a0ca940de182f0 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 5 Feb 2026 11:51:42 +0000 Subject: [PATCH 15/32] macOS: Ensure render target is at least 1x1 (#20610) * Ensure Metal render target is at least 1x1 * Ensure software render target is at least 1x1 --- native/Avalonia.Native/src/OSX/AvnView.mm | 3 ++- src/Avalonia.Native/TopLevelImpl.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 0da6f43bf4..600dca865f 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -42,7 +42,8 @@ - (void) updateRenderTarget { if(_currentRenderTarget) { - [_currentRenderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])]; + AvnPixelSize size { MAX(_lastPixelSize.Width, 1), MAX(_lastPixelSize.Height, 1) }; + [_currentRenderTarget resize:size withScale:static_cast([[self window] backingScaleFactor])]; [self setNeedsDisplayInRect:[self frame]]; } } diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index c707da379f..9ac24eb85a 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -566,8 +566,8 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { ObjectDisposedException.ThrowIf(_target is null, this); - var w = _parent._savedLogicalSize.Width * _parent._savedScaling; - var h = _parent._savedLogicalSize.Height * _parent._savedScaling; + var w = Math.Max(_parent._savedLogicalSize.Width * _parent._savedScaling, 1); + var h = Math.Max(_parent._savedLogicalSize.Height * _parent._savedScaling, 1); var dpi = _parent._savedScaling * 96; return new DeferredFramebuffer(_target, cb => { From 7b548189180c7f0d8b05dfdd6b77ad29f9888027 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 5 Feb 2026 12:00:25 +0000 Subject: [PATCH 16/32] Remove IDataObject to IDataTransfer wrappers and related methods (#20521) * Remove IDataObject to IDataTransfer wrappers and related methods * Remove IDataObject --- api/Avalonia.Headless.nupkg.xml | 16 + api/Avalonia.nupkg.xml | 276 ++++++++++++++++++ src/Avalonia.Base/Input/DataFormats.cs | 61 +--- src/Avalonia.Base/Input/DataObject.cs | 45 +-- .../Input/DataObjectExtensions.cs | 54 ---- .../Input/DataTransferExtensions.cs | 9 +- src/Avalonia.Base/Input/DragDrop.cs | 10 - src/Avalonia.Base/Input/DragEventArgs.cs | 17 -- src/Avalonia.Base/Input/IDataObject.cs | 32 -- src/Avalonia.Base/Input/Platform/Clipboard.cs | 55 +--- .../DataObjectToDataTransferItemWrapper.cs | 76 ----- .../DataObjectToDataTransferWrapper.cs | 95 ------ .../DataTransferToDataObjectWrapper.cs | 42 --- .../Input/Platform/IClipboard.cs | 55 ---- .../Input/Platform/IPlatformDragSource.cs | 9 +- src/Avalonia.Base/Input/Raw/RawDragEvent.cs | 22 +- .../Platform/InProcessDragSource.cs | 10 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 - .../AvaloniaNativeDragSource.cs | 7 - .../HeadlessWindowExtensions.cs | 8 - .../Avalonia.Headless/HeadlessWindowImpl.cs | 7 - .../Avalonia.Headless/IHeadlessWindow.cs | 2 - src/Windows/Avalonia.Win32/DragSource.cs | 10 +- 23 files changed, 314 insertions(+), 618 deletions(-) create mode 100644 api/Avalonia.Headless.nupkg.xml delete mode 100644 src/Avalonia.Base/Input/DataObjectExtensions.cs delete mode 100644 src/Avalonia.Base/Input/IDataObject.cs delete mode 100644 src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs delete mode 100644 src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs delete mode 100644 src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs diff --git a/api/Avalonia.Headless.nupkg.xml b/api/Avalonia.Headless.nupkg.xml new file mode 100644 index 0000000000..df64d3d757 --- /dev/null +++ b/api/Avalonia.Headless.nupkg.xml @@ -0,0 +1,16 @@ + + + + + CP0002 + M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) + baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll + current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll + + + CP0002 + M:Avalonia.Headless.HeadlessWindowExtensions.DragDrop(Avalonia.Controls.TopLevel,Avalonia.Point,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) + baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll + current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll + + diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 4cf1912a2e..75154ef969 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -43,6 +43,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Input.DataObjectExtensions + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Input.IDataObject + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -157,6 +169,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Input.DataObjectExtensions + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Input.IDataObject + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Media.Fonts.FontFamilyLoader @@ -241,6 +265,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Input.DataFormats.FileNames + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.DataFormats.Files + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.DataFormats.Text + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -289,6 +331,48 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.DataObject.Contains(System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.Get(System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.GetDataFormats + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.Set(System.String,System.Object) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragDrop.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragEventArgs.get_Data + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -301,6 +385,60 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetDataAsync(System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetFormatsAsync + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetTextAsync + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.SetDataObjectAsync(Avalonia.Input.IDataObject) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.SetTextAsync(System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Raw.RawDragEvent.get_Data + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel @@ -769,6 +907,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Input.DataFormats.FileNames + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.DataFormats.Files + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + F:Avalonia.Input.DataFormats.Text + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 F:Avalonia.Media.DrawingImage.ViewboxProperty @@ -817,6 +973,48 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.DataObject.Contains(System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.Get(System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.GetDataFormats + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DataObject.Set(System.String,System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragDrop.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.DragEventArgs.get_Data + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) @@ -829,6 +1027,60 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetDataAsync(System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetFormatsAsync + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.GetTextAsync + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.SetDataObjectAsync(Avalonia.Input.IDataObject) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.SetTextAsync(System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDrop(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.Raw.RawDragEvent.get_Data + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel @@ -1621,6 +1873,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Input.DataObject + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.ImmediateDrawingContext @@ -1729,6 +1987,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Vulkan.dll current/Avalonia/lib/net10.0/Avalonia.Vulkan.dll + + CP0008 + T:Avalonia.Input.DataObject + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.ImmediateDrawingContext @@ -1843,6 +2107,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0009 + T:Avalonia.Input.DataObject + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen @@ -1855,6 +2125,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0009 + T:Avalonia.Input.DataObject + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen diff --git a/src/Avalonia.Base/Input/DataFormats.cs b/src/Avalonia.Base/Input/DataFormats.cs index 2fe818f20d..935f03de25 100644 --- a/src/Avalonia.Base/Input/DataFormats.cs +++ b/src/Avalonia.Base/Input/DataFormats.cs @@ -1,56 +1,11 @@ using System; -using System.ComponentModel; -using Avalonia.Input.Platform; -namespace Avalonia.Input -{ - public static class DataFormats - { - /// - /// Dataformat for plaintext - /// - [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.Text)} instead.")] - public static readonly string Text = nameof(Text); +namespace Avalonia.Input; - /// - /// Dataformat for one or more files. - /// - [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead.")] - public static readonly string Files = nameof(Files); - - /// - /// Dataformat for one or more filenames - /// - /// - /// This data format is supported only on desktop platforms. - /// - [Obsolete($"Use {nameof(DataFormat)}.{nameof(DataFormat.File)} instead."), EditorBrowsable(EditorBrowsableState.Never)] - public static readonly string FileNames = nameof(FileNames); - -#pragma warning disable CS0618 // Type or member is obsolete - - internal static DataFormat ToDataFormat(string format) - { - if (format == Text) - return DataFormat.Text; - - if (format == Files || format == FileNames) - return DataFormat.File; - - return DataFormat.CreateBytesPlatformFormat(format); - } - - internal static string ToString(DataFormat format) - { - if (DataFormat.Text.Equals(format)) - return Text; - - if (DataFormat.File.Equals(format)) - return Files; - - return format.Identifier; - } - -#pragma warning restore CS0618 // Type or member is obsolete - } -} +// TODO13: remove +/// +/// This class does not do anything anymore. +/// Use instead. +/// +[Obsolete($"Use {nameof(DataFormat)} instead", true)] +public static class DataFormats; diff --git a/src/Avalonia.Base/Input/DataObject.cs b/src/Avalonia.Base/Input/DataObject.cs index dc9e2a29af..708ac61e16 100644 --- a/src/Avalonia.Base/Input/DataObject.cs +++ b/src/Avalonia.Base/Input/DataObject.cs @@ -1,40 +1,11 @@ using System; -using System.Collections.Generic; -namespace Avalonia.Input -{ - /// - /// Specific and mutable implementation of the IDataObject interface. - /// - [Obsolete($"Use {nameof(DataTransfer)} instead")] - public class DataObject : IDataObject - { - private readonly Dictionary _items = new(); +namespace Avalonia.Input; - /// - public bool Contains(string dataFormat) - { - return _items.ContainsKey(dataFormat); - } - - /// - public object? Get(string dataFormat) - { - return _items.TryGetValue(dataFormat, out var item) ? item : null; - } - - /// - public IEnumerable GetDataFormats() - { - return _items.Keys; - } - - /// - /// Sets a value to the internal store of the data object with as a key. - /// - public void Set(string dataFormat, object value) - { - _items[dataFormat] = value; - } - } -} +// TODO13: remove +/// +/// This class does not do anything anymore. +/// Use instead. +/// +[Obsolete($"Use {nameof(DataTransfer)} instead", true)] +public sealed class DataObject; diff --git a/src/Avalonia.Base/Input/DataObjectExtensions.cs b/src/Avalonia.Base/Input/DataObjectExtensions.cs deleted file mode 100644 index f361485404..0000000000 --- a/src/Avalonia.Base/Input/DataObjectExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using Avalonia.Platform.Storage; - -#pragma warning disable CS0618 // Type or member is obsolete - -namespace Avalonia.Input -{ - // TODO12: remove - public static class DataObjectExtensions - { - /// - /// Returns a list of files if the DataObject contains files or filenames. - /// . - /// - /// - /// Collection of storage items - files or folders. If format isn't available, returns null. - /// - public static IEnumerable? GetFiles(this IDataObject dataObject) - { - return dataObject.Get(DataFormats.Files) as IEnumerable; - } - - /// - /// Returns a list of filenames if the DataObject contains filenames. - /// - /// - /// - /// Collection of file names. If format isn't available, returns null. - /// - [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] - public static IEnumerable? GetFileNames(this IDataObject dataObject) - { - return (dataObject.Get(DataFormats.FileNames) as IEnumerable) - ?? dataObject.GetFiles()? - .Select(f => f.TryGetLocalPath()) - .Where(p => !string.IsNullOrEmpty(p)) - .OfType(); - } - - /// - /// Returns the dragged text if the DataObject contains any text. - /// - /// - /// - /// A text string. If format isn't available, returns null. - /// - public static string? GetText(this IDataObject dataObject) - { - return dataObject.Get(DataFormats.Text) as string; - } - } -} diff --git a/src/Avalonia.Base/Input/DataTransferExtensions.cs b/src/Avalonia.Base/Input/DataTransferExtensions.cs index 517ca51719..a4f74bef3e 100644 --- a/src/Avalonia.Base/Input/DataTransferExtensions.cs +++ b/src/Avalonia.Base/Input/DataTransferExtensions.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Avalonia.Input.Platform; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; @@ -14,11 +12,6 @@ namespace Avalonia.Input; /// public static class DataTransferExtensions { - [Obsolete] - internal static IDataObject ToLegacyDataObject(this IDataTransfer dataTransfer) - => (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject - ?? new DataTransferToDataObjectWrapper(dataTransfer); - /// /// Gets whether a supports a specific format. /// diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs index 551f436854..33f538c443 100644 --- a/src/Avalonia.Base/Input/DragDrop.cs +++ b/src/Avalonia.Base/Input/DragDrop.cs @@ -122,16 +122,6 @@ namespace Avalonia.Input element.RemoveHandler(DropEvent, handler); } - /// - /// Starts a dragging operation with the given and returns the applied drop effect from the target. - /// - /// - [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")] - public static Task DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects) - { - return DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects); - } - /// /// Starts a dragging operation with the given and returns the applied drop effect from the target. /// diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index d4a058e5b6..e68a6138e0 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Metadata; @@ -9,16 +8,11 @@ namespace Avalonia.Input { private readonly Interactive _target; private readonly Point _targetLocation; - [Obsolete] private IDataObject? _legacyDataObject; public DragDropEffects DragEffects { get; set; } public IDataTransfer DataTransfer { get; } - [Obsolete($"Use {nameof(DataTransfer)} instead.")] - public IDataObject Data - => _legacyDataObject ??= DataTransfer.ToLegacyDataObject(); - public KeyModifiers KeyModifiers { get; } public Point GetPosition(Visual relativeTo) @@ -31,17 +25,6 @@ namespace Avalonia.Input return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0); } - [Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")] - public DragEventArgs( - RoutedEvent routedEvent, - IDataObject data, - Interactive target, - Point targetLocation, - KeyModifiers keyModifiers) - : this(routedEvent, new DataObjectToDataTransferWrapper(data), target, targetLocation, keyModifiers) - { - } - [Unstable("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")] public DragEventArgs( RoutedEvent routedEvent, diff --git a/src/Avalonia.Base/Input/IDataObject.cs b/src/Avalonia.Base/Input/IDataObject.cs deleted file mode 100644 index 560ffc0c02..0000000000 --- a/src/Avalonia.Base/Input/IDataObject.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Avalonia.Input -{ - /// - /// Interface to access information about the data of a drag-and-drop operation. - /// - [Obsolete($"Use {nameof(IDataTransfer)} or {nameof(IAsyncDataTransfer)} instead")] - public interface IDataObject - { - /// - /// Lists all formats which are present in the DataObject. - /// - /// - IEnumerable GetDataFormats(); - - /// - /// Checks whether a given DataFormat is present in this object - /// - /// - bool Contains(string dataFormat); - - /// - /// Tries to get the data of the given DataFormat. - /// - /// - /// Object data. If format isn't available, returns null. - /// - object? Get(string dataFormat); - } -} diff --git a/src/Avalonia.Base/Input/Platform/Clipboard.cs b/src/Avalonia.Base/Input/Platform/Clipboard.cs index 0d961cde3e..c1ebf09196 100644 --- a/src/Avalonia.Base/Input/Platform/Clipboard.cs +++ b/src/Avalonia.Base/Input/Platform/Clipboard.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Compatibility; -using Avalonia.Platform.Storage; +using System.Threading.Tasks; namespace Avalonia.Input.Platform; @@ -15,12 +10,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard private readonly IClipboardImpl _clipboardImpl = clipboardImpl; private IAsyncDataTransfer? _lastDataTransfer; - Task IClipboard.GetTextAsync() - => this.TryGetTextAsync(); - - Task IClipboard.SetTextAsync(string? text) - => this.SetValueAsync(DataFormat.Text, text); - public Task ClearAsync() { _lastDataTransfer?.Dispose(); @@ -29,10 +18,6 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard return _clipboardImpl.ClearAsync(); } - [Obsolete($"Use {nameof(SetDataAsync)} instead.")] - Task IClipboard.SetDataObjectAsync(IDataObject data) - => SetDataAsync(new DataObjectToDataTransferWrapper(data)); - public Task SetDataAsync(IAsyncDataTransfer? dataTransfer) { if (dataTransfer is null) @@ -47,47 +32,9 @@ internal sealed class Clipboard(IClipboardImpl clipboardImpl) : IClipboard public Task FlushAsync() => _clipboardImpl is IFlushableClipboardImpl flushable ? flushable.FlushAsync() : Task.CompletedTask; - async Task IClipboard.GetFormatsAsync() - { - var dataTransfer = await TryGetDataAsync(); - return dataTransfer is null ? [] : dataTransfer.Formats.Select(DataFormats.ToString).ToArray(); - } - - [Obsolete($"Use {nameof(TryGetDataAsync)} instead.")] - async Task IClipboard.GetDataAsync(string format) - { - // No ConfigureAwait(false) here: we want TryGetXxxAsync() below to be called on the initial thread. - using var dataTransfer = await TryGetDataAsync(); - if (dataTransfer is null) - return null; - - if (format == DataFormats.Text) - return await dataTransfer.TryGetTextAsync().ConfigureAwait(false); - - if (format == DataFormats.Files) - return await dataTransfer.TryGetFilesAsync().ConfigureAwait(false); - - if (format == DataFormats.FileNames) - { - return (await dataTransfer.TryGetFilesAsync().ConfigureAwait(false)) - ?.Select(file => file.TryGetLocalPath()) - .Where(path => path is not null) - .ToArray(); - } - - return null; - } - public Task TryGetDataAsync() => _clipboardImpl.TryGetDataAsync(); - [Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")] - async Task IClipboard.TryGetInProcessDataObjectAsync() - { - var dataTransfer = await TryGetInProcessDataAsync().ConfigureAwait(false); - return (dataTransfer as DataObjectToDataTransferWrapper)?.DataObject; - } - public async Task TryGetInProcessDataAsync() { if (_lastDataTransfer is null || _clipboardImpl is not IOwnedClipboardImpl ownedClipboardImpl) diff --git a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs b/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs deleted file mode 100644 index fad3716bc7..0000000000 --- a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferItemWrapper.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using Avalonia.Compatibility; - -namespace Avalonia.Input.Platform; - -/// -/// Wraps a legacy into a . -/// -[Obsolete] -internal sealed class DataObjectToDataTransferItemWrapper( - IDataObject dataObject, - DataFormat[] formats, - string[] formatStrings) - : PlatformDataTransferItem -{ - private readonly IDataObject _dataObject = dataObject; - private readonly DataFormat[] _formats = formats; - private readonly string[] _formatStrings = formatStrings; - - protected override DataFormat[] ProvideFormats() - => _formats; - - protected override object? TryGetRawCore(DataFormat format) - { - var index = Array.IndexOf(Formats, format); - if (index < 0) - return null; - - // We should never have DataFormat.File here, it's been handled by DataObjectToDataTransferWrapper. - Debug.Assert(!DataFormat.File.Equals(format)); - - var formatString = _formatStrings[index]; - var data = _dataObject.Get(formatString); - - if (DataFormat.Text.Equals(format)) - return Convert.ToString(data) ?? string.Empty; - - if (format is DataFormat) - return Convert.ToString(data); - - if (format is DataFormat) - return ConvertLegacyDataToBytes(format, data); - - return null; - } - - private static byte[]? ConvertLegacyDataToBytes(DataFormat format, object? data) - { - switch (data) - { - case null: - return null; - - case byte[] bytes: - return bytes; - - case string str: - return OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsIOS() ? - Encoding.Unicode.GetBytes(str) : - Encoding.UTF8.GetBytes(str); - - case Stream stream: - var length = (int)(stream.Length - stream.Position); - var buffer = new byte[length]; - - stream.ReadExactly(buffer, 0, length); - return buffer; - - default: - return null; - } - } -} diff --git a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs b/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs deleted file mode 100644 index 6fe64134d8..0000000000 --- a/src/Avalonia.Base/Input/Platform/DataObjectToDataTransferWrapper.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Avalonia.Platform.Storage; -using Avalonia.Platform.Storage.FileIO; - -namespace Avalonia.Input.Platform; - -#pragma warning disable CS0618 // Type or member is obsolete: usages of IDataObject and DataFormats - -// TODO12: remove -/// -/// Wraps a legacy into a . -/// -[Obsolete] -internal sealed class DataObjectToDataTransferWrapper(IDataObject dataObject) - : PlatformDataTransfer -{ - public IDataObject DataObject { get; } = dataObject; - - protected override DataFormat[] ProvideFormats() - => DataObject.GetDataFormats().Select(DataFormats.ToDataFormat).Distinct().ToArray(); - - protected override PlatformDataTransferItem[] ProvideItems() - { - var items = new List(); - var nonFileFormats = new List(); - var nonFileFormatStrings = new List(); - var hasFiles = false; - - foreach (var formatString in DataObject.GetDataFormats()) - { - var format = DataFormats.ToDataFormat(formatString); - - if (formatString == DataFormats.Files) - { - if (hasFiles) - continue; - - // This is not ideal as we're reading the filenames ahead of time to generate the appropriate items. - // We don't really care about that for this legacy wrapper. - if (DataObject.Get(formatString) is IEnumerable storageItems) - { - hasFiles = true; - - foreach (var storageItem in storageItems) - items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem)); - } - } - else if (formatString == DataFormats.FileNames) - { - if (hasFiles) - continue; - - if (DataObject.Get(formatString) is IEnumerable fileNames) - { - hasFiles = true; - - foreach (var fileName in fileNames) - { - if (StorageProviderHelpers.TryCreateBclStorageItem(fileName) is { } storageItem) - items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem)); - } - } - } - else - { - nonFileFormats.Add(format); - nonFileFormatStrings.Add(formatString); - } - } - - if (nonFileFormats.Count > 0) - { - Debug.Assert(nonFileFormats.Count == nonFileFormatStrings.Count); - - // Single item containing all formats except for DataFormat.File. - items.Add(new DataObjectToDataTransferItemWrapper( - DataObject, - nonFileFormats.ToArray(), - nonFileFormatStrings.ToArray())); - } - - return items.ToArray(); - } - - [SuppressMessage( - "ReSharper", - "SuspiciousTypeConversion.Global", - Justification = "IDisposable may be implemented externally by the IDataObject instance.")] - public override void Dispose() - => (DataObject as IDisposable)?.Dispose(); -} diff --git a/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs b/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs deleted file mode 100644 index 814adfc4ff..0000000000 --- a/src/Avalonia.Base/Input/Platform/DataTransferToDataObjectWrapper.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Platform.Storage; - -namespace Avalonia.Input.Platform; - -/// -/// Wraps a into a legacy . -/// -[Obsolete] -internal sealed class DataTransferToDataObjectWrapper(IDataTransfer dataTransfer) : IDataObject -{ - public IDataTransfer DataTransfer { get; } = dataTransfer; - - public IEnumerable GetDataFormats() - => DataTransfer.Formats.Select(DataFormats.ToString); - - public bool Contains(string dataFormat) - => DataTransfer.Contains(DataFormats.ToDataFormat(dataFormat)); - - public object? Get(string dataFormat) - { - if (dataFormat == DataFormats.Text) - return DataTransfer.TryGetText(); - - if (dataFormat == DataFormats.Files) - return DataTransfer.TryGetFiles(); - - if (dataFormat == DataFormats.FileNames) - { - return DataTransfer - .TryGetFiles() - ?.Select(file => file.TryGetLocalPath()) - .Where(path => path is not null) - .ToArray(); - } - - return null; - } - -} diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index b75e1b5a40..bc061be268 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Avalonia.Metadata; @@ -10,41 +9,11 @@ namespace Avalonia.Input.Platform [NotClientImplementable] public interface IClipboard { - // TODO12: remove, ClipboardExtensions.TryGetTextAsync exists - /// - /// Returns a string containing the text data on the clipboard. - /// - /// A string containing text data, or null if no corresponding text data is available. - [Obsolete($"Use {nameof(ClipboardExtensions)}.{nameof(ClipboardExtensions.TryGetTextAsync)} instead")] - Task GetTextAsync(); - - // TODO12: remove, ClipboardExtensions.SetTextAsync exists - /// - /// Places a text on the clipboard. - /// - /// The text value to set. - /// - /// By calling this method, the clipboard will get cleared of any possible previous data. - /// - /// If is null or empty, nothing will get placed on the clipboard and this method - /// will be equivalent to . - /// - /// - Task SetTextAsync(string? text); - /// /// Clears any data from the system clipboard. /// Task ClearAsync(); - /// - /// Places a specified non-persistent data object on the system Clipboard. - /// - /// A data object (an object that implements ) to place on the system Clipboard. - /// is null. - [Obsolete($"Use {nameof(SetDataAsync)} instead.")] - Task SetDataObjectAsync(IDataObject data); - /// /// Places a data object on the clipboard. /// The data object is responsible for providing supported formats and data upon request. @@ -69,20 +38,6 @@ namespace Avalonia.Input.Platform /// This method is only supported on the Windows platform. This method will do nothing on other platforms. Task FlushAsync(); - /// - /// Get list of available Clipboard format. - /// - [Obsolete($"Use {nameof(ClipboardExtensions.GetDataFormatsAsync)} instead.")] - Task GetFormatsAsync(); - - /// - /// Retrieves data in a specified format from the Clipboard. - /// - /// A string that specifies the format of the data to retrieve. For a set of predefined data formats, see the class. - /// - [Obsolete($"Use {nameof(TryGetDataAsync)} instead.")] - Task GetDataAsync(string format); - /// /// Retrieves data from the clipboard. /// @@ -95,16 +50,6 @@ namespace Avalonia.Input.Platform /// Task TryGetDataAsync(); - /// - /// If clipboard contains the IDataObject that was set by a previous call to , - /// return said IDataObject instance. Otherwise, return null. - /// Note that not every platform supports that method, on unsupported platforms this method will always return - /// null - /// - /// - [Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")] - Task TryGetInProcessDataObjectAsync(); - /// /// Retrieves the exact instance of a previously placed on the clipboard /// by , if any. diff --git a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs index 44881bcee1..4ad36576ca 100644 --- a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs +++ b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Avalonia.Metadata; namespace Avalonia.Input.Platform @@ -7,12 +6,6 @@ namespace Avalonia.Input.Platform [NotClientImplementable] public interface IPlatformDragSource { - [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")] - Task DoDragDrop( - PointerEventArgs triggerEvent, - IDataObject data, - DragDropEffects allowedEffects); - Task DoDragDropAsync( PointerEventArgs triggerEvent, IDataTransfer dataTransfer, diff --git a/src/Avalonia.Base/Input/Raw/RawDragEvent.cs b/src/Avalonia.Base/Input/Raw/RawDragEvent.cs index 5f7c5e3e04..9ba6f56cbb 100644 --- a/src/Avalonia.Base/Input/Raw/RawDragEvent.cs +++ b/src/Avalonia.Base/Input/Raw/RawDragEvent.cs @@ -1,40 +1,20 @@ -using System; -using Avalonia.Input.Platform; -using Avalonia.Metadata; +using Avalonia.Metadata; namespace Avalonia.Input.Raw { [PrivateApi] public class RawDragEvent : RawInputEventArgs { - [Obsolete] private IDataObject? _legacyDataObject; - public Point Location { get; set; } public IDataTransfer DataTransfer { get; } - [Obsolete($"Use {nameof(DataTransfer)} instead.")] - public IDataObject Data - => _legacyDataObject ??= DataTransfer.ToLegacyDataObject(); - public DragDropEffects Effects { get; set; } public RawDragEventType Type { get; } public KeyModifiers KeyModifiers { get; } - [Obsolete($"Use the constructor accepting a {nameof(IDataTransfer)} instance instead.")] - public RawDragEvent(IDragDropDevice inputDevice, - RawDragEventType type, - IInputRoot root, - Point location, - IDataObject data, - DragDropEffects effects, - RawInputModifiers modifiers) - : this(inputDevice, type, root, location, new DataObjectToDataTransferWrapper(data), effects, modifiers) - { - } - public RawDragEvent( IDragDropDevice inputDevice, RawDragEventType type, diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index aa8694b6ac..615decc753 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls; @@ -31,13 +30,6 @@ namespace Avalonia.Platform _dragDrop = AvaloniaLocator.Current.GetRequiredService(); } - [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")] - Task IPlatformDragSource.DoDragDrop( - PointerEventArgs triggerEvent, - IDataObject data, - DragDropEffects allowedEffects) - => DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects); - public async Task DoDragDropAsync( PointerEventArgs triggerEvent, IDataTransfer dataTransfer, diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a352b414fe..caa8bff00f 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -223,26 +223,12 @@ namespace Avalonia.DesignerSupport.Remote class ClipboardStub : IClipboard { - public Task GetTextAsync() => Task.FromResult(null); - - public Task SetTextAsync(string? text) => Task.CompletedTask; - public Task ClearAsync() => Task.CompletedTask; - [Obsolete($"Use {nameof(SetDataAsync)} instead.")] - public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - public Task SetDataAsync(IAsyncDataTransfer? dataTransfer) => Task.CompletedTask; - public Task GetFormatsAsync() => Task.FromResult([]); - - public Task GetDataAsync(string format) => Task.FromResult(null); - public Task TryGetDataAsync() => Task.FromResult(null); - [Obsolete($"Use {nameof(TryGetInProcessDataAsync)} instead.")] - public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); - public Task FlushAsync() => Task.CompletedTask; public Task TryGetInProcessDataAsync() => Task.FromResult(null); diff --git a/src/Avalonia.Native/AvaloniaNativeDragSource.cs b/src/Avalonia.Native/AvaloniaNativeDragSource.cs index 3c9397f8cc..662d79d1b8 100644 --- a/src/Avalonia.Native/AvaloniaNativeDragSource.cs +++ b/src/Avalonia.Native/AvaloniaNativeDragSource.cs @@ -32,13 +32,6 @@ namespace Avalonia.Native } } - [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")] - Task IPlatformDragSource.DoDragDrop( - PointerEventArgs triggerEvent, - IDataObject data, - DragDropEffects allowedEffects) - => DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects); - public Task DoDragDropAsync( PointerEventArgs triggerEvent, IDataTransfer dataTransfer, diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs index b17a1150fc..7de2243f80 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs @@ -121,14 +121,6 @@ public static class HeadlessWindowExtensions RawInputModifiers modifiers = RawInputModifiers.None) => RunJobsOnImpl(topLevel, w => w.MouseWheel(point, delta, modifiers)); - /// - /// Simulates a drag and drop target event on the headless window/toplevel. This event simulates a user moving files from another app to the current app. - /// - [Obsolete($"Use the overload accepting a {nameof(IDataTransfer)} instance instead.")] - public static void DragDrop(this TopLevel topLevel, Point point, RawDragEventType type, IDataObject data, - DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) => - RunJobsOnImpl(topLevel, w => w.DragDrop(point, type, data, effects, modifiers)); - /// /// Simulates a drag and drop target event on the headless window/toplevel. This event simulates a user moving files from another app to the current app. /// diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 8f80e22138..e3fc75eac4 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -349,13 +349,6 @@ namespace Avalonia.Headless point, delta, modifiers)); } - [Obsolete($"Use the overload accepting a {nameof(IDataTransfer)} instance instead.")] - void IHeadlessWindow.DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers) - { - var device = AvaloniaLocator.Current.GetRequiredService(); - Input?.Invoke(new RawDragEvent(device, type, InputRoot!, point, new DataObjectToDataTransferWrapper(data), effects, modifiers)); - } - void IHeadlessWindow.DragDrop(Point point, RawDragEventType type, IDataTransfer data, DragDropEffects effects, RawInputModifiers modifiers) { var device = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs index 2fe11df2ca..30c2390f64 100644 --- a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs +++ b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs @@ -15,8 +15,6 @@ namespace Avalonia.Headless void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None); void MouseUp(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None); void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None); - [Obsolete($"Use the overload accepting a {nameof(IDataTransfer)} instance instead.")] - void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None); void DragDrop(Point point, RawDragEventType type, IDataTransfer data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None); } } diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index 36d04a644e..f1cdfa440a 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Threading; @@ -10,13 +9,6 @@ namespace Avalonia.Win32 { internal sealed class DragSource : IPlatformDragSource { - [Obsolete($"Use {nameof(DoDragDropAsync)} instead.")] - Task IPlatformDragSource.DoDragDrop( - PointerEventArgs triggerEvent, - IDataObject data, - DragDropEffects allowedEffects) - => DoDragDropAsync(triggerEvent, new DataObjectToDataTransferWrapper(data), allowedEffects); - public Task DoDragDropAsync( PointerEventArgs triggerEvent, IDataTransfer dataTransfer, From 424863d5ff007a21eaa6cd483e57f2046313e4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Thu, 5 Feb 2026 17:35:47 +0100 Subject: [PATCH 17/32] Add PlaceholderForeground property to TextBox, AutoCompleteBox, CalendarDatePicker, NumericUpDown (#20303) * Added WatermarkForeground property * Added samples * Added unit tests * Added render tests * Fix merge issues * Updated render tests * Standardize watermark foreground naming * Pending changes * More changes * Use UseFloatingPlaceholder * Fix tests --- samples/BindingDemo/MainWindow.xaml | 44 +++--- .../Pages/AutoCompleteBoxPage.xaml | 8 +- .../Pages/CalendarDatePickerPage.xaml | 19 ++- .../ControlCatalog/Pages/ClipboardPage.xaml | 4 +- samples/ControlCatalog/Pages/DialogsPage.xaml | 12 +- .../Pages/NumericUpDownPage.xaml | 26 ++-- samples/ControlCatalog/Pages/TextBoxPage.xaml | 43 +++--- samples/ControlCatalog/Pages/ThemePage.axaml | 6 +- .../Controls/SignUpView.xaml | 14 +- .../Pages/ScreensPage.axaml | 14 +- .../Pages/WindowDecorationsPage.axaml | 2 +- .../IntegrationTestApp/Pages/WindowPage.axaml | 2 +- samples/SafeAreaDemo/Views/MainView.xaml | 4 +- samples/SingleProjectSandbox/MainView.axaml | 8 +- .../AutoCompleteBox.Properties.cs | 75 +++++++++- .../CalendarDatePicker.Properties.cs | 95 ++++++++++-- .../CalendarDatePicker/CalendarDatePicker.cs | 24 +-- .../NumericUpDown/NumericUpDown.cs | 81 ++++++++-- src/Avalonia.Controls/TextBox.cs | 113 +++++++++++--- .../Controls/AutoCompleteBox.xaml | 13 +- .../Controls/CalendarDatePicker.xaml | 9 +- .../Controls/ComboBox.xaml | 6 +- .../Controls/ManagedFileChooser.xaml | 8 +- .../Controls/NumericUpDown.xaml | 25 ++-- .../Controls/TextBox.xaml | 39 +++-- .../Strings/InvariantResources.xaml | 2 +- .../Controls/AutoCompleteBox.xaml | 5 +- .../Controls/CalendarDatePicker.xaml | 5 +- .../Controls/ManagedFileChooser.xaml | 4 +- .../Controls/NumericUpDown.xaml | 3 +- .../Controls/TextBox.xaml | 20 +-- .../AutoCompleteBoxTests.cs | 27 +++- .../CalendarDatePickerTests.cs | 17 ++- .../NumericUpDownTests.cs | 31 ++-- .../TextBoxTests.cs | 141 ++++++++++++------ .../Views/AttachedProps.xml | 4 +- .../Views/ControlWithoutWindow.xml | 4 +- .../Views/DataTemplates.xml | 8 +- .../Views/FieldModifier.xml | 16 +- .../Views/NamedControl.xml | 4 +- .../Views/NamedControls.xml | 8 +- .../Views/NoNamedControls.xml | 4 +- .../Views/SignUpView.xml | 12 +- .../Views/xNamedControl.xml | 4 +- .../Views/xNamedControls.xml | 8 +- .../Data/BindingTests.cs | 4 +- .../Controls/TextBoxTests.cs | 135 +++++++++++++++++ ...ceholder_With_Blue_Foreground.expected.png | Bin 0 -> 1645 bytes ...older_With_Default_Foreground.expected.png | Bin 0 -> 2059 bytes ...aceholder_With_Red_Foreground.expected.png | Bin 0 -> 1587 bytes 50 files changed, 844 insertions(+), 316 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Controls/TextBoxTests.cs create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Blue_Foreground.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Default_Foreground.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Red_Foreground.expected.png diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index 9d68c8da8a..3ff80069f4 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -1,7 +1,7 @@ - + - - - - + + + + - + - + !BooleanString !!BooleanString @@ -43,24 +43,24 @@ - + - - - - + - + @@ -91,19 +91,19 @@ - + - + - - + + - + - - + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index b682ebf51d..35e917f996 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -39,8 +39,12 @@ - - + + + + + + diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 7a22c0ddab..8734758d26 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -5,7 +5,7 @@ x:Class="ControlCatalog.Pages.CalendarDatePickerPage"> A control for selecting dates with a calendar drop-down - + + PlaceholderText="Placeholder"/> - + PlaceholderText="Floating Placeholder" + UseFloatingPlaceholder="True"/> + + + + - + - + diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml b/samples/ControlCatalog/Pages/ClipboardPage.xaml index 80b3f4d1ed..864a520aca 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml @@ -1,4 +1,4 @@ - @@ -21,7 +21,7 @@ + PlaceholderText="Text to copy of file names per line" /> diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 7320c1f3d7..f32cc601ab 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -58,17 +58,17 @@ - + - + - + Desktop @@ -80,17 +80,17 @@ - + - + + PlaceholderText="Picked file content" /> diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index b01b4a93bc..a112f79c02 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -1,4 +1,4 @@ - - Watermark: - + PlaceholderText: + Text: @@ -81,23 +81,23 @@ + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/> + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/> - + + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"> - + @@ -110,10 +110,18 @@ - + + + + - - + + + + - + - + - + + SelectionStart="5" SelectionEnd="22" + SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/> @@ -54,11 +61,11 @@ - + + FontFamily="Comic Sans MS" + FontSize="10" + Foreground="Red"/> - diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml b/samples/ControlCatalog/Pages/ThemePage.axaml index 2d948c44a0..7eb95471a0 100644 --- a/samples/ControlCatalog/Pages/ThemePage.axaml +++ b/samples/ControlCatalog/Pages/ThemePage.axaml @@ -1,4 +1,4 @@ - - - + + @@ -184,7 +184,7 @@ IsVisible="{Binding ShowFilters}" ItemsSource="{Binding Filters}" SelectedItem="{Binding SelectedFilter}" /> - + @@ -344,7 +344,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index 22b6ef045c..b6c19484e9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -1,21 +1,21 @@ - + - + Maximum="10" + Increment="0.5" + VerticalContentAlignment="Center" + HorizontalContentAlignment="Center" + ButtonSpinnerLocation="Left" + PlaceholderText="Enter text" /> Clear Reveal Password Password Revealed - - - Content + + + Content @@ -20,7 +20,7 @@ M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z - + @@ -99,6 +99,7 @@ + @@ -132,11 +133,12 @@ Grid.Column="1" Grid.ColumnSpan="1" Margin="{TemplateBinding Padding}"> - + - + - @@ -217,19 +218,12 @@ - - - - - + - @@ -203,7 +205,7 @@ diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index b4e0a055e3..26ece37acf 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -406,7 +406,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(control.Text, control.ItemSelector(input, selectedItem)); }); } - + [Fact] public void Text_Validation() { @@ -421,7 +421,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal([exception], DataValidationErrors.GetErrors(control)); }); } - + [Fact] public void Text_Validation_TextBox_Errors_Binding() { @@ -430,20 +430,20 @@ namespace Avalonia.Controls.UnitTests // simulate the TemplateBinding that would be used within the AutoCompleteBox control theme for the inner PART_TextBox // DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" textbox.Bind(DataValidationErrors.ErrorsProperty, control.GetBindingObservable(DataValidationErrors.ErrorsProperty)); - + var exception = new InvalidCastException("failed validation"); var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); control.Bind(AutoCompleteBox.TextProperty, textObservable); Dispatcher.UIThread.RunJobs(); - + Assert.True(DataValidationErrors.GetHasErrors(control)); Assert.Equal([exception], DataValidationErrors.GetErrors(control)); - + Assert.True(DataValidationErrors.GetHasErrors(textbox)); Assert.Equal([exception], DataValidationErrors.GetErrors(textbox)); }); } - + [Fact] public void SelectedItem_Validation() { @@ -583,7 +583,7 @@ namespace Avalonia.Controls.UnitTests } /// - /// Retrieves a defined predicate filter through a new AutoCompleteBox + /// Retrieves a defined predicate filter through a new AutoCompleteBox /// control instance. /// /// The FilterMode of interest. @@ -1283,5 +1283,18 @@ namespace Avalonia.Controls.UnitTests IsOpen = true; } } + + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var control = CreateControl(); + control.PlaceholderText = "Search..."; + control.PlaceholderForeground = Media.Brushes.Green; + + Assert.Equal(Media.Brushes.Green, control.PlaceholderForeground); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index 9124c317b8..d97a9729fb 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests { return new FuncControlTemplate((control, scope) => { - var textBox = + var textBox = new TextBox { Name = "PART_TextBox" @@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests var calendar = new Calendar { - Name = "PART_Calendar", + Name = "PART_Calendar", [!Calendar.SelectedDateProperty] = control[!CalendarDatePicker.SelectedDateProperty], [!Calendar.DisplayDateProperty] = control[!CalendarDatePicker.DisplayDateProperty], [!Calendar.DisplayDateStartProperty] = control[!CalendarDatePicker.DisplayDateStartProperty], @@ -179,5 +179,18 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var control = CreateControl(); + control.PlaceholderText = "Select date"; + control.PlaceholderForeground = Media.Brushes.Purple; + + Assert.Equal(Media.Brushes.Purple, control.PlaceholderForeground); + } + } + } } diff --git a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs index ab8766e216..868b33d7bd 100644 --- a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -58,7 +58,7 @@ namespace Avalonia.Controls.UnitTests var spinner = GetSpinner(control); spinner.RaiseEvent(new SpinEventArgs(Spinner.SpinEvent, direction)); - + Assert.Equal(control.Value, expected); } @@ -105,16 +105,16 @@ namespace Avalonia.Controls.UnitTests // if min and max are not defined and value was null, 0 should be ne new value after spin yield return [decimal.MinValue, decimal.MaxValue, null, SpinDirection.Decrease, 0m]; yield return [decimal.MinValue, decimal.MaxValue, null, SpinDirection.Increase, 0m]; - + // if no value was defined, but Min or Max are defined, use these as the new value yield return [-400m, -200m, null, SpinDirection.Decrease, -200m]; yield return [200m, 400m, null, SpinDirection.Increase, 200m]; - + // Value should be clamped to Min / Max after spinning yield return [200m, 400m, 5m, SpinDirection.Increase, 200m]; yield return [200m, 400m, 200m, SpinDirection.Decrease, 200m]; } - + private void RunTest(Action test) { using (UnitTestApplication.Start(Services)) @@ -148,14 +148,14 @@ namespace Avalonia.Controls.UnitTests .OfType() .First(); } - + private static ButtonSpinner GetSpinner(NumericUpDown control) { return control.GetTemplateChildren() .OfType() .First(); } - + private static IControlTemplate CreateTemplate() { return new FuncControlTemplate((control, scope) => @@ -180,14 +180,27 @@ namespace Avalonia.Controls.UnitTests { // Set TabIndex on NumericUpDown control.TabIndex = 5; - + // The inner TextBox should inherit the same TabIndex Assert.Equal(5, textbox.TabIndex); - + // Change TabIndex and verify it gets synchronized control.TabIndex = 10; Assert.Equal(10, textbox.TabIndex); }); } + + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var control = CreateControl(); + control.PlaceholderText = "Enter value"; + control.PlaceholderForeground = Media.Brushes.Red; + + Assert.Equal(Media.Brushes.Red, control.PlaceholderForeground); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 3e830859a4..31b8fe45e8 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -42,25 +42,25 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "5678" }; - + var sp = new StackPanel(); sp.Children.Add(target1); sp.Children.Add(target2); target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot() { Child = sp }; target1.SelectionStart = 0; target1.SelectionEnd = 3; - + target1.Focus(); Assert.False(target2.IsFocused); Assert.True(target1.IsFocused); target2.Focus(); - + Assert.Equal("123", target1.SelectedText); } } @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests } } }; - + target1.ApplyTemplate(); @@ -165,16 +165,16 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + target.Measure(Size.Infinity); - + target.CaretIndex = 3; RaiseKeyEvent(target, Key.Right, 0); Assert.Equal(4, target.CaretIndex); } } - + [Fact] public void Control_Backspace_Should_Set_Caret_Position_To_The_Start_Of_The_Deletion() { @@ -194,11 +194,11 @@ namespace Avalonia.Controls.UnitTests // (First Second |Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First |Third) - + Assert.Equal(6, target.CaretIndex); } } - + [Fact] public void Control_Backspace_Should_Remove_The_Double_Whitespace_If_Caret_Index_Was_At_The_End_Of_A_Word() { @@ -213,7 +213,7 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + // (First Second| Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First| Third) @@ -236,11 +236,11 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + // (First Second| Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First| Third) - + target.Undo(); // (First Second| Third) @@ -258,7 +258,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "1234" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.A, KeyModifiers.Control); @@ -314,7 +314,7 @@ namespace Avalonia.Controls.UnitTests SelectionStart = 5, SelectionEnd = 5 }; - + textBox.ApplyTemplate(); // (First| Second Third Fourth) @@ -356,7 +356,7 @@ namespace Avalonia.Controls.UnitTests Text = "First Second Third Fourth", CaretIndex = 19, }; - + textBox.ApplyTemplate(); // (First Second Third |Fourth) @@ -400,7 +400,7 @@ namespace Avalonia.Controls.UnitTests textBox.SelectionStart = 2; textBox.SelectionEnd = 2; - + Assert.Equal(2, textBox.CaretIndex); } } @@ -443,7 +443,7 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = false, Text = "1234" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -462,7 +462,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), AcceptsReturn = true }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -482,7 +482,7 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = true, NewLine = "Test" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -523,7 +523,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); target.SelectionStart = 0; @@ -547,7 +547,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); target.SelectionStart = 8; @@ -592,7 +592,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); Assert.True(target.SelectedText == ""); @@ -614,7 +614,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123" }; - + target.ApplyTemplate(); target.SelectedText = "AA"; @@ -679,7 +679,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(true); } } - + [Theory] [InlineData(Key.Up)] [InlineData(Key.Down)] @@ -725,7 +725,7 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; var gfcount = 0; @@ -746,7 +746,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, lfcount); } } - + [Fact] public void TextBox_CaretIndex_Persists_When_Focus_Lost() { @@ -768,7 +768,7 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; target2.Focus(); @@ -777,11 +777,11 @@ namespace Avalonia.Controls.UnitTests Assert.True(target2.IsFocused); target1.Focus(); - + Assert.Equal(2, target2.CaretIndex); } } - + [Fact] public void TextBox_Reveal_Password_Reset_When_Lost_Focus() { @@ -804,14 +804,14 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; target1.Focus(); target1.RevealPassword = true; - + target2.Focus(); - + Assert.False(target1.RevealPassword); } } @@ -833,7 +833,7 @@ namespace Avalonia.Controls.UnitTests Assert.Null(target.Text); } } - + [Theory] [InlineData("abc", "d", 3, 0, 0, false, "abc")] [InlineData("abc", "dd", 4, 3, 3, false, "abcd")] @@ -870,7 +870,7 @@ namespace Avalonia.Controls.UnitTests topLevel.LayoutManager.ExecuteInitialLayoutPass(); target.Measure(Size.Infinity); - + if (fromClipboard) { await topLevel.Clipboard!.SetTextAsync(textInput); @@ -882,7 +882,7 @@ namespace Avalonia.Controls.UnitTests { RaiseTextEvent(target, textInput); } - + Assert.Equal(expected, target.Text); } } @@ -1246,7 +1246,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal((minLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MinHeight); } } - + [Theory] [InlineData(null, 1)] [InlineData("", 1)] @@ -1285,7 +1285,7 @@ namespace Avalonia.Controls.UnitTests var b = new TextBox(); Assert.Equal(-1, b.GetLineCount()); } - + [Fact] public void LineCount_Is_Correct_After_Text_Change() { @@ -1309,7 +1309,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Measure(Size.Infinity); - + Assert.Equal(1, target.GetLineCount()); target.Text = "Hello\r\nWorld"; @@ -1505,7 +1505,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("ABCDEF123", tb.Text); // Undo will take us back one step - tb.Undo(); + tb.Undo(); Assert.Equal("ABCDEF", tb.Text); // Undo again @@ -2050,7 +2050,7 @@ namespace Avalonia.Controls.UnitTests Assert.NotNull(client); Assert.Equal(string.Empty, client.SurroundingText); } - + [Fact] public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Same_Line() { @@ -2122,7 +2122,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(target1.IsFocused); - Assert.Equal("1234", target1.SelectedText); + Assert.Equal("1234", target1.SelectedText); target2.Focus(); @@ -2149,6 +2149,61 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("FirstSecond", target.Text); } + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text", + PlaceholderForeground = Brushes.Red + }; + + target.ApplyTemplate(); + + Assert.Equal(Brushes.Red, target.PlaceholderForeground); + } + } + + [Fact] + public void PlaceholderForeground_Defaults_To_Null() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text" + }; + + target.ApplyTemplate(); + + Assert.Null(target.PlaceholderForeground); + } + } + + [Fact] + public void PlaceholderForeground_Can_Be_Set_To_Null() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text", + PlaceholderForeground = Brushes.Blue + }; + + target.ApplyTemplate(); + + target.PlaceholderForeground = null; + + Assert.Null(target.PlaceholderForeground); + } + } + private static TestServices FocusServices => TestServices.MockThreadingInterface.With( keyboardDevice: () => new KeyboardDevice(), keyboardNavigation: () => new KeyboardNavigationHandler(), @@ -2160,7 +2215,7 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of(), renderInterface: new HeadlessPlatformRenderInterface(), - textShaperImpl: new HarfBuzzTextShaper(), + textShaperImpl: new HarfBuzzTextShaper(), fontManagerImpl: new TestFontManager(), assetLoader: new StandardAssetLoader()); diff --git a/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml b/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml index 209b7ca9f1..ccd199040e 100644 --- a/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml +++ b/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml @@ -3,6 +3,6 @@ x:Class="Sample.App.AttachedProps" Design.Width="300"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml b/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml index 485fe93e4c..54e7f156e1 100644 --- a/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml +++ b/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml @@ -3,6 +3,6 @@ x:Class="Sample.App.ControlWithoutWindow" Design.Width="300"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml b/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml index f7e15644aa..c4d587baf2 100644 --- a/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml +++ b/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml @@ -3,14 +3,14 @@ x:Class="Sample.App.DataTemplates"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Templated input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml b/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml index 3ee5e51466..65fafb414e 100644 --- a/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml +++ b/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml @@ -4,20 +4,20 @@ + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Password input" + UseFloatingPlaceholder="True" /> + Placeholder="Password input" + UseFloatingPlaceholder="True" />