From 934ca7c9f5ab2b8f5ce32604e1912c1cfa36d5a8 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 4 Feb 2026 10:50:15 +0000 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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" />