diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 32ab43f84f..3858eaa6ff 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -19,6 +19,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IReadableBitmapWithAlphaImpl + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Utilities.StringTokenizer + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable @@ -49,6 +61,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IReadableBitmapWithAlphaImpl + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Utilities.StringTokenizer + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Controls.Primitives.IScrollable @@ -73,6 +97,24 @@ 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) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.DrawingImage.get_Viewbox @@ -97,6 +139,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat) + 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) @@ -121,6 +169,48 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}}) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}}) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) @@ -157,6 +247,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) + 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) @@ -181,6 +277,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -223,6 +325,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) @@ -259,6 +367,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) @@ -343,6 +463,24 @@ 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) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.DrawingImage.get_Viewbox @@ -367,6 +505,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat) + 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) @@ -391,6 +535,48 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}}) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}}) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) @@ -427,6 +613,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) + 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) @@ -451,6 +643,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.TextBlock.LetterSpacingProperty @@ -493,6 +691,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) @@ -529,6 +733,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) @@ -619,6 +835,12 @@ baseline/netstandard2.0/Avalonia.Base.dll target/netstandard2.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType}) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions @@ -667,6 +889,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0006 + P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0006 + P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0006 M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) @@ -727,6 +961,12 @@ baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll + + CP0006 + M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType}) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0006 M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) @@ -811,6 +1051,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0006 + P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0006 + P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0006 M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) @@ -895,12 +1147,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IWriteableBitmapImpl + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.StreamGeometryContext baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IWriteableBitmapImpl + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 8fe77a095d..d3750935f9 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -134,6 +134,8 @@ false false false + true + false @@ -162,6 +164,7 @@ DelaySign="$(DelaySign)" SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)" DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)" + CreateSourceInfo="$(AvaloniaXamlCreateSourceInfo)" DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)" VerboseExceptions="$(AvaloniaXamlVerboseExceptions)" AnalyzerConfigFiles="@(EditorConfigFiles)"/> diff --git a/packages/Avalonia/AvaloniaRules.Project.xml b/packages/Avalonia/AvaloniaRules.Project.xml index b69ea6de17..0a5c1b8243 100644 --- a/packages/Avalonia/AvaloniaRules.Project.xml +++ b/packages/Avalonia/AvaloniaRules.Project.xml @@ -31,6 +31,11 @@ Description="Allow debug XAML compilation" Category="Debug" /> + + The current element. /// The direction to move. /// Any key modifiers active at the time of focus. - void Move( + /// The device type used to move the focus. + bool Move( IInputElement element, NavigationDirection direction, - KeyModifiers keyModifiers = KeyModifiers.None); + KeyModifiers keyModifiers = KeyModifiers.None, + KeyDeviceType? deviceType = null); } } diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index 3444a88aba..e5e7eb0699 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -98,22 +98,12 @@ namespace Avalonia.Input return result; } - /// - /// Moves the focus in the specified direction. - /// - /// The current element. - /// The direction to move. - /// Any key modifiers active at the time of focus. - public void Move( + /// + public bool Move( IInputElement? element, NavigationDirection direction, - KeyModifiers keyModifiers = KeyModifiers.None) - { - MovePrivate(element, direction, keyModifiers, null); - } - - // TODO12: remove MovePrivate, and make Move return boolean. Or even remove whole KeyboardNavigationHandler. - private bool MovePrivate(IInputElement? element, NavigationDirection direction, KeyModifiers keyModifiers, KeyDeviceType? deviceType) + KeyModifiers keyModifiers = KeyModifiers.None, + KeyDeviceType? deviceType = null) { var next = GetNextPrivate(element, _owner, direction, deviceType); @@ -140,7 +130,7 @@ namespace Avalonia.Input var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement(); var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ? NavigationDirection.Next : NavigationDirection.Previous; - e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType); + e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType); } else if (e.Key is Key.Left or Key.Right or Key.Up or Key.Down) { @@ -153,7 +143,7 @@ namespace Avalonia.Input Key.Down => NavigationDirection.Down, _ => throw new ArgumentOutOfRangeException() }; - e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType); + e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType); } } diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs index 7f9870315b..36e14cd3fd 100644 --- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs @@ -82,13 +82,6 @@ namespace Avalonia.Input.TextInput { SetPreeditText(preeditText); } - - //TODO12: remove - [Obsolete] - public virtual void ShowInputPanel() - { - RaiseInputPaneActivationRequested(); - } protected virtual void RaiseTextViewVisualChanged() { diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index c285ce2f51..5000acb2c5 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -184,13 +184,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( runProps.Typeface, - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, foregroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); #pragma warning restore 6506 @@ -240,13 +240,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( runProps.Typeface, - fontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + fontFeatures ); #pragma warning restore 6506 @@ -328,14 +328,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight), - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo - ); + runProps.CultureInfo, + runProps.FontFeatures); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, @@ -388,13 +387,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( runProps.Typeface, - runProps.FontFeatures, emSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, @@ -451,13 +450,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( runProps.Typeface, - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - culture + culture, + runProps.FontFeatures ); #pragma warning restore 6506 @@ -511,13 +510,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight), - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); @@ -568,13 +567,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight), - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); #pragma warning restore 6506 @@ -625,13 +624,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( typeface, - runProps.FontFeatures, runProps.FontRenderingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); #pragma warning restore 6506 @@ -683,13 +682,13 @@ namespace Avalonia.Media var newProps = new GenericTextRunProperties( runProps.Typeface, - runProps.FontFeatures, runProps.FontRenderingEmSize, textDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, - runProps.CultureInfo + runProps.CultureInfo, + runProps.FontFeatures ); #pragma warning restore 6506 diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index 2a1ce15feb..9c0c20d170 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using Avalonia.Platform; @@ -177,7 +176,7 @@ namespace Avalonia.Media.Imaging public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format; - public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapWithAlphaImpl)?.AlphaFormat; + public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapImpl)?.AlphaFormat; private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride, ILockedFramebuffer fb) @@ -237,16 +236,15 @@ namespace Avalonia.Media.Imaging /// Copies pixels to the target buffer and transcodes the pixel and alpha format if needed. /// /// The target buffer. - /// The alpha format. /// - public void CopyPixels(ILockedFramebuffer buffer, AlphaFormat alphaFormat) + public void CopyPixels(ILockedFramebuffer buffer) { - if (PlatformImpl.Item is not IReadableBitmapWithAlphaImpl readable || readable.Format == null || readable.AlphaFormat == null) + if (PlatformImpl.Item is not IReadableBitmapImpl readable || readable.Format == null || readable.AlphaFormat == null) { throw new NotSupportedException("CopyPixels is not supported for this bitmap type"); } - if (buffer.Format != readable.Format || alphaFormat != readable.AlphaFormat) + if (buffer.Format != readable.Format || buffer.AlphaFormat != readable.AlphaFormat) { using (var fb = readable.Lock()) { @@ -255,11 +253,11 @@ namespace Avalonia.Media.Imaging fb.Size, fb.RowBytes, fb.Format, - readable.AlphaFormat.Value, + fb.AlphaFormat, buffer.Address, buffer.RowBytes, buffer.Format, - alphaFormat); + buffer.AlphaFormat); } } else diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs index 435e51009d..f2b3b4d0c7 100644 --- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs @@ -71,10 +71,10 @@ namespace Avalonia.Media.Imaging return new LockedFramebuffer(_pixelFormatMemory.Address, _pixelFormatMemory.Size, _pixelFormatMemory.RowBytes, - Dpi, _pixelFormatMemory.Format, () => + Dpi, _pixelFormatMemory.Format, _pixelFormatMemory.AlphaFormat, () => { using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock(); - _pixelFormatMemory.CopyToRgba(Platform.AlphaFormat.Unpremul, inner.Address, inner.RowBytes); + _pixelFormatMemory.CopyToRgba(inner.AlphaFormat, inner.Address, inner.RowBytes); }); } diff --git a/src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs b/src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs index 0a8ab72a25..a747854e3b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; namespace Avalonia.Media.TextFormatting { @@ -9,28 +7,17 @@ namespace Avalonia.Media.TextFormatting /// public class GenericTextRunProperties : TextRunProperties { - private const double DefaultFontRenderingEmSize = 12; + internal const double DefaultFontRenderingEmSize = 12; - // TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional - public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = DefaultFontRenderingEmSize, - TextDecorationCollection? textDecorations = null, IBrush? foregroundBrush = null, - IBrush? backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline, - CultureInfo? cultureInfo = null) : - this(typeface, null, fontRenderingEmSize, textDecorations, foregroundBrush, - backgroundBrush, baselineAlignment, cultureInfo) - { - } - - // TODO12:Change signature in 12.0.0 public GenericTextRunProperties( - Typeface typeface, - FontFeatureCollection? fontFeatures, + Typeface typeface, double fontRenderingEmSize = DefaultFontRenderingEmSize, TextDecorationCollection? textDecorations = null, IBrush? foregroundBrush = null, IBrush? backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline, - CultureInfo? cultureInfo = null) + CultureInfo? cultureInfo = null, + FontFeatureCollection? fontFeatures = null) { Typeface = typeface; FontRenderingEmSize = fontRenderingEmSize; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs index dcee8cea46..b621dada54 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs @@ -26,16 +26,14 @@ /// Text line to collapse. public abstract TextRun[]? Collapse(TextLine textLine); - // TODO12: Remove the flowDirection parameter /// /// Creates a list of runs for given collapsed length which includes specified symbol at the end. /// /// The text line. /// The collapsed length. - /// The flow direction. /// The symbol. /// List of remaining runs. - public static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, FlowDirection flowDirection, TextRun shapedSymbol) + public static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, TextRun shapedSymbol) { if (collapsedLength <= 0) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index 426388f295..685bb08f27 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -71,7 +71,7 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, properties.FlowDirection, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, shapedSymbol); } availableWidth -= textRunWidth; @@ -84,7 +84,7 @@ namespace Avalonia.Media.TextFormatting //The whole run needs to fit into available space if (drawableRun.Size.Width > availableWidth) { - return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, properties.FlowDirection, shapedSymbol); + return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, shapedSymbol); } availableWidth -= drawableRun.Size.Width; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs index 1dbf55fb97..ed60dd44a0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs @@ -59,8 +59,8 @@ var cultureInfo = textRun.Properties.CultureInfo; - var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures, - fontRenderingEmSize, (sbyte)flowDirection, cultureInfo); + var shaperOptions = new TextShaperOptions(glyphTypeface, fontRenderingEmSize, + (sbyte)flowDirection, cultureInfo, 0, 0, textRun.Properties.FontFeatures); var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 605804a82d..4428dff0f1 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -290,9 +290,13 @@ namespace Avalonia.Media.TextFormatting } var shaperOptions = new TextShaperOptions( - properties.CachedGlyphTypeface, properties.FontFeatures, - properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo, - paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); + properties.CachedGlyphTypeface, + properties.FontRenderingEmSize, + shapeableRun.BidiLevel, + properties.CultureInfo, + paragraphProperties.DefaultIncrementalTab, + paragraphProperties.LetterSpacing, + properties.FontFeatures); ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 3a578fb72d..24e9019084 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -17,7 +17,6 @@ namespace Avalonia.Media.TextFormatting private int _textSourceLength; - // TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional /// /// Initializes a new instance of the class. /// @@ -35,54 +34,13 @@ namespace Avalonia.Media.TextFormatting /// The height of each line of text. /// The letter spacing that is applied to rendered glyphs. /// The maximum number of text lines. - /// The text style overrides. - public TextLayout( - string? text, - Typeface typeface, - double fontSize, - IBrush? foreground, - TextAlignment textAlignment = TextAlignment.Left, - TextWrapping textWrapping = TextWrapping.NoWrap, - TextTrimming? textTrimming = null, - TextDecorationCollection? textDecorations = null, - FlowDirection flowDirection = FlowDirection.LeftToRight, - double maxWidth = double.PositiveInfinity, - double maxHeight = double.PositiveInfinity, - double lineHeight = double.NaN, - double letterSpacing = 0, - int maxLines = 0, - IReadOnlyList>? textStyleOverrides = null) - : this(text, typeface, null, fontSize, foreground, textAlignment, textWrapping, textTrimming, textDecorations, - flowDirection, maxWidth, maxHeight, lineHeight, letterSpacing, maxLines, textStyleOverrides) - { - } - - // TODO12:Change signature in 12.0.0 - /// - /// Initializes a new instance of the class. - /// - /// The text. - /// The typeface. - /// Size of the font. - /// The foreground. - /// The text alignment. - /// The text wrapping. - /// The text trimming. - /// The text decorations. - /// The text flow direction. - /// The maximum width. - /// The maximum height. - /// The height of each line of text. - /// The letter spacing that is applied to rendered glyphs. - /// The maximum number of text lines. - /// The text style overrides. /// Optional list of turned on/off features. + /// The text style overrides. public TextLayout( string? text, Typeface typeface, - FontFeatureCollection? fontFeatures, - double fontSize, - IBrush? foreground, + double fontSize = GenericTextRunProperties.DefaultFontRenderingEmSize, + IBrush? foreground = null, TextAlignment textAlignment = TextAlignment.Left, TextWrapping textWrapping = TextWrapping.NoWrap, TextTrimming? textTrimming = null, @@ -93,6 +51,7 @@ namespace Avalonia.Media.TextFormatting double lineHeight = double.NaN, double letterSpacing = 0, int maxLines = 0, + FontFeatureCollection? fontFeatures = null, IReadOnlyList>? textStyleOverrides = null) { _paragraphProperties = @@ -534,7 +493,7 @@ namespace Avalonia.Media.TextFormatting TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, double letterSpacing, FontFeatureCollection? features) { - var textRunStyle = new GenericTextRunProperties(typeface, features, fontSize, textDecorations, foreground); + var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground, fontFeatures: features); return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false, textRunStyle, textWrapping, lineHeight, 0, letterSpacing); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs index cf2f74ee4d..ac4cf4855d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs @@ -107,8 +107,15 @@ namespace Avalonia.Media.TextFormatting if (this is GenericTextRunProperties other && other.Typeface == typeface) return this; - return new GenericTextRunProperties(typeface, FontFeatures, FontRenderingEmSize, - TextDecorations, ForegroundBrush, BackgroundBrush, BaselineAlignment); + return new GenericTextRunProperties( + typeface, + FontRenderingEmSize, + TextDecorations, + ForegroundBrush, + BackgroundBrush, + BaselineAlignment, + CultureInfo, + FontFeatures); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 9ae20a3eaa..f19ce2ee5d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -8,27 +8,14 @@ namespace Avalonia.Media.TextFormatting /// public readonly record struct TextShaperOptions { - // TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional - public TextShaperOptions( - GlyphTypeface typeface, - double fontRenderingEmSize = 12, - sbyte bidiLevel = 0, - CultureInfo? culture = null, - double incrementalTabWidth = 0, - double letterSpacing = 0) - : this(typeface, null, fontRenderingEmSize, bidiLevel, culture, incrementalTabWidth, letterSpacing) - { - } - - // TODO12:Change signature in 12.0.0 public TextShaperOptions( GlyphTypeface typeface, - IReadOnlyList? fontFeatures, - double fontRenderingEmSize = 12, + double fontRenderingEmSize = GenericTextRunProperties.DefaultFontRenderingEmSize, sbyte bidiLevel = 0, CultureInfo? culture = null, double incrementalTabWidth = 0, - double letterSpacing = 0) + double letterSpacing = 0, + IReadOnlyList? fontFeatures = null) { GlyphTypeface = typeface; FontRenderingEmSize = fontRenderingEmSize; diff --git a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs index f963b77cd9..a73b339fcc 100644 --- a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs @@ -29,6 +29,9 @@ namespace Avalonia.Platform /// PixelFormat Format { get; } - //TODO12: Add AlphaFormat + /// + /// Gets the alpha format. + /// + AlphaFormat AlphaFormat { get; } } } diff --git a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs index d5a0c765cc..6332d302af 100644 --- a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs @@ -2,15 +2,10 @@ using Avalonia.Metadata; namespace Avalonia.Platform; -public interface IReadableBitmapImpl +[PrivateApi] +public interface IReadableBitmapImpl : IBitmapImpl { PixelFormat? Format { get; } - ILockedFramebuffer Lock(); -} - -//TODO12: Remove me once we can change IReadableBitmapImpl -[Unstable] -public interface IReadableBitmapWithAlphaImpl : IReadableBitmapImpl -{ AlphaFormat? AlphaFormat { get; } + ILockedFramebuffer Lock(); } diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs index 685491a326..185b116c9a 100644 --- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs @@ -5,8 +5,8 @@ namespace Avalonia.Platform /// /// Defines the platform-specific interface for a . /// - [Unstable] - public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapWithAlphaImpl + [PrivateApi] + public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl { } } diff --git a/src/Avalonia.Base/Platform/LockedFramebuffer.cs b/src/Avalonia.Base/Platform/LockedFramebuffer.cs index b9094d9e14..8924947662 100644 --- a/src/Avalonia.Base/Platform/LockedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/LockedFramebuffer.cs @@ -7,7 +7,7 @@ namespace Avalonia.Platform private readonly Action? _onDispose; public LockedFramebuffer(IntPtr address, PixelSize size, int rowBytes, Vector dpi, PixelFormat format, - Action? onDispose) + AlphaFormat alphaFormat, Action? onDispose) { _onDispose = onDispose; Address = address; @@ -15,6 +15,7 @@ namespace Avalonia.Platform RowBytes = rowBytes; Dpi = dpi; Format = format; + AlphaFormat = alphaFormat; } public IntPtr Address { get; } @@ -22,6 +23,7 @@ namespace Avalonia.Platform public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } public void Dispose() { diff --git a/src/Avalonia.Base/Platform/RetainedFramebuffer.cs b/src/Avalonia.Base/Platform/RetainedFramebuffer.cs index e5ca1070dc..e1983afde1 100644 --- a/src/Avalonia.Base/Platform/RetainedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/RetainedFramebuffer.cs @@ -1,6 +1,4 @@ using System; -using System.Runtime.InteropServices; -using Avalonia.Metadata; using Avalonia.Platform.Internal; namespace Avalonia.Platform; @@ -10,6 +8,7 @@ internal class RetainedFramebuffer : IDisposable public PixelSize Size { get; } public int RowBytes { get; } public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } public IntPtr Address => _blob?.Address ?? throw new ObjectDisposedException(nameof(RetainedFramebuffer)); private UnmanagedBlob? _blob; @@ -17,13 +16,13 @@ internal class RetainedFramebuffer : IDisposable ? format : throw new ArgumentOutOfRangeException(nameof(format)); - public RetainedFramebuffer(PixelSize size, PixelFormat format) : this(size, ValidateKnownFormat(format), - format.BitsPerPixel / 8 * size.Width) + public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat) + : this(size, ValidateKnownFormat(format), alphaFormat, format.BitsPerPixel / 8 * size.Width) { } - public RetainedFramebuffer(PixelSize size, PixelFormat format, int rowBytes) + public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat, int rowBytes) { if (size.Width <= 0 || size.Height <= 0) throw new ArgumentOutOfRangeException(nameof(size)); @@ -32,6 +31,7 @@ internal class RetainedFramebuffer : IDisposable Size = size; RowBytes = rowBytes; Format = format; + AlphaFormat = alphaFormat; _blob = new UnmanagedBlob(RowBytes * size.Height); } @@ -39,7 +39,7 @@ internal class RetainedFramebuffer : IDisposable { if (_blob == null) throw new ObjectDisposedException(nameof(RetainedFramebuffer)); - return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, () => + return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, AlphaFormat, () => { blit(this); GC.KeepAlive(this); @@ -51,4 +51,4 @@ internal class RetainedFramebuffer : IDisposable _blob?.Dispose(); _blob = null; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs deleted file mode 100644 index e83ef8c479..0000000000 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using static System.Char; - -namespace Avalonia.Utilities -{ - // TODO12: Remove this struct in 12.0 (breaking change) - - [Obsolete("This type has been superseded by SpanStringTokenizer.")] -#if !BUILDTASK - public -#endif - record struct StringTokenizer : IDisposable - { - private const char DefaultSeparatorChar = ','; - - private readonly string _s; - private readonly int _length; - private readonly char _separator; - private readonly string? _exceptionMessage; - private readonly IFormatProvider _formatProvider; - private int _index; - private int _tokenIndex; - private int _tokenLength; - - public StringTokenizer(string s, IFormatProvider formatProvider, string? exceptionMessage = null) - : this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage) - { - _formatProvider = formatProvider; - } - - public StringTokenizer(string s, char separator = DefaultSeparatorChar, string? exceptionMessage = null) - { - _s = s ?? throw new ArgumentNullException(nameof(s)); - _length = s?.Length ?? 0; - _separator = separator; - _exceptionMessage = exceptionMessage; - _formatProvider = CultureInfo.InvariantCulture; - _index = 0; - _tokenIndex = -1; - _tokenLength = 0; - - while (_index < _length && IsWhiteSpace(_s, _index)) - { - _index++; - } - } - - public string? CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength); - - public ReadOnlySpan CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan.Empty : _s.AsSpan().Slice(_tokenIndex, _tokenLength); - - public void Dispose() - { - if (_index != _length) - { - throw GetFormatException(); - } - } - - public bool TryReadInt32(out Int32 result, char? separator = null) - { - if (TryReadSpan(out var stringResult, separator) && - SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result)) - { - return true; - } - else - { - result = default; - return false; - } - } - - public int ReadInt32(char? separator = null) - { - if (!TryReadInt32(out var result, separator)) - { - throw GetFormatException(); - } - - return result; - } - - public bool TryReadDouble(out double result, char? separator = null) - { - if (TryReadSpan(out var stringResult, separator) && - SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result)) - { - return true; - } - else - { - result = default; - return false; - } - } - - public double ReadDouble(char? separator = null) - { - if (!TryReadDouble(out var result, separator)) - { - throw GetFormatException(); - } - - return result; - } - - public bool TryReadString([NotNull] out string result, char? separator = null) - { - var success = TryReadToken(separator ?? _separator); - result = CurrentTokenSpan.ToString(); - return success; - } - - public string ReadString(char? separator = null) - { - if (!TryReadString(out var result, separator)) - { - throw GetFormatException(); - } - - return result; - } - - public bool TryReadSpan(out ReadOnlySpan result, char? separator = null) - { - var success = TryReadToken(separator ?? _separator); - result = CurrentTokenSpan; - return success; - } - - public ReadOnlySpan ReadSpan(char? separator = null) - { - if (!TryReadSpan(out var result, separator)) - { - throw GetFormatException(); - } - - return result; - } - - private bool TryReadToken(char separator) - { - _tokenIndex = -1; - - if (_index >= _length) - { - return false; - } - - var c = _s[_index]; - - var index = _index; - var length = 0; - - while (_index < _length) - { - c = _s[_index]; - - if (IsWhiteSpace(c) || c == separator) - { - break; - } - - _index++; - length++; - } - - SkipToNextToken(separator); - - _tokenIndex = index; - _tokenLength = length; - - if (_tokenLength < 1) - { - throw GetFormatException(); - } - - return true; - } - - private void SkipToNextToken(char separator) - { - if (_index < _length) - { - var c = _s[_index]; - - if (c != separator && !IsWhiteSpace(c)) - { - throw GetFormatException(); - } - - var length = 0; - - while (_index < _length) - { - c = _s[_index]; - - if (c == separator) - { - length++; - _index++; - - if (length > 1) - { - throw GetFormatException(); - } - } - else - { - if (!IsWhiteSpace(c)) - { - break; - } - - _index++; - } - } - - if (length > 0 && _index >= _length) - { - throw GetFormatException(); - } - } - } - - private FormatException GetFormatException() => - _exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException(); - - private static char GetSeparatorFromFormatProvider(IFormatProvider provider) - { - var c = DefaultSeparatorChar; - - var formatInfo = NumberFormatInfo.GetInstance(provider); - if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0]) - { - c = ';'; - } - - return c; - } - } -} diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 313e66b207..bc6380a012 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -62,9 +62,6 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - - Markup/%(RecursiveDir)%(FileName)%(Extension) - Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index 138f6d3b90..305000a0b1 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -32,7 +32,7 @@ namespace Avalonia.Build.Tasks ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance, new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles), (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, - SkipXamlCompilation, DebuggerLaunch, VerboseExceptions); + SkipXamlCompilation, DebuggerLaunch, VerboseExceptions, CreateSourceInfo); if (res.Success && !res.WrittenFile) { @@ -99,6 +99,8 @@ namespace Avalonia.Build.Tasks public bool DebuggerLaunch { get; set; } + public bool CreateSourceInfo { get; set; } + public bool VerboseExceptions { get; set; } public ITaskItem[] AnalyzerConfigFiles { get; set; } diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 50b0fea6b3..db4b6a7f6f 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -52,7 +52,7 @@ namespace Avalonia.Build.Tasks string[] references, string projectDirectory, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey, - bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions) + bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions, bool createSourceInfo) { try { @@ -67,7 +67,7 @@ namespace Avalonia.Build.Tasks var compileRes = CompileCore( engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, diagnosticsFilter, - debuggerLaunch, verboseExceptions); + debuggerLaunch, verboseExceptions, createSourceInfo); if (compileRes == null) return new CompileResult(true); if (compileRes == false) @@ -107,7 +107,8 @@ namespace Avalonia.Build.Tasks MessageImportance logImportance, XamlCompilerDiagnosticsFilter diagnosticsFilter, bool debuggerLaunch, - bool verboseExceptions) + bool verboseExceptions, + bool createSourceInfo) { if (debuggerLaunch) { @@ -210,6 +211,7 @@ namespace Avalonia.Build.Tasks { EnableIlVerification = verifyIl, DefaultCompileBindings = defaultCompileBindings, + CreateSourceInfo = createSourceInfo, DynamicSetterContainerProvider = new DefaultXamlDynamicSetterContainerProvider(dynamicSettersBuilder) }; diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index 9d6bb93ebb..1e7912d75f 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -127,22 +127,6 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty PreviewWithProperty = AvaloniaProperty .RegisterAttached("PreviewWith", typeof (Design)); - - /// - /// Sets a preview template for the specified at design-time. - /// - /// - /// This method allows you to specify a substitute control to be rendered in the previewer - /// for a given object. - /// - /// The target object. - /// The preview control. - // TODO12: Remove this overload in Avalonia 12 - [Obsolete("Use SetPreviewWith(AvaloniaObject, ITemplate) overload instead. Use from XAML")] - public static void SetPreviewWith(AvaloniaObject target, Control? control) - { - s_previewWith[target] = control is not null ? new FuncTemplate(() => control) : null; - } /// /// Sets a preview template for the specified at design-time. diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 1ededcf81c..4338dc6d53 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -10,11 +10,10 @@ namespace Avalonia.Controls.Documents /// public abstract class Inline : TextElement { - // TODO12: change the field type to an AttachedProperty for consistency (breaking change) /// /// AvaloniaProperty for property. /// - public static readonly StyledProperty TextDecorationsProperty = + public static readonly AttachedProperty TextDecorationsProperty = AvaloniaProperty.RegisterAttached( nameof(TextDecorations), inherits: true); @@ -82,12 +81,13 @@ namespace Avalonia.Controls.Documents return new GenericTextRunProperties( typeface, - FontFeatures, FontSize, - TextDecorations, + TextDecorations, Foreground, parentOrSelfBackground, - BaselineAlignment); + BaselineAlignment, + null, + FontFeatures); } /// diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 7078650da4..05c74b39e5 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -346,13 +346,26 @@ namespace Avalonia.Controls.Presenters private TextLayout CreateTextLayoutInternal(Size constraint, string? text, Typeface typeface, IReadOnlyList>? textStyleOverrides) { - var foreground = Foreground; var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width; var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height; - var textLayout = new TextLayout(text, typeface, FontFeatures, FontSize, foreground, TextAlignment, - TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, - flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing); + var textLayout = new TextLayout( + text, + typeface, + FontSize, + Foreground, + TextAlignment, + TextWrapping, + null, + null, + FlowDirection, + maxWidth, + maxHeight, + LineHeight, + LetterSpacing, + 0, + FontFeatures, + textStyleOverrides); return textLayout; } @@ -553,9 +566,12 @@ namespace Avalonia.Controls.Presenters if (!string.IsNullOrEmpty(preeditText)) { var preeditHighlight = new ValueSpan(caretIndex, preeditText.Length, - new GenericTextRunProperties(typeface, FontFeatures, FontSize, - foregroundBrush: foreground, - textDecorations: TextDecorations.Underline)); + new GenericTextRunProperties( + typeface, + FontSize, + TextDecorations.Underline, + foreground, + fontFeatures: FontFeatures)); textStyleOverrides = new[] { @@ -569,8 +585,11 @@ namespace Avalonia.Controls.Presenters textStyleOverrides = new[] { new ValueSpan(start, length, - new GenericTextRunProperties(typeface, FontFeatures, FontSize, - foregroundBrush: SelectionForegroundBrush)) + new GenericTextRunProperties( + typeface, + FontSize, + foregroundBrush: SelectionForegroundBrush, + fontFeatures: FontFeatures)) }; } } diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs index 5099567630..aa83266683 100644 --- a/src/Avalonia.Controls/Primitives/TextSearch.cs +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -24,22 +24,20 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty TextBindingProperty = AvaloniaProperty.RegisterAttached("TextBinding", typeof(TextSearch)); - // TODO12: Control should be Interactive to match the property definition. /// /// Sets the value of the attached property to a given . /// /// The control. /// The search text to set. - public static void SetText(Control control, string? text) + public static void SetText(Interactive control, string? text) => control.SetValue(TextProperty, text); - // TODO12: Control should be Interactive to match the property definition. /// /// Gets the value of the attached property from a given . /// /// The control. /// The search text. - public static string? GetText(Control control) + public static string? GetText(Interactive control) => control.GetValue(TextProperty); /// diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs index 593adfb225..cc7d7608ff 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs @@ -76,6 +76,7 @@ namespace Avalonia.Controls.Remote.Server Stride, new Vector(_dpi, _dpi), new PlatformPixelFormat((PixelFormatEnum)Format), + Format == ProtocolPixelFormat.Rgb565 ? AlphaFormat.Opaque : AlphaFormat.Premul, () => { handle.Free(); diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index 55321655cc..f3532763f6 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -186,10 +186,10 @@ namespace Avalonia.Controls var defaultProperties = new GenericTextRunProperties( typeface, - FontFeatures, FontSize, TextDecorations, - Foreground); + Foreground, + fontFeatures: FontFeatures); var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing) @@ -235,9 +235,9 @@ namespace Avalonia.Controls overlapLength, new GenericTextRunProperties( textRun.Properties?.Typeface ?? typeface, - textRun.Properties?.FontFeatures ?? FontFeatures, FontSize, - foregroundBrush: SelectionForegroundBrush))); + foregroundBrush: SelectionForegroundBrush, + fontFeatures: textRun.Properties?.FontFeatures ?? FontFeatures))); accumulatedLength += runLength; } @@ -247,8 +247,11 @@ namespace Avalonia.Controls textStyleOverrides = [ new ValueSpan(start, length, - new GenericTextRunProperties(typeface, FontFeatures, FontSize, - foregroundBrush: SelectionForegroundBrush)) + new GenericTextRunProperties( + typeface, + FontSize, + foregroundBrush: SelectionForegroundBrush, + fontFeatures: FontFeatures)) ]; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 55cbae773c..c70d06ae7f 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -657,10 +657,10 @@ namespace Avalonia.Controls var defaultProperties = new GenericTextRunProperties( typeface, - FontFeatures, FontSize, TextDecorations, - Foreground); + Foreground, + fontFeatures: FontFeatures); var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, IsMeasureValid ? TextAlignment : TextAlignment.Left, true, false, defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing) diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs index fd3f3ce676..a352fc2e8b 100644 --- a/src/Avalonia.Native/DeferredFramebuffer.cs +++ b/src/Avalonia.Native/DeferredFramebuffer.cs @@ -29,6 +29,7 @@ namespace Avalonia.Native public int RowBytes { get; set; } public Vector Dpi { get; set; } public PixelFormat Format { get; set; } + public AlphaFormat AlphaFormat { get; set; } public void Dispose() { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 2af06960bc..c6be968bfd 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -133,7 +133,7 @@ namespace Avalonia.X11 return new LockedFramebuffer( _blob.Address + Marshal.SizeOf(), _pixelSize, _pixelSize.Width * 4, - new Vector(96, 96), PixelFormat.Bgra8888, null); + new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul, null); } public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs index 5b01ce91de..4efd7f072d 100644 --- a/src/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -61,7 +61,7 @@ namespace Avalonia.X11 { _fb?.Dispose(); _fb = null; - _fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888); + _fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888, AlphaFormat.Premul); } properties = new FramebufferLockProperties(framebufferValid); diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index bde6de6614..1cfe009062 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -83,7 +83,7 @@ namespace Avalonia.X11 { var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned); return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4, - new Vector(96, 96), PixelFormat.Bgra8888, + new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul, () => h.Free()); } diff --git a/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs b/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs index b3395e87d9..6a0fb61f70 100644 --- a/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs +++ b/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs @@ -47,7 +47,7 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla { _fb?.Dispose(); _fb = null; - _fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888); + _fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888, AlphaFormat.Premul); } return _fb.Lock(new Vector(scaling * 96, scaling * 96), _parent._blit); @@ -66,4 +66,4 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla { PutPixelData(Js, fb.Address.ToInt32(), fb.Size.Width * fb.Size.Height * 4, fb.Size.Width, fb.Size.Height); } -} \ No newline at end of file +} diff --git a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs b/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs index f854e922f8..91495ea0ea 100644 --- a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs +++ b/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs @@ -1,10 +1,7 @@ using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; -using System.Threading; using System.Threading.Tasks; using Avalonia.Browser.Interop; @@ -19,11 +16,14 @@ internal partial class RenderWorker private static partial void InitializeRenderTargets(); internal static int WorkerThreadId; + + // The worker task needs to be rooted otherwise the web worker will exit. + private static Task? s_workerTask; public static Task InitializeAsync() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var workerTask = JSWebWorkerClone.RunAsync(async () => + s_workerTask = JSWebWorkerRunAsync(null, async () => { try { @@ -41,103 +41,18 @@ internal partial class RenderWorker } }); - workerTask.ContinueWith(_ => + s_workerTask.ContinueWith(_ => { - if (workerTask.IsFaulted) - tcs.TrySetException(workerTask.Exception); + if (s_workerTask.IsFaulted) + tcs.TrySetException(s_workerTask.Exception); }); return tcs.Task; } - public static class JSWebWorkerClone - { - private static readonly MethodInfo _setExtLoop; - private static readonly MethodInfo _intallInterop; - - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSSynchronizationContext", - "System.Runtime.InteropServices.JavaScript")] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSHostImplementation", - "System.Runtime.InteropServices.JavaScript")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")] - static JSWebWorkerClone() - { - var syncContext = typeof(System.Runtime.InteropServices.JavaScript.JSHost) - .Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext")!; - var hostImpl = typeof(System.Runtime.InteropServices.JavaScript.JSHost) - .Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSHostImplementation")!; - - _setExtLoop = hostImpl.GetMethod("SetHasExternalEventLoop")!; - _intallInterop = syncContext.GetMethod("InstallWebWorkerInterop")!; - } - - public static Task RunAsync(Func run) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var th = new Thread(_ => - { - _intallInterop.Invoke(null, [false, CancellationToken.None]); - try - { - run().ContinueWith(t => - { - if (t.IsFaulted) - tcs.TrySetException(t.Exception); - else if (t.IsCanceled) - tcs.TrySetCanceled(); - else - tcs.TrySetResult(); - }); - } - catch(Exception e) - { - tcs.TrySetException(e); - } - }) - { - Name = "Manual JS worker" - }; - _setExtLoop.Invoke(null, [th]); -#pragma warning disable CA1416 - th.Start(); -#pragma warning restore CA1416 - return tcs.Task; - } - - } - - // TODO: Use this class instead of JSWebWorkerClone once https://github.com/dotnet/runtime/issues/102010 is fixed - // TODO12: It was fixed in .NET 10 - class JSWebWorkerWrapper - { - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", - "System.Runtime.InteropServices.JavaScript")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")] - [UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")] - static JSWebWorkerWrapper() - { - var type = typeof(System.Runtime.InteropServices.JavaScript.JSHost) - .Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); -#pragma warning disable IL2075 - var m = type! - - .GetMethods(BindingFlags.Static | BindingFlags.Public - ).First(m => m.Name == "RunAsync" - && m.ReturnType == typeof(Task) - && m.GetParameters() is { } parameters - && parameters.Length == 1 - && parameters[0].ParameterType == typeof(Func)); - -#pragma warning restore IL2075 - RunAsync = (Func, Task>) Delegate.CreateDelegate(typeof(Func, Task>), m); - - } - - public static Func, Task> RunAsync { get; set; } - } + // Even though this API is public in the .NET code, it's not part of ref assemblies and is not a stable API. + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RunAsync")] + private static extern Task JSWebWorkerRunAsync( + [UnsafeAccessorType("System.Runtime.InteropServices.JavaScript.JSWebWorker, System.Runtime.InteropServices.JavaScript")] object? instance, + Func body); } diff --git a/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj index bbd10677e9..edd5ed183b 100644 --- a/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Headless/Avalonia.Headless/Avalonia.Headless.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 0a29b9a5b8..05d835c57c 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -414,8 +414,8 @@ namespace Avalonia.Headless public Vector Dpi { get; } public PixelSize PixelSize { get; } - public PixelFormat? Format { get; } - public AlphaFormat? AlphaFormat { get; } + public PixelFormat? Format => PixelFormat.Rgba8888; + public AlphaFormat? AlphaFormat => Platform.AlphaFormat.Premul; public int Version { get; set; } public void Save(string fileName, int? quality = null) @@ -434,7 +434,7 @@ namespace Avalonia.Headless Version++; var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4); return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888, - () => Marshal.FreeHGlobal(mem)); + Platform.AlphaFormat.Premul, () => Marshal.FreeHGlobal(mem)); } } diff --git a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs index 26c076c1e1..0e5a6b0c8b 100644 --- a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs +++ b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs @@ -236,7 +236,9 @@ public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable // If windowing subsystem wasn't initialized by user, force headless with default parameters. if (appBuilder.WindowingSubsystemName != "Headless") { - appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions()); + appBuilder = appBuilder + .UseHeadless(new AvaloniaHeadlessPlatformOptions()) + .UseHarfBuzz(); } // ReSharper disable once AccessToModifiedClosure diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index e4138bee66..8f80e22138 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -200,6 +200,7 @@ namespace Avalonia.Headless public int RowBytes => _fb.RowBytes; public Vector Dpi => _fb.Dpi; public PixelFormat Format => _fb.Format; + public AlphaFormat AlphaFormat => _fb.AlphaFormat; } public ILockedFramebuffer Lock() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs index 97b730c160..f9df24ed69 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs @@ -101,12 +101,17 @@ namespace Avalonia.LinuxFramebuffer.Output public static LockedFramebuffer LockFb(IntPtr address, fb_var_screeninfo varInfo, fb_fix_screeninfo fixedInfo, Vector dpi, Action? dispose) { + var (format, alphaFormat) = varInfo switch + { + { bits_per_pixel: 16 } => (PixelFormat.Rgb565, AlphaFormat.Opaque), + { bits_per_pixel: 32, blue.offset: 16 } => (PixelFormat.Rgba8888, AlphaFormat.Premul), + _ => (PixelFormat.Bgra8888, AlphaFormat.Premul) + }; + return new LockedFramebuffer(address, new PixelSize((int)varInfo.xres, (int)varInfo.yres), (int)fixedInfo.line_length, dpi, - varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 - : varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 - : PixelFormat.Bgra8888, dispose); + format, alphaFormat, dispose); } private void BlitToDevice() diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index 5ab4489c21..728fdb397a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -344,7 +344,8 @@ namespace Avalonia.Markup.Xaml.XamlIl { EnableIlVerification = true, DefaultCompileBindings = configuration.UseCompiledBindingsByDefault, - IsDesignMode = configuration.DesignMode + IsDesignMode = configuration.DesignMode, + CreateSourceInfo = configuration.CreateSourceInfo, }; var parsedDocuments = new List(); @@ -363,7 +364,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } var parsed = compiler.Parse(xaml, overrideType); - parsed.Document = "runtimexaml:" + parsedDocuments.Count; + parsed.Document = document.Document ?? ("runtimexaml" + parsedDocuments.Count); compiler.Transform(parsed); var xamlName = GetSafeUriIdentifier(document.BaseUri) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index e0225a24f7..5b00ea0bea 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX; @@ -20,6 +21,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions private readonly IXamlType _contextType = null!; private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer; private readonly AvaloniaBindingExtensionTransformer _bindingTransformer; + private readonly AvaloniaXamlIlAddSourceInfoTransformer _addSourceInfoTransformer; + private readonly AvaloniaXamlResourceTransformer _resourceTransformer; private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings) : base(configuration, emitMappings, true) @@ -47,6 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlResolveClassesPropertiesTransformer(), new AvaloniaXamlIlTransformInstanceAttachedProperties(), new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers()); + + InsertAfter( new AvaloniaXamlIlAvaloniaPropertyResolver(), new AvaloniaXamlIlReorderClassesPropertiesTransformer(), @@ -85,7 +90,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions ); InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) }, - new AvaloniaXamlIlDeferredResourceTransformer()); + _resourceTransformer = new AvaloniaXamlResourceTransformer()); InsertBefore(new AvaloniaXamlIlTransformRoutedEvent()); @@ -94,6 +99,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer()); Transformers.Add(new AvaloniaXamlIlRootObjectScope()); + Transformers.Add(_addSourceInfoTransformer = new AvaloniaXamlIlAddSourceInfoTransformer()); + Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter()); Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter()); @@ -122,6 +129,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public const string PopulateName = "__AvaloniaXamlIlPopulate"; public const string BuildName = "__AvaloniaXamlIlBuild"; + public bool CreateSourceInfo + { + get => _addSourceInfoTransformer.CreateSourceInfo || _resourceTransformer.CreateSourceInfo; + set => _addSourceInfoTransformer.CreateSourceInfo = _resourceTransformer.CreateSourceInfo = value; + } + public bool IsDesignMode { get => _designTransformer.IsDesignMode; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index 766c6d9e02..9ee893e0d4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -20,8 +20,12 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer { public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) { - if (node is not XamlValueWithManipulationNode valueNode - || valueNode.Value is not XamlAstNewClrObjectNode objectNode + // Filter object initialization nodes like: + // > XamlValueWithManipulationNode + // > > XamlAstNewClrObjectNode // StyleInclude or ResourceInclude, can be nested in another XamlValueWithManipulationNode + // > > XamlObjectInitializationNode + if (node is not XamlValueWithManipulationNode { Manipulation: XamlObjectInitializationNode initializationNode } valueNode + || valueNode.UnwrapValue() is not { } objectNode || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) { @@ -36,11 +40,6 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined"); } - if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode) - { - throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization."); - } - var additionalProperties = new List(); if (initializationNode.Manipulation is not XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty) { @@ -176,9 +175,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning, $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.", node); - + // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`. - if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode + if (sourceProperty.Values.Count != 1) + { + OnInvalidSource(sourceProperty); + return (null, null); + } + + // `new Uri` can be wrapped in manipulation node if source info or another manipulation was applied. + var sourceUriNodeWrapped = sourceProperty.Values.Single(); + var sourceUriNode = sourceUriNodeWrapped switch + { + XamlAstNewClrObjectNode newObj => newObj, + XamlValueWithManipulationNode manipulation => manipulation.UnwrapValue(), + _ => null + }; + + // Validate Uri type and constant arguments. + if (sourceUriNode is null || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs new file mode 100644 index 0000000000..1ce6e456d6 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs @@ -0,0 +1,69 @@ +using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers +{ + /// + /// An XAMLIL AST transformer that injects metadata into the generated XAML code. + /// + /// + /// This transformer wraps object creation nodes with a manipulation node that adds source information. + /// This source information includes line number, position, and document name, which can be useful for debugging and diagnostics. + /// Note: ResourceDictionary source info is handled separately in . + /// + internal class AvaloniaXamlIlAddSourceInfoTransformer : IXamlAstTransformer + { + /// + /// Gets or sets a value indicating whether source information should be generated + /// and injected into the compiled XAML output. + /// + public bool CreateSourceInfo { get; set; } + + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (CreateSourceInfo + && node is XamlAstNewClrObjectNode objNode + && context.ParentNodes().FirstOrDefault() is not XamlValueWithManipulationNode { Manipulation: XamlSourceInfoValueManipulation } + && !objNode.Type.GetClrType().IsValueType) + { + var avaloniaTypes = context.GetAvaloniaTypes(); + + return new XamlValueWithManipulationNode( + objNode, objNode, + new XamlSourceInfoValueManipulation(avaloniaTypes, objNode, context.Document)); + } + + return node; + } + + private class XamlSourceInfoValueManipulation( + AvaloniaXamlIlWellKnownTypes avaloniaTypes, + XamlAstNewClrObjectNode objNode, string? document) + : XamlAstNode(objNode), IXamlAstManipulationNode, IXamlAstILEmitableNode + { + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + // Target object is already on stack. + + // var info = new XamlSourceInfo(Line, Position, Document); + codeGen.Ldc_I4(Line); + codeGen.Ldc_I4(Position); + if (document is not null) + codeGen.Ldstr(document); + else + codeGen.Ldnull(); + codeGen.Newobj(avaloniaTypes.XamlSourceInfoConstructor); + + // Set the XamlSourceInfo property on the current object + // XamlSourceInfo.SetValue(@this, info); + codeGen.EmitCall(avaloniaTypes.XamlSourceInfoSetter); + + return XamlILNodeEmitResult.Void(1); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs deleted file mode 100644 index 81a174c6e2..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; -using XamlX; -using XamlX.Ast; -using XamlX.Emit; -using XamlX.IL; -using XamlX.Transform; -using XamlX.TypeSystem; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers -{ - internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer - { - public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) - { - if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) - return node; - - var types = context.GetAvaloniaTypes(); - - if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content" - && ShouldBeDeferred(pa.Values[1])) - { - IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared - ? types.ResourceDictionaryNotSharedDeferredAdd - : types.ResourceDictionaryDeferredAdd; - - pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); - pa.PossibleSetters = new List - { - new XamlDirectCallPropertySetter(addMethod), - }; - } - else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true - && ShouldBeDeferred(pa.Values[1])) - { - IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared - ? types.ResourceDictionaryNotSharedDeferredAdd - : types.ResourceDictionaryDeferredAdd; - - pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); - pa.PossibleSetters = new List - { - new AdderSetter(pa.Property.Getter, addMethod), - }; - } - - return node; - - bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) - { - value = default; - if (valueNode is XamlAstConstructableObjectNode co) - { - // Try find x:Share directive - if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) - { - if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) - { - if (bool.TryParse(text.Text, out var parseValue)) - { - // If the parser succeeds, remove the x:Share directive - co.Children.Remove(sharedDirective); - return true; - } - else - { - context.ReportTransformError("Invalid argument type for x:Shared directive.", node); - } - } - else - { - context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); - } - } - } - return false; - } - } - - private static bool ShouldBeDeferred(IXamlAstValueNode node) - { - var clrType = node.Type.GetClrType(); - - // XAML compiler is currently strict about value types, allowing them to be created only through converters. - // At the moment it should be safe to not defer structs. - if (clrType.IsValueType) - { - return false; - } - - // Never defer strings. - if (clrType.FullName == "System.String") - { - return false; - } - - // Do not defer resources, if it has any x:Name registration, as it cannot be delayed. - // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes. - // We set target scope level to 0, assuming that this resource node is a scope of itself. - var nameRegistrationsVisitor = new NameScopeRegistrationVisitor( - targetMetadataScopeLevel: 0); - node.Visit(nameRegistrationsVisitor); - if (nameRegistrationsVisitor.Count > 0) - { - return false; - } - - return true; - } - - class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable - { - private readonly IXamlMethod _getter; - private readonly IXamlMethod _adder; - - public AdderSetter(IXamlMethod getter, IXamlMethod adder) - { - _getter = getter; - _adder = adder; - TargetType = getter.DeclaringType; - Parameters = adder.ParametersWithThis().Skip(1).ToList(); - - bool allowNull = Parameters.Last().AcceptsNull(); - BinderParameters = new PropertySetterBinderParameters - { - AllowMultiple = true, - AllowXNull = allowNull, - AllowRuntimeNull = allowNull, - AllowAttributeSyntax = false, - }; - } - - public IXamlType TargetType { get; } - - public PropertySetterBinderParameters BinderParameters { get; } - - public IReadOnlyList Parameters { get; } - public IReadOnlyList CustomAttributes => _adder.CustomAttributes; - - public void Emit(IXamlILEmitter emitter) - { - var locals = new Stack(); - // Save all "setter" parameters - for (var c = Parameters.Count - 1; c >= 0; c--) - { - var loc = emitter.LocalsPool.GetLocal(Parameters[c]); - locals.Push(loc); - emitter.Stloc(loc.Local); - } - - emitter.EmitCall(_getter); - while (locals.Count>0) - using (var loc = locals.Pop()) - emitter.Ldloc(loc.Local); - emitter.EmitCall(_adder, true); - } - - public void EmitWithArguments( - XamlEmitContextWithLocals context, - IXamlILEmitter emitter, - IReadOnlyList arguments) - { - emitter.EmitCall(_getter); - - for (var i = 0; i < arguments.Count; ++i) - context.Emit(arguments[i], emitter, Parameters[i]); - - emitter.EmitCall(_adder, true); - } - - public bool Equals(AdderSetter? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return _getter.Equals(other._getter) && _adder.Equals(other._adder); - } - - public override bool Equals(object? obj) - => Equals(obj as AdderSetter); - - public override int GetHashCode() - => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs new file mode 100644 index 0000000000..4e2458696e --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; +using XamlX; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + /// + /// Transforms ResourceDictionary and IResourceDictionary property assignments + /// to use Add method calls with deferred content where applicable. + /// Additionally, handles x:Shared on assignments and injects XamlSourceInfo. + /// + internal class AvaloniaXamlResourceTransformer : IXamlAstTransformer + { + /// + /// Gets or sets a value indicating whether source information should be generated + /// and injected into the compiled XAML output. + /// + public bool CreateSourceInfo { get; set; } = true; + + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) + return node; + + var types = context.GetAvaloniaTypes(); + var document = context.Document; + + if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") + { + var value = pa.Values[1]; + (var adder, value) = ResolveAdderAndValue(value); + + pa.Values[1] = value; + pa.PossibleSetters = new List + { + new AdderSetter(adder, CreateSourceInfo, types, value.Line, value.Position, document), + }; + } + else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true) + { + var value = pa.Values[1]; + (var adder, value) = ResolveAdderAndValue(value); + + pa.Values[1] = value; + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, adder, CreateSourceInfo, types, value.Line, value.Position, document), + }; + } + + return node; + + (IXamlMethod adder, IXamlAstValueNode newValue) ResolveAdderAndValue(IXamlAstValueNode valueNode) + { + if (ShouldBeDeferred(valueNode)) + { + var adder = TryGetSharedValue(valueNode, out var isShared) && !isShared + ? types.ResourceDictionaryNotSharedDeferredAdd + : types.ResourceDictionaryDeferredAdd; + var deferredNode = new XamlDeferredContentNode(valueNode, types.XamlIlTypes.Object, context.Configuration); + return (adder, deferredNode); + } + else + { + var adder = XamlTransformHelpers.FindPossibleAdders(context, types.IResourceDictionary) + .FirstOrDefault() ?? throw new XamlTransformException("No suitable Add method found for IResourceDictionary.", node); + return (adder, valueNode); + } + } + + bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) + { + value = default; + if (valueNode is XamlAstConstructableObjectNode co) + { + // Try find x:Share directive + if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) + { + if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) + { + if (bool.TryParse(text.Text, out var parseValue)) + { + // If the parser succeeds, remove the x:Share directive + co.Children.Remove(sharedDirective); + return true; + } + else + { + context.ReportTransformError("Invalid argument type for x:Shared directive.", node); + } + } + else + { + context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); + } + } + } + return false; + } + } + + private static bool ShouldBeDeferred(IXamlAstValueNode node) + { + var clrType = node.Type.GetClrType(); + + // XAML compiler is currently strict about value types, allowing them to be created only through converters. + // At the moment it should be safe to not defer structs. + if (clrType.IsValueType) + { + return false; + } + + // Never defer strings. + if (clrType.FullName == "System.String") + { + return false; + } + + // Do not defer resources, if it has any x:Name registration, as it cannot be delayed. + // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes. + // We set target scope level to 0, assuming that this resource node is a scope of itself. + var nameRegistrationsVisitor = new NameScopeRegistrationVisitor( + targetMetadataScopeLevel: 0); + node.Visit(nameRegistrationsVisitor); + if (nameRegistrationsVisitor.Count > 0) + { + return false; + } + + return true; + } + + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable + { + private readonly IXamlMethod? _getter; + private readonly IXamlMethod _adder; + private readonly bool _emitSourceInfo; + private readonly AvaloniaXamlIlWellKnownTypes _avaloniaTypes; + private readonly string? _document; + private readonly int _line, _position; + + /// + /// Creates an adder-only setter. Target is assumed to be already on the stack before emit. + /// For example: + /// var resourceDictionary = ... + /// resourceDictionary.Add(key, value); + /// resourceDictionary.Add(key2, value2); + /// + public AdderSetter( + IXamlMethod adder, + bool emitSourceInfo, + AvaloniaXamlIlWellKnownTypes avaloniaTypes, + int line, int position, string? document) + { + _adder = adder; + _emitSourceInfo = emitSourceInfo; + _avaloniaTypes = avaloniaTypes; + _line = line; + _position = position; + _document = document; + + TargetType = adder.ThisOrFirstParameter(); + Parameters = adder.ParametersWithThis().Skip(1).ToList(); + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; + } + + /// + /// Explicit target getter - target will be obtained by calling the getter first. + /// + /// + public AdderSetter( + IXamlMethod getter, IXamlMethod adder, + bool emitSourceInfo, + AvaloniaXamlIlWellKnownTypes avaloniaTypes, + int line, int position, string? document) + : this(adder, emitSourceInfo, avaloniaTypes, line, position, document) + { + _getter = getter; + TargetType = getter.DeclaringType; + BinderParameters.AllowMultiple = false; + BinderParameters.AllowAttributeSyntax = false; + } + + public IXamlType TargetType { get; } + + public PropertySetterBinderParameters BinderParameters { get; } + + public IReadOnlyList Parameters { get; } + public IReadOnlyList CustomAttributes => _adder.CustomAttributes; + + /// + /// Emits the setter with arguments already on the stack. + /// + /// + /// If _getter is null - assume target is already on the stack. + /// In this case, we can just call Emit. Unless _emitSourceInfo is true. + /// + /// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting, + /// so we need to store parameters to locals first regardless. + /// + public void Emit(IXamlILEmitter emitter) + { + using var keyLocal = emitter.LocalsPool.GetLocal(Parameters[0]); + + if (_getter is not null || _emitSourceInfo) + { + var locals = new Stack(); + // Save all "setter" parameters + for (var c = Parameters.Count - 1; c >= 0; c--) + { + var loc = emitter.LocalsPool.GetLocal(Parameters[c]); + locals.Push(loc); + emitter.Stloc(loc.Local); + + if (c == 0 && _emitSourceInfo) + { + // Store the key argument for XamlSourceInfo later + emitter.Ldloc(loc.Local); + emitter.Stloc(keyLocal.Local); + } + } + + if (_getter is not null) + { + emitter.EmitCall(_getter); + } + + // Duplicate the target object on stack for setting XamlSourceInfo later + emitter.Dup(); + + while (locals.Count > 0) + using (var loc = locals.Pop()) + emitter.Ldloc(loc.Local); + } + + emitter.EmitCall(_adder, true); + + if (_emitSourceInfo) + { + // Target is already on stack (dup) + // Load the key argument from local + emitter.Ldloc(keyLocal.Local); + EmitSetSourceInfo(emitter); + } + } + + /// + /// Emits the setter with provided arguments that are not yet on the stack. + /// + /// + /// If _getter is null - assume target is already on the stack. + /// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting. + /// + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + using var keyLocal = _emitSourceInfo ? emitter.LocalsPool.GetLocal(Parameters[0]) : null; + + if (_getter is not null) + { + emitter.EmitCall(_getter); + } + + if (_emitSourceInfo) + { + // Duplicate the target object on stack for setting XamlSourceInfo later + emitter.Dup(); + } + + for (var i = 0; i < arguments.Count; ++i) + { + context.Emit(arguments[i], emitter, Parameters[i]); + + // Store the key argument for XamlSourceInfo later + if (i == 0 && _emitSourceInfo) + { + emitter.Stloc(keyLocal!.Local); + emitter.Ldloc(keyLocal.Local); + } + } + + emitter.EmitCall(_adder, true); + + if (_emitSourceInfo) + { + // Target is already on stack (dub) + // Load the key argument from local + emitter.Ldloc(keyLocal!.Local); + + EmitSetSourceInfo(emitter); + } + } + + private void EmitSetSourceInfo(IXamlILEmitter emitter) + { + // Assumes the target object and key are already on the stack + + emitter.Ldc_I4(_line); + emitter.Ldc_I4(_position); + if (_document is not null) + emitter.Ldstr(_document); + else + emitter.Ldnull(); + emitter.Newobj(_avaloniaTypes.XamlSourceInfoConstructor); + + // Set the XamlSourceInfo property on the current object + // XamlSourceInfo.SetXamlSourceInfo(@this, key, info); + emitter.EmitCall(_avaloniaTypes.XamlSourceInfoDictionarySetter); + } + + public bool Equals(AdderSetter? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter?.Equals(other._getter) == true && _adder.Equals(other._adder); + } + + public override bool Equals(object? obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter, _adder).GetHashCode(); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 8659eb1299..df54e71108 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -134,6 +134,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ControlTemplate { get; } public IXamlType EventHandlerT { get; } public IXamlMethod GetClassProperty { get; } + public IXamlConstructor XamlSourceInfoConstructor { get; } + public IXamlMethod XamlSourceInfoSetter { get; } + public IXamlMethod XamlSourceInfoDictionarySetter { get; } sealed internal class InteractivityWellKnownTypes { @@ -343,6 +346,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers allowDowncast:false, cfg.WellKnownTypes.String ); + + var xamlSourceInfo = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo"); + XamlSourceInfoConstructor = xamlSourceInfo.GetConstructor([ + XamlIlTypes.Int32, XamlIlTypes.Int32, XamlIlTypes.String + ]); + XamlSourceInfoSetter = + xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, XamlIlTypes.Object, xamlSourceInfo); + XamlSourceInfoDictionarySetter = + xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, IResourceDictionary, XamlIlTypes.Object, xamlSourceInfo); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs new file mode 100644 index 0000000000..612f675124 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs @@ -0,0 +1,26 @@ +using XamlX.Ast; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +internal static class XamlAstNewClrObjectHelper +{ + /// + /// Tries to resolve the underlying value of a , + /// unwrapping any nested instances. + /// + public static TXamlAstValueNode? UnwrapValue(this XamlValueWithManipulationNode node) + where TXamlAstValueNode : class, IXamlAstValueNode + { + var current = node.Value; + while (current is XamlValueWithManipulationNode valueWithManipulation) + { + current = valueWithManipulation.Value; + if (current is TXamlAstValueNode typedValue) + { + return typedValue; + } + } + + return current as TXamlAstValueNode; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index e6186bbea6..6dd2690414 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -38,6 +38,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs b/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs new file mode 100644 index 0000000000..1bb5f1c98c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.Diagnostics +{ + /// + /// Represents source location information for an element within a XAML or code file. + /// + // ReSharper disable once ClassNeverInstantiated.Global //This class is instantiated through the XAML compiler. + public record XamlSourceInfo + { + private static readonly AttachedProperty s_xamlSourceInfo = + AvaloniaProperty.RegisterAttached( + "XamlSourceInfo", typeof(XamlSourceInfo)); + + private static readonly ConditionalWeakTable s_sourceInfo = []; + private static readonly ConditionalWeakTable> s_keyedSourceInfo = []; + + /// + /// Gets the full path of the source file containing the element, or null if unavailable. + /// + public Uri? SourceUri { get; } + + /// + /// Gets the 1-based line number in the source file where the element is defined. + /// + public int LineNumber { get; } + + /// + /// Gets the 1-based column number in the source file where the element is defined. + /// + public int LinePosition { get; } + + /// + /// Initializes a new instance of the class + /// with a specified line, column, and file path. + /// + /// The line number of the source element. + /// The column number of the source element. + /// The full path of the source file. + public XamlSourceInfo(int line, int column, string? filePath) + { + LineNumber = line; + LinePosition = column; + SourceUri = filePath is not null ? new UriBuilder("file", "") { Path = filePath }.Uri : null; + } + + /// + /// Associates XAML source information with the specified object for debugging or diagnostic purposes. + /// + /// This method is typically used to enable enhanced debugging or diagnostics by tracking + /// the origin of XAML elements at runtime. If the same object is passed multiple times, the most recent source + /// information will overwrite any previous value. + /// The object to associate with the XAML source information. Cannot be null. + /// The XAML source information to associate with the object, or null to remove any existing association. + public static void SetXamlSourceInfo(object obj, XamlSourceInfo? info) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + + if (obj is AvaloniaObject avaloniaObject) + { + avaloniaObject.SetValue(s_xamlSourceInfo, info); + } + else + { + s_sourceInfo.AddOrUpdate(obj, info); + } + } + + /// + /// Associates XAML source information with the specified key in the given resource dictionary. + /// + /// The resource dictionary to associate with the XAML source information. + /// The key associated with the source info. + /// The XAML source information to associate with the object, or null to remove any existing association. + public static void SetXamlSourceInfo(IResourceDictionary dictionary, object key, XamlSourceInfo? info) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); + + var dict = s_keyedSourceInfo.GetOrCreateValue(dictionary); + if (info == null) + { + _ = dict.Remove(key); + } + else + { + dict[key] = info; + } + } + + /// + /// Retrieves the XAML source information associated with the specified object, if available. + /// + /// The object for which to obtain XAML source information. Cannot be null. + /// A instance containing the XAML source information for the specified object, or + /// if no source information is available. + public static XamlSourceInfo? GetXamlSourceInfo(object obj) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + + if (obj is AvaloniaObject avaloniaObject) + { + return avaloniaObject.GetValue(s_xamlSourceInfo); + } + else + { + s_sourceInfo.TryGetValue(obj, out var info); + return info; + } + } + + /// + /// Retrieves the XAML source information associated with the specified key in the given resource dictionary, if available. + /// + /// The resource dictionary associated with the XAML source information. + /// The key associated with the source info. + /// A instance containing the XAML source information for the specified key, or + /// if no source information is available. + public static XamlSourceInfo? GetXamlSourceInfo(IResourceDictionary dictionary, object key) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); + + if (s_keyedSourceInfo.TryGetValue(dictionary, out var dict) + && dict.TryGetValue(key, out var info)) + { + return info; + } + + return null; + } + + /// + /// Returns a string that represents the current . + /// + /// + /// A formatted string in the form "FilePath:Line,Column", + /// or "(unknown):Line,Column" if the file path is not set. + /// + public override string ToString() + { + var filePath = SourceUri?.LocalPath ?? "(unknown)"; + return $"{filePath}:{LineNumber},{LinePosition}"; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs index 8723ff4f90..c9c421b766 100644 --- a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs +++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs @@ -22,6 +22,12 @@ public class RuntimeXamlLoaderConfiguration /// public bool DesignMode { get; set; } = false; + /// + /// When enabled, the XAML compiler embeds SourceInfo metadata (file path, line, and column) into generated code. + /// Default is 'false'. + /// + public bool CreateSourceInfo { get; set; } = false; + /// /// XAML diagnostics handler. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs index 5fabc6ee35..937f64f14e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs +++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs @@ -58,6 +58,11 @@ public class RuntimeXamlLoaderDocument /// public Uri? BaseUri { get; set; } + /// + /// Path to the XAML document being loaded. + /// + public string? Document { get; set; } + /// /// The optional instance into which the XAML should be loaded. /// diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 318cdac22c..46be609ec0 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -67,7 +67,7 @@ namespace Avalonia.Skia var framebuffer = _renderTargetWithProperties?.Lock(out lockProperties) ?? _renderTarget.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType(), - framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); + framebuffer.AlphaFormat.ToSkAlphaType()); CreateSurface(framebufferImageInfo, framebuffer); _hadConversionShim |= _conversionShim != null; diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 0372f25047..44408a5333 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -10,7 +10,7 @@ namespace Avalonia.Skia /// /// Immutable Skia bitmap. /// - internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapWithAlphaImpl + internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl { private readonly SKImage _image; private readonly SKBitmap? _bitmap; @@ -195,7 +195,9 @@ namespace Avalonia.Skia if (_bitmap.ColorType.ToAvalonia() is not { } format) throw new NotSupportedException($"Unsupported format {_bitmap.ColorType}"); - return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, null); + var alphaFormat = _bitmap.AlphaType.ToAlphaFormat(); + + return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, alphaFormat, null); } } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 7d8b247ad4..f57f84b168 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -238,6 +238,8 @@ namespace Avalonia.Skia public Vector Dpi => _parent.Dpi; /// public PixelFormat Format => _bitmap.ColorType.ToPixelFormat(); + + public AlphaFormat AlphaFormat => _bitmap.AlphaType.ToAlphaFormat(); } } } diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 5c8e84aaa6..7c11277c57 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -49,7 +49,7 @@ namespace Avalonia.Win32 return fb = new LockedFramebuffer( framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes, - GetCurrentDpi(), s_format, _onDisposeAction); + GetCurrentDpi(), s_format, AlphaFormat.Premul, _onDisposeAction); } finally { diff --git a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs index 57da43da92..1fe301526b 100644 --- a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs +++ b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs @@ -96,14 +96,14 @@ internal class Win32Icon : IDisposable static IntPtr CreateHBitmap(Bitmap source) { - using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888); - source.CopyPixels(fb, AlphaFormat.Unpremul); + using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888, AlphaFormat.Unpremul); + source.CopyPixels(fb); return UnmanagedMethods.CreateBitmap(source.PixelSize.Width, source.PixelSize.Height, 1, 32, fb.Address); } static unsafe IntPtr AlphaToMask(Bitmap source) { - using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite); + using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite, AlphaFormat.Opaque); var height = alphaMaskBuffer.Size.Height; var width = alphaMaskBuffer.Size.Width; @@ -114,8 +114,8 @@ internal class Win32Icon : IDisposable } else { - using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888); - source.CopyPixels(argbBuffer, AlphaFormat.Unpremul); + using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888, AlphaFormat.Unpremul); + source.CopyPixels(argbBuffer); var pSource = (byte*)argbBuffer.Address; var pDest = (byte*)alphaMaskBuffer.Address; @@ -140,7 +140,7 @@ internal class Win32Icon : IDisposable return UnmanagedMethods.CreateBitmap(width, height, 1, 1, alphaMaskBuffer.Address); } - static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format) + static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat) { if (size.Width < 1 || size.Height < 1) throw new ArgumentOutOfRangeException(); @@ -149,7 +149,7 @@ internal class Win32Icon : IDisposable var data = Marshal.AllocHGlobal(size.Height * stride); if (data == IntPtr.Zero) throw new OutOfMemoryException(); - return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format, + return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format, alphaFormat, () => Marshal.FreeHGlobal(data)); } diff --git a/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs deleted file mode 100644 index b7d09a40d7..0000000000 --- a/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using Avalonia.Utilities; -using Xunit; - -#pragma warning disable CS0618 // Type or member is obsolete - -namespace Avalonia.Base.UnitTests.Utilities -{ - public class StringTokenizerTests - { - [Fact] - public void ReadInt32_Reads_Values() - { - var target = new StringTokenizer("123,456"); - - Assert.Equal(123, target.ReadInt32()); - Assert.Equal(456, target.ReadInt32()); - Assert.Throws(() => target.ReadInt32()); - } - - [Fact] - public void ReadDouble_Reads_Values() - { - var target = new StringTokenizer("12.3,45.6"); - - Assert.Equal(12.3, target.ReadDouble()); - Assert.Equal(45.6, target.ReadDouble()); - Assert.Throws(() => target.ReadDouble()); - } - - [Fact] - public void TryReadInt32_Reads_Values() - { - var target = new StringTokenizer("123,456"); - - Assert.True(target.TryReadInt32(out var value)); - Assert.Equal(123, value); - Assert.True(target.TryReadInt32(out value)); - Assert.Equal(456, value); - Assert.False(target.TryReadInt32(out value)); - } - - [Fact] - public void TryReadInt32_Doesnt_Throw() - { - var target = new StringTokenizer("abc"); - - Assert.False(target.TryReadInt32(out var value)); - } - - [Fact] - public void TryReadDouble_Reads_Values() - { - var target = new StringTokenizer("12.3,45.6"); - - Assert.True(target.TryReadDouble(out var value)); - Assert.Equal(12.3, value); - Assert.True(target.TryReadDouble(out value)); - Assert.Equal(45.6, value); - Assert.False(target.TryReadDouble(out value)); - } - - [Fact] - public void TryReadDouble_Doesnt_Throw() - { - var target = new StringTokenizer("abc"); - - Assert.False(target.TryReadDouble(out var value)); - } - - [Fact] - public void ReadSpan_And_ReadString_Reads_Same() - { - var target1 = new StringTokenizer("abc,def"); - var target2 = new StringTokenizer("abc,def"); - - Assert.Equal(target1.ReadString(), target2.ReadSpan().ToString()); - Assert.True(target1.ReadSpan().SequenceEqual(target2.ReadString())); - } - } -} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs index 624e14899b..8949df2eb4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs @@ -12,8 +12,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions { public class ResourceIncludeTests : XamlTestBase { - [Fact] - public void ResourceInclude_Loads_ResourceDictionary() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ResourceInclude_Loads_ResourceDictionary(bool createSourceInfo) { var documents = new[] { @@ -37,9 +39,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions ") }; + var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo }; + using (StartWithResources()) { - var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config); var userControl = Assert.IsType(compiled[1]); var border = userControl.GetControl("border"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs index 608e10f739..c805619f44 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs @@ -18,8 +18,10 @@ public class MergeResourceIncludeTests : XamlTestBase RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle); } - [Fact] - public void MergeResourceInclude_Works_With_Single_Resource() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void MergeResourceInclude_Works_With_Single_Resource(bool createSourceInfo) { var documents = new[] { @@ -41,8 +43,9 @@ public class MergeResourceIncludeTests : XamlTestBase ") }; - - var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + + var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo }; + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config); var contentControl = Assert.IsType(objects[1]); var resources = Assert.IsType(contentControl.Resources); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs index b28e049191..1fdbd9bcc3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs @@ -265,6 +265,8 @@ public class StyleIncludeTests : XamlTestBase [Fact] public void StyleInclude_Should_Be_Replaced_With_Direct_Call() { + using var _ = UnitTestApplication.Start(TestServices.StyledWindow); + var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@" (); var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@" + + """) + { + Document = document + }; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xamlDocument, s_configuration); + + var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl); + + Assert.NotNull(sourceInfo); + Assert.Equal("file", sourceInfo.SourceUri!.Scheme); + Assert.True(sourceInfo.SourceUri!.IsAbsoluteUri); + Assert.Equal(new UriBuilder("file", "") {Path = document}.Uri, sourceInfo.SourceUri); + } + + [Fact] + public void Root_UserControl_Gets_XamlSourceInfo_Set() + { + var xaml = new RuntimeXamlLoaderDocument(@" + +"); + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); + + var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl); + + Assert.NotNull(sourceInfo); + } + + [Fact] + public void Nested_Controls_All_Get_XamlSourceInfo_Set() + { + var xaml = new RuntimeXamlLoaderDocument(@" + + +