diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 1726028a3f..a9bf150ff9 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -60,7 +60,7 @@ namespace ControlCatalog { if (flowDirections.SelectedItem is FlowDirection flowDirection) { - this.FlowDirection = flowDirection; + TopLevel.GetTopLevel(this).FlowDirection = flowDirection; } }; diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index ee10f21317..0bb8f38219 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.Rendering.Composition; +using Avalonia.Utilities; namespace ControlCatalog.Pages { @@ -53,6 +54,7 @@ namespace ControlCatalog.Pages { _currentScale = 1; compositionVisual.Scale = new Vector3(1,1,1); + compositionVisual.Offset = default; image.InvalidateMeasure(); } }; @@ -100,13 +102,19 @@ namespace ControlCatalog.Pages { InitComposition(control!); - isZooming = true; - if(compositionVisual != null) { var scale = _currentScale * (float)e.Scale; + if (scale <= 1) + { + scale = 1; + compositionVisual.Offset = default; + } + compositionVisual.Scale = new(scale, scale, 1); + + e.Handled = true; } }); @@ -114,8 +122,6 @@ namespace ControlCatalog.Pages { InitComposition(control!); - isZooming = false; - if (compositionVisual != null) { _currentScale = compositionVisual.Scale.X; @@ -126,11 +132,19 @@ namespace ControlCatalog.Pages { InitComposition(control!); - if (compositionVisual != null && !isZooming) + if (compositionVisual != null && _currentScale != 1) { - currentOffset -= new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0); + currentOffset += new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0); + + var currentSize = control.Bounds.Size * _currentScale; + + currentOffset = new Vector3((float)MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width), + (float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height), + 0); - compositionVisual.Offset = currentOffset; + compositionVisual.Offset = currentOffset * -1; + + e.Handled = true; } }); } @@ -173,6 +187,8 @@ namespace ControlCatalog.Pages if (ballCompositionVisual != null) { ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1); + + e.Handled = true; } }); @@ -187,11 +203,6 @@ namespace ControlCatalog.Pages void InitComposition(Control control) { - if (ballCompositionVisual != null) - { - return; - } - ballCompositionVisual = ElementComposition.GetElementVisual(ball); if (ballCompositionVisual != null) diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index a43194153c..31b6cad8ab 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -24,11 +24,9 @@ namespace Avalonia Func? validate = null) : base(name, ownerType, metadata, inherits, validate) { + IsAttached = true; } - /// - public override bool IsAttached => true; - /// /// Attaches the property as a non-attached property on the specified type. /// diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index dc94dfba40..1946d4ba5c 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -132,7 +132,7 @@ namespace Avalonia switch (property) { - case StyledPropertyBase styled: + case StyledProperty styled: ClearValue(styled); break; case DirectPropertyBase direct: @@ -147,7 +147,7 @@ namespace Avalonia /// Clears a 's local value. /// /// The property. - public void ClearValue(StyledPropertyBase property) + public void ClearValue(StyledProperty property) { property = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -220,7 +220,7 @@ namespace Avalonia /// The type of the property. /// The property. /// The value. - public T GetValue(StyledPropertyBase property) + public T GetValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -243,7 +243,7 @@ namespace Avalonia } /// - public Optional GetBaseValue(StyledPropertyBase property) + public Optional GetBaseValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -309,7 +309,7 @@ namespace Avalonia /// An if setting the property can be undone, otherwise null. /// public IDisposable? SetValue( - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { @@ -373,7 +373,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { @@ -396,7 +396,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { @@ -419,7 +419,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) { diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 7b17b9152d..6231483ff8 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -146,7 +146,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.Bind(styled, source, priority), + StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; @@ -170,7 +170,7 @@ namespace Avalonia { return property switch { - StyledPropertyBase styled => target.Bind(styled, source, priority), + StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; @@ -231,7 +231,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.GetValue(styled), + StyledProperty styled => target.GetValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; @@ -280,7 +280,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.GetBaseValue(styled), + StyledProperty styled => target.GetBaseValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index e0782c51a2..5db4d81f03 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -107,22 +107,22 @@ namespace Avalonia /// /// Gets a value indicating whether the property inherits its value. /// - public virtual bool Inherits => false; + public bool Inherits { get; private protected set; } /// /// Gets a value indicating whether this is an attached property. /// - public virtual bool IsAttached => false; + public bool IsAttached { get; private protected set; } /// /// Gets a value indicating whether this is a direct property. /// - public virtual bool IsDirect => false; + public bool IsDirect { get; private protected set; } /// /// Gets a value indicating whether this is a readonly property. /// - public virtual bool IsReadOnly => false; + public bool IsReadOnly { get; private protected set; } /// /// Gets an observable that is fired when this property changes on any diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index 2b9da0a61a..9cf25281f2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -15,7 +15,7 @@ namespace Avalonia.Data.Core.Plugins private static MethodInfo? s_observableGeneric; private static MethodInfo? s_observableSelect; - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] public ObservableStreamPlugin() { diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 729240e5a1..d02e277074 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -33,6 +33,8 @@ namespace Avalonia { Getter = getter ?? throw new ArgumentNullException(nameof(getter)); Setter = setter; + IsDirect = true; + IsReadOnly = setter is null; } /// @@ -51,17 +53,10 @@ namespace Avalonia { Getter = getter ?? throw new ArgumentNullException(nameof(getter)); Setter = setter; + IsDirect = true; + IsReadOnly = setter is null; } - /// - public override bool IsDirect => true; - - /// - public override bool IsReadOnly => Setter == null; - - /// - public override Type Owner => typeof(TOwner); - /// /// Gets the getter function. /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index ec9eba6d61..9ee1eee0fa 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -1,8 +1,6 @@ using System; using Avalonia.Data; using Avalonia.PropertyStore; -using Avalonia.Reactive; -using Avalonia.Styling; namespace Avalonia { @@ -28,6 +26,7 @@ namespace Avalonia AvaloniaPropertyMetadata metadata) : base(name, ownerType, metadata) { + Owner = ownerType; } /// @@ -42,12 +41,13 @@ namespace Avalonia AvaloniaPropertyMetadata metadata) : base(source, ownerType, metadata) { + Owner = ownerType; } /// /// Gets the type that registered the property. /// - public abstract Type Owner { get; } + public Type Owner { get; } /// /// Gets the value of the property on the instance. diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index eea7c3b7d1..3b83d0cb87 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -57,7 +57,10 @@ namespace Avalonia.Input var scale = distance / _initialDistance; - _target?.RaiseEvent(new PinchEventArgs(scale, _origin)); + var pinchEventArgs = new PinchEventArgs(scale, _origin); + _target?.RaiseEvent(pinchEventArgs); + + e.Handled = pinchEventArgs.Handled; } } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 23bab13fc8..991694cc60 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input @@ -88,7 +89,10 @@ namespace Avalonia.Input } _pullInProgress = true; - _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection)); + var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection); + _target?.RaiseEvent(pullEventArgs); + + e.Handled = pullEventArgs.Handled; } } diff --git a/src/Avalonia.Base/Logging/TraceLogSink.cs b/src/Avalonia.Base/Logging/TraceLogSink.cs index fc3897fade..a1b4dfe3aa 100644 --- a/src/Avalonia.Base/Logging/TraceLogSink.cs +++ b/src/Avalonia.Base/Logging/TraceLogSink.cs @@ -141,7 +141,7 @@ namespace Avalonia.Logging result.Append(')'); } - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } } } diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index b9a560ad8f..accabce145 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : Animatable + public abstract class Brush : Animatable, IBrush { /// /// Defines the property. diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 65575617d0..0ec7152359 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -13,14 +13,22 @@ namespace Avalonia.Media /// public sealed class GlyphRun : IDisposable { + private readonly static IPlatformRenderInterface s_renderInterface; + private IRef? _platformImpl; private double _fontRenderingEmSize; private int _biDiLevel; private GlyphRunMetrics? _glyphRunMetrics; private ReadOnlyMemory _characters; private IReadOnlyList _glyphInfos; + private Point? _baselineOrigin; private bool _hasOneCharPerCluster; // if true, character index and cluster are similar + static GlyphRun() + { + s_renderInterface = AvaloniaLocator.Current.GetRequiredService(); + } + /// /// Initializes a new instance of the class by specifying properties of the class. /// @@ -28,15 +36,17 @@ namespace Avalonia.Media /// The rendering em size. /// The characters. /// The glyph indices. + /// The baseline origin of the run. /// The bidi level. public GlyphRun( IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlyMemory characters, IReadOnlyList glyphIndices, + Point? baselineOrigin = null, int biDiLevel = 0) : this(glyphTypeface, fontRenderingEmSize, characters, - CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), biDiLevel) + CreateGlyphInfos(glyphIndices, fontRenderingEmSize, glyphTypeface), baselineOrigin, biDiLevel) { _hasOneCharPerCluster = true; } @@ -48,12 +58,14 @@ namespace Avalonia.Media /// The rendering em size. /// The characters. /// The list of glyphs used. + /// The baseline origin of the run. /// The bidi level. public GlyphRun( IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlyMemory characters, IReadOnlyList glyphInfos, + Point? baselineOrigin = null, int biDiLevel = 0) { GlyphTypeface = glyphTypeface; @@ -64,6 +76,8 @@ namespace Avalonia.Media _glyphInfos = glyphInfos; + _baselineOrigin = baselineOrigin; + _biDiLevel = biDiLevel; } @@ -72,6 +86,7 @@ namespace Avalonia.Media _glyphInfos = Array.Empty(); GlyphTypeface = Typeface.Default.GlyphTypeface; _platformImpl = platformImpl; + _baselineOrigin = platformImpl.Item.BaselineOrigin; } private static IReadOnlyList CreateGlyphInfos(IReadOnlyList glyphIndices, @@ -147,9 +162,13 @@ namespace Avalonia.Media => _glyphRunMetrics ??= CreateGlyphRunMetrics(); /// - /// Gets the baseline origin of the. + /// Gets or sets the baseline origin of the. /// - public Point BaselineOrigin => PlatformImpl.Item.BaselineOrigin; + public Point BaselineOrigin + { + get => _baselineOrigin ?? default; + set => Set(ref _baselineOrigin, value); + } /// /// Gets or sets the list of UTF16 code points that represent the Unicode content of the . @@ -204,9 +223,7 @@ namespace Avalonia.Media /// The geometry returned contains the combined geometry of all glyphs in the glyph run. public Geometry BuildGeometry() { - var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); - - var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this); + var geometryImpl = s_renderInterface.BuildGlyphRunGeometry(this); return new PlatformGeometry(geometryImpl); } @@ -802,9 +819,11 @@ namespace Avalonia.Media private IRef CreateGlyphRunImpl() { - var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); - - var platformImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphInfos); + var platformImpl = s_renderInterface.CreateGlyphRun( + GlyphTypeface, + FontRenderingEmSize, + GlyphInfos, + _baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale)); _platformImpl = RefCountable.Create(platformImpl); diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs b/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs index cb8168e693..c7cd58eb6d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs @@ -93,16 +93,19 @@ namespace Avalonia.Media.TextFormatting [Conditional("DEBUG")] public void VerifyAllReturned() { - if (_pendingReturnCount > 0) + var pendingReturnCount = _pendingReturnCount; + _pendingReturnCount = 0; + + if (pendingReturnCount > 0) { throw new InvalidOperationException( - $"{_pendingReturnCount} RentedList<{typeof(T).Name} haven't been returned to the pool!"); + $"{pendingReturnCount} RentedList<{typeof(T).Name}> haven't been returned to the pool!"); } - if (_pendingReturnCount < 0) + if (pendingReturnCount < 0) { throw new InvalidOperationException( - $"{-_pendingReturnCount} RentedList<{typeof(T).Name} extra lists have been returned to the pool!"); + $"{-pendingReturnCount} RentedList<{typeof(T).Name}> extra lists have been returned to the pool!"); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index ac196bf7e0..7f23ac98b4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -185,7 +185,7 @@ namespace Avalonia.Media.TextFormatting ShapedBuffer.FontRenderingEmSize, Text, ShapedBuffer, - BidiLevel); + biDiLevel: BidiLevel); } public void Dispose() diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index 47973e37b5..4c93a1d851 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -113,14 +113,18 @@ namespace Avalonia.Media.TextFormatting var (preSplitRuns, postSplitRuns) = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength, objectPool); - var collapsedRuns = new TextRun[preSplitRuns.Count + 1]; - preSplitRuns.CopyTo(collapsedRuns); - collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; - - objectPool.TextRunLists.Return(ref preSplitRuns); - objectPool.TextRunLists.Return(ref postSplitRuns); - - return collapsedRuns; + try + { + var collapsedRuns = new TextRun[preSplitRuns.Count + 1]; + preSplitRuns.CopyTo(collapsedRuns); + collapsedRuns[collapsedRuns.Length - 1] = shapedSymbol; + return collapsedRuns; + } + finally + { + objectPool.TextRunLists.Return(ref preSplitRuns); + objectPool.TextRunLists.Return(ref postSplitRuns); + } } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 7de842ab39..7505b9ccdd 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -32,58 +32,64 @@ namespace Avalonia.Media.TextFormatting var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine, out var textSourceLength); - RentedList? shapedTextRuns; + RentedList? shapedTextRuns = null; - if (previousLineBreak?.RemainingRuns is { } remainingRuns) + try { - resolvedFlowDirection = previousLineBreak.FlowDirection; - textRuns = remainingRuns; - nextLineBreak = previousLineBreak; - shapedTextRuns = null; - } - else - { - shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, out resolvedFlowDirection); - textRuns = shapedTextRuns; - - if (nextLineBreak == null && textEndOfLine != null) + if (previousLineBreak?.RemainingRuns is { } remainingRuns) { - nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); + resolvedFlowDirection = previousLineBreak.FlowDirection; + textRuns = remainingRuns; + nextLineBreak = previousLineBreak; + shapedTextRuns = null; } - } + else + { + shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, + out resolvedFlowDirection); + textRuns = shapedTextRuns; - TextLineImpl textLine; + if (nextLineBreak == null && textEndOfLine != null) + { + nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); + } + } - switch (textWrapping) - { - case TextWrapping.NoWrap: + TextLineImpl textLine; + + switch (textWrapping) { - // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class - // which already uses an array: ToArray() won't ever be called in this case - var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); + case TextWrapping.NoWrap: + { + // perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class + // which already uses an array: ToArray() won't ever be called in this case + var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray(); - textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); + textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength, + paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); - textLine.FinalizeLine(); + textLine.FinalizeLine(); - break; - } - case TextWrapping.WrapWithOverflow: - case TextWrapping.Wrap: - { - textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, - paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager); - break; + break; + } + case TextWrapping.WrapWithOverflow: + case TextWrapping.Wrap: + { + textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, + paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(textWrapping)); } - default: - throw new ArgumentOutOfRangeException(nameof(textWrapping)); - } - - objectPool.TextRunLists.Return(ref shapedTextRuns); - objectPool.TextRunLists.Return(ref fetchedRuns); - return textLine; + return textLine; + } + finally + { + objectPool.TextRunLists.Return(ref shapedTextRuns); + objectPool.TextRunLists.Return(ref fetchedRuns); + } } /// @@ -224,23 +230,26 @@ namespace Avalonia.Media.TextFormatting (resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; var processedRuns = objectPool.TextRunLists.Rent(); + var groupedRuns = objectPool.UnshapedTextRunLists.Rent(); - CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns); + try + { + CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns); - bidiData.Reset(); - bidiAlgorithm.Reset(); + bidiData.Reset(); + bidiAlgorithm.Reset(); - var groupedRuns = objectPool.UnshapedTextRunLists.Rent(); - var textShaper = TextShaper.Current; - for (var index = 0; index < processedRuns.Count; index++) - { - var currentRun = processedRuns[index]; + var textShaper = TextShaper.Current; - switch (currentRun) + for (var index = 0; index < processedRuns.Count; index++) { - case UnshapedTextRun shapeableRun: + var currentRun = processedRuns[index]; + + switch (currentRun) { + case UnshapedTextRun shapeableRun: + { groupedRuns.Clear(); groupedRuns.Add(shapeableRun); @@ -277,17 +286,20 @@ namespace Avalonia.Media.TextFormatting break; } - default: + default: { shapedRuns.Add(currentRun); break; } + } } } - - objectPool.TextRunLists.Return(ref processedRuns); - objectPool.UnshapedTextRunLists.Return(ref groupedRuns); + finally + { + objectPool.TextRunLists.Return(ref processedRuns); + objectPool.UnshapedTextRunLists.Return(ref groupedRuns); + } return shapedRuns; } @@ -805,25 +817,29 @@ namespace Avalonia.Media.TextFormatting var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength, objectPool); - var textLineBreak = postSplitRuns?.Count > 0 ? - new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) : - null; - - if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null) + try { - textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); - } + var textLineBreak = postSplitRuns?.Count > 0 ? + new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) : + null; - var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength, - paragraphWidth, paragraphProperties, resolvedFlowDirection, - textLineBreak); - - textLine.FinalizeLine(); + if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null) + { + textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); + } - objectPool.TextRunLists.Return(ref preSplitRuns); - objectPool.TextRunLists.Return(ref postSplitRuns); + var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength, + paragraphWidth, paragraphProperties, resolvedFlowDirection, + textLineBreak); - return textLine; + textLine.FinalizeLine(); + return textLine; + } + finally + { + objectPool.TextRunLists.Return(ref preSplitRuns); + objectPool.TextRunLists.Return(ref postSplitRuns); + } } private struct TextRunEnumerator diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index bb58e0d692..4923cdbe32 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -441,128 +441,133 @@ namespace Avalonia.Media.TextFormatting var textLines = objectPool.TextLines.Rent(); - double left = double.PositiveInfinity, width = 0.0, height = 0.0; - - _textSourceLength = 0; + try + { + double left = double.PositiveInfinity, width = 0.0, height = 0.0; - TextLine? previousLine = null; + _textSourceLength = 0; - var textFormatter = TextFormatter.Current; + TextLine? previousLine = null; - while (true) - { - var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, - previousLine?.TextLineBreak); + var textFormatter = TextFormatter.Current; - if (textLine.Length == 0) + while (true) { - if (previousLine != null && previousLine.NewLineLength > 0) + var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, + _paragraphProperties, previousLine?.TextLineBreak); + + if (textLine.Length == 0) { - var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, - _paragraphProperties, fontManager); + if (previousLine != null && previousLine.NewLineLength > 0) + { + var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, + _paragraphProperties, fontManager); - textLines.Add(emptyTextLine); + textLines.Add(emptyTextLine); - UpdateBounds(emptyTextLine, ref left, ref width, ref height); - } + UpdateBounds(emptyTextLine, ref left, ref width, ref height); + } - break; - } + break; + } - _textSourceLength += textLine.Length; + _textSourceLength += textLine.Length; - //Fulfill max height constraint - if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight) - { - if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) + //Fulfill max height constraint + if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) + && height + textLine.Height > MaxHeight) { - var collapsedLine = - previousLine.Collapse(GetCollapsingProperties(MaxWidth)); + if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) + { + var collapsedLine = + previousLine.Collapse(GetCollapsingProperties(MaxWidth)); - textLines[textLines.Count - 1] = collapsedLine; - } + textLines[textLines.Count - 1] = collapsedLine; + } - break; - } + break; + } - var hasOverflowed = textLine.HasOverflowed; + var hasOverflowed = textLine.HasOverflowed; - if (hasOverflowed && _textTrimming != TextTrimming.None) - { - textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth)); - } + if (hasOverflowed && _textTrimming != TextTrimming.None) + { + textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth)); + } - textLines.Add(textLine); + textLines.Add(textLine); - UpdateBounds(textLine, ref left, ref width, ref height); + UpdateBounds(textLine, ref left, ref width, ref height); - previousLine = textLine; + previousLine = textLine; - //Fulfill max lines constraint - if (MaxLines > 0 && textLines.Count >= MaxLines) - { - if(textLine.TextLineBreak?.RemainingRuns is not null) + //Fulfill max lines constraint + if (MaxLines > 0 && textLines.Count >= MaxLines) { - textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + if (textLine.TextLineBreak?.RemainingRuns is not null) + { + textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + } + + break; } - break; + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + { + break; + } } - if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + //Make sure the TextLayout always contains at least on empty line + if (textLines.Count == 0) { - break; - } - } + var textLine = + TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager); - //Make sure the TextLayout always contains at least on empty line - if (textLines.Count == 0) - { - var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager); - - textLines.Add(textLine); - - UpdateBounds(textLine, ref left, ref width, ref height); - } + textLines.Add(textLine); - Bounds = new Rect(left, 0, width, height); + UpdateBounds(textLine, ref left, ref width, ref height); + } - if (_paragraphProperties.TextAlignment == TextAlignment.Justify) - { - var whitespaceWidth = 0d; + Bounds = new Rect(left, 0, width, height); - for (var i = 0; i < textLines.Count; i++) + if (_paragraphProperties.TextAlignment == TextAlignment.Justify) { - var line = textLines[i]; - var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; + var whitespaceWidth = 0d; - if (lineWhitespaceWidth > whitespaceWidth) + for (var i = 0; i < textLines.Count; i++) { - whitespaceWidth = lineWhitespaceWidth; - } - } + var line = textLines[i]; + var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; - var justificationWidth = width - whitespaceWidth; + if (lineWhitespaceWidth > whitespaceWidth) + { + whitespaceWidth = lineWhitespaceWidth; + } + } - if (justificationWidth > 0) - { - var justificationProperties = new InterWordJustification(justificationWidth); + var justificationWidth = width - whitespaceWidth; - for (var i = 0; i < textLines.Count - 1; i++) + if (justificationWidth > 0) { - var line = textLines[i]; + var justificationProperties = new InterWordJustification(justificationWidth); - line.Justify(justificationProperties); + for (var i = 0; i < textLines.Count - 1; i++) + { + var line = textLines[i]; + + line.Justify(justificationProperties); + } } } - } - var result = textLines.ToArray(); - - objectPool.TextLines.Return(ref textLines); - objectPool.VerifyAllReturned(); - - return result; + return textLines.ToArray(); + } + finally + { + objectPool.TextLines.Return(ref textLines); + objectPool.VerifyAllReturned(); + } } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 0d777ad043..2e85b1e187 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -86,7 +86,6 @@ namespace Avalonia.Media.TextFormatting RentedList? rentedPreSplitRuns = null; RentedList? rentedPostSplitRuns = null; - TextRun[]? results; try { @@ -113,9 +112,7 @@ namespace Avalonia.Media.TextFormatting if (measuredLength <= _prefixLength || effectivePostSplitRuns is null) { - results = collapsedRuns.ToArray(); - objectPool.TextRunLists.Return(ref collapsedRuns); - return results; + return collapsedRuns.ToArray(); } var availableSuffixWidth = availableWidth; @@ -157,16 +154,15 @@ namespace Avalonia.Media.TextFormatting } } } + + return collapsedRuns.ToArray(); } finally { objectPool.TextRunLists.Return(ref rentedPreSplitRuns); objectPool.TextRunLists.Return(ref rentedPostSplitRuns); + objectPool.TextRunLists.Return(ref collapsedRuns); } - - results = collapsedRuns.ToArray(); - objectPool.TextRunLists.Return(ref collapsedRuns); - return results; } return new TextRun[] { shapedSymbol }; diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index e2160f21d2..41e792d58e 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -168,8 +168,9 @@ namespace Avalonia.Platform /// The glyph typeface. /// The font rendering em size. /// The list of glyphs. + /// The baseline origin of the run. Can be null. /// An . - IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos); + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin); /// /// Creates a backend-specific object using a low-level API graphics context diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 93fffb3755..3e20dcce56 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -19,7 +19,7 @@ namespace Avalonia.PropertyStore private T? _baseValue; private UncommonFields? _uncommon; - public EffectiveValue(AvaloniaObject owner, StyledPropertyBase property) + public EffectiveValue(AvaloniaObject owner, StyledProperty property) { Priority = BindingPriority.Unset; BasePriority = BindingPriority.Unset; @@ -57,12 +57,12 @@ namespace Avalonia.PropertyStore Debug.Assert(priority != BindingPriority.LocalValue); UpdateValueEntry(value, priority); - SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, GetValue(value), priority); + SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority); } public void SetLocalValueAndRaise( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value) { SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue); @@ -82,7 +82,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(oldValue is not null || newValue is not null); - var p = (StyledPropertyBase)property; + var p = (StyledProperty)property; var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : _metadata.DefaultValue; var n = newValue is not null ? ((EffectiveValue)newValue).Value : _metadata.DefaultValue; var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; @@ -98,7 +98,7 @@ namespace Avalonia.PropertyStore Debug.Assert(Priority != BindingPriority.Animation); Debug.Assert(BasePriority != BindingPriority.Unset); UpdateValueEntry(null, BindingPriority.Animation); - SetAndRaiseCore(owner, (StyledPropertyBase)property, _baseValue!, BasePriority); + SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority); } public override void CoerceValue(ValueStore owner, AvaloniaProperty property) @@ -107,7 +107,7 @@ namespace Avalonia.PropertyStore return; SetAndRaiseCore( owner, - (StyledPropertyBase)property, + (StyledProperty)property, _uncommon._uncoercedValue!, Priority, _uncommon._uncoercedBaseValue!, @@ -117,10 +117,10 @@ namespace Avalonia.PropertyStore public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) { UnsubscribeValueEntries(); - DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); + DisposeAndRaiseUnset(owner, (StyledProperty)property); } - public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase property) + public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty property) { BindingPriority priority; T oldValue; @@ -156,7 +156,7 @@ namespace Avalonia.PropertyStore private void SetAndRaiseCore( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority) { @@ -203,7 +203,7 @@ namespace Avalonia.PropertyStore private void SetAndRaiseCore( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority, T baseValue, diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index 364b4e1225..d8a353dc70 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -9,7 +9,7 @@ namespace Avalonia.PropertyStore public ImmediateValueEntry( ImmediateValueFrame owner, - StyledPropertyBase property, + StyledProperty property, T value) { _owner = owner; @@ -17,7 +17,7 @@ namespace Avalonia.PropertyStore Property = property; } - public StyledPropertyBase Property { get; } + public StyledProperty Property { get; } public bool HasValue => true; AvaloniaProperty IValueEntry.Property => Property; diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 50d5333b9f..7e9f3ab312 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -15,7 +15,7 @@ namespace Avalonia.PropertyStore } public TypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable> source) { var e = new TypedBindingEntry(this, property, source); @@ -24,7 +24,7 @@ namespace Avalonia.PropertyStore } public TypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source) { var e = new TypedBindingEntry(this, property, source); @@ -33,7 +33,7 @@ namespace Avalonia.PropertyStore } public SourceUntypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source) { var e = new SourceUntypedBindingEntry(this, property, source); @@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore return e; } - public ImmediateValueEntry AddValue(StyledPropertyBase property, T value) + public ImmediateValueEntry AddValue(StyledProperty property, T value) { var e = new ImmediateValueEntry(this, property, value); Add(e); diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 8acb885604..f89cb029b6 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -11,13 +11,13 @@ namespace Avalonia.PropertyStore private readonly ValueStore _owner; private IDisposable? _subscription; - public LocalValueBindingObserver(ValueStore owner, StyledPropertyBase property) + public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { _owner = owner; Property = property; } - public StyledPropertyBase Property { get;} + public StyledProperty Property { get;} public void Start(IObservable source) { @@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledPropertyBase property, T value) + static void Execute(ValueStore owner, StyledProperty property, T value) { if (property.ValidateValue?.Invoke(value) != false) owner.SetValue(property, value, BindingPriority.LocalValue); diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 7c529591b6..2d157b2519 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -11,13 +11,13 @@ namespace Avalonia.PropertyStore private readonly ValueStore _owner; private IDisposable? _subscription; - public LocalValueUntypedBindingObserver(ValueStore owner, StyledPropertyBase property) + public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { _owner = owner; Property = property; } - public StyledPropertyBase Property { get; } + public StyledProperty Property { get; } public void Start(IObservable source) { diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b4ac06d2bf..b56d0d4529 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -13,14 +13,14 @@ namespace Avalonia.PropertyStore public SourceUntypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable source) : base(frame, property, source) { _validate = property.ValidateValue; } - public new StyledPropertyBase Property => (StyledPropertyBase)base.Property; + public new StyledProperty Property => (StyledProperty)base.Property; protected override BindingValue ConvertAndValidate(object? value) { diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 2276991a18..697725c87b 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -11,7 +11,7 @@ namespace Avalonia.PropertyStore { public TypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable source) : base(frame, property, source) { @@ -19,13 +19,13 @@ namespace Avalonia.PropertyStore public TypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable> source) : base(frame, property, source) { } - public new StyledPropertyBase Property => (StyledPropertyBase)base.Property; + public new StyledProperty Property => (StyledProperty)base.Property; protected override BindingValue ConvertAndValidate(T value) { diff --git a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs index 5c5591dcb5..372a808fb2 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs @@ -26,7 +26,7 @@ namespace Avalonia.PropertyStore [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] public static bool TryConvertAndValidate( - StyledPropertyBase property, + StyledProperty property, object? value, [MaybeNullWhen(false)] out T result) { diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 92e5288255..f36a96992b 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -43,7 +43,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable> source, BindingPriority priority) { @@ -71,7 +71,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority) { @@ -99,7 +99,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority) { @@ -165,7 +165,7 @@ namespace Avalonia.PropertyStore } } - public IDisposable? SetValue(StyledPropertyBase property, T value, BindingPriority priority) + public IDisposable? SetValue(StyledProperty property, T value, BindingPriority priority) { if (property.ValidateValue?.Invoke(value) == false) { @@ -219,7 +219,7 @@ namespace Avalonia.PropertyStore return GetDefaultValue(property); } - public T GetValue(StyledPropertyBase property) + public T GetValue(StyledProperty property) { if (_effectiveValues.TryGetValue(property, out var v)) return ((EffectiveValue)v).Value; @@ -248,7 +248,7 @@ namespace Avalonia.PropertyStore v.CoerceValue(this, property); } - public Optional GetBaseValue(StyledPropertyBase property) + public Optional GetBaseValue(StyledProperty property) { if (TryGetEffectiveValue(property, out var v) && ((EffectiveValue)v).TryGetBaseValue(out var baseValue)) @@ -450,7 +450,7 @@ namespace Avalonia.PropertyStore /// The old value of the property. /// The effective value instance. public void OnInheritedEffectiveValueChanged( - StyledPropertyBase property, + StyledProperty property, T oldValue, EffectiveValue value) { @@ -475,7 +475,7 @@ namespace Avalonia.PropertyStore /// /// The property whose value changed. /// The old value of the property. - public void OnInheritedEffectiveValueDisposed(StyledPropertyBase property, T oldValue) + public void OnInheritedEffectiveValueDisposed(StyledProperty property, T oldValue) { Debug.Assert(property.Inherits); @@ -520,7 +520,7 @@ namespace Avalonia.PropertyStore /// The old value of the property. /// The new value of the property. public void OnAncestorInheritedValueChanged( - StyledPropertyBase property, + StyledProperty property, T oldValue, T newValue) { diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 019ed09c20..79d1b9202d 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -1,14 +1,18 @@ using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; +using Avalonia.PropertyStore; +using Avalonia.Utilities; namespace Avalonia { /// /// A styled avalonia property. /// - public class StyledProperty : StyledPropertyBase + public class StyledProperty : AvaloniaProperty, IStyledPropertyAccessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the class that registers the property. @@ -23,20 +27,30 @@ namespace Avalonia bool inherits = false, Func? validate = null, Action? notifying = null) - : base(name, ownerType, metadata, inherits, validate, notifying) + : base(name, ownerType, metadata, notifying) { + Inherits = inherits; + ValidateValue = validate; + HasCoercion |= metadata.CoerceValue != null; + + if (validate?.Invoke(metadata.DefaultValue) == false) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); + } } /// - /// Initializes a new instance of the class. + /// Gets the value validation callback for the property. /// - /// The property to add the owner to. - /// The type of the class that registers the property. - internal StyledProperty(StyledPropertyBase source, Type ownerType) - : base(source, ownerType) - { - } - + public Func? ValidateValue { get; } + + /// + /// Gets a value indicating whether this property has any value coercion callbacks defined + /// in its metadata. + /// + internal bool HasCoercion { get; private set; } + /// /// Registers the property on another type. /// @@ -47,5 +61,177 @@ namespace Avalonia AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; } + + public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) + { + var metadata = GetMetadata(instance.GetType()); + + if (metadata.CoerceValue != null) + { + return metadata.CoerceValue.Invoke(instance, baseValue); + } + + return baseValue; + } + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public TValue GetDefaultValue(Type type) + { + return GetMetadata(type).DefaultValue; + } + + /// + /// Gets the property metadata for the specified type. + /// + /// The type. + /// + /// The property metadata. + /// + public new StyledPropertyMetadata GetMetadata(Type type) + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + return (StyledPropertyMetadata)base.GetMetadata(type); + } + + /// + /// Overrides the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject + { + OverrideDefaultValue(typeof(T), defaultValue); + } + + /// + /// Overrides the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(Type type, TValue defaultValue) + { + OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); + } + + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject + { + base.OverrideMetadata(typeof(T), metadata); + } + + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) + { + if (ValidateValue != null) + { + if (!ValidateValue(metadata.DefaultValue)) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); + } + } + + HasCoercion |= metadata.CoerceValue != null; + + base.OverrideMetadata(type, metadata); + } + + /// + /// Gets the string representation of the property. + /// + /// The property's string representation. + public override string ToString() + { + return Name; + } + + /// + object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); + + bool IStyledPropertyAccessor.ValidateValue(object? value) + { + if (value is null && !typeof(TValue).IsValueType) + return ValidateValue?.Invoke(default!) ?? true; + if (value is TValue typed) + return ValidateValue?.Invoke(typed) ?? true; + return false; + } + + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) + { + return new EffectiveValue(o, this); + } + + /// + internal override void RouteClearValue(AvaloniaObject o) + { + o.ClearValue(this); + } + + /// + internal override object? RouteGetValue(AvaloniaObject o) + { + return o.GetValue(this); + } + + /// + internal override object? RouteGetBaseValue(AvaloniaObject o) + { + var value = o.GetBaseValue(this); + return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; + } + + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] + internal override IDisposable? RouteSetValue( + AvaloniaObject target, + object? value, + BindingPriority priority) + { + if (value == BindingOperations.DoNothing) + { + return null; + } + else if (value == UnsetValue) + { + target.ClearValue(this); + return null; + } + else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) + { + return target.SetValue(this, (TValue)converted!, priority); + } + else + { + var type = value?.GetType().FullName ?? "(null)"; + throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); + } + } + + internal override IDisposable RouteBind( + AvaloniaObject target, + IObservable source, + BindingPriority priority) + { + return target.Bind(this, source, priority); + } + + private object? GetDefaultBoxedValue(Type type) + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + return GetMetadata(type).DefaultValue; + } } } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs deleted file mode 100644 index a281a7b7f6..0000000000 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using Avalonia.Data; -using Avalonia.PropertyStore; -using Avalonia.Reactive; -using Avalonia.Styling; -using Avalonia.Utilities; - -namespace Avalonia -{ - /// - /// Base class for styled properties. - /// - public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor - { - private readonly bool _inherits; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the property. - /// The type of the class that registers the property. - /// The property metadata. - /// Whether the property inherits its value. - /// A value validation callback. - /// A callback. - protected StyledPropertyBase( - string name, - Type ownerType, - StyledPropertyMetadata metadata, - bool inherits = false, - Func? validate = null, - Action? notifying = null) - : base(name, ownerType, metadata, notifying) - { - _inherits = inherits; - ValidateValue = validate; - HasCoercion |= metadata.CoerceValue != null; - - if (validate?.Invoke(metadata.DefaultValue) == false) - { - throw new ArgumentException( - $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The property to add the owner to. - /// The type of the class that registers the property. - protected StyledPropertyBase(StyledPropertyBase source, Type ownerType) - : base(source, ownerType, null) - { - _inherits = source.Inherits; - } - - /// - /// Gets a value indicating whether the property inherits its value. - /// - /// - /// A value indicating whether the property inherits its value. - /// - public override bool Inherits => _inherits; - - /// - /// Gets the value validation callback for the property. - /// - public Func? ValidateValue { get; } - - /// - /// Gets a value indicating whether this property has any value coercion callbacks defined - /// in its metadata. - /// - internal bool HasCoercion { get; private set; } - - public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) - { - var metadata = GetMetadata(instance.GetType()); - - if (metadata.CoerceValue != null) - { - return metadata.CoerceValue.Invoke(instance, baseValue); - } - - return baseValue; - } - - /// - /// Gets the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public TValue GetDefaultValue(Type type) - { - return GetMetadata(type).DefaultValue; - } - - /// - /// Gets the property metadata for the specified type. - /// - /// The type. - /// - /// The property metadata. - /// - public new StyledPropertyMetadata GetMetadata(Type type) - { - _ = type ?? throw new ArgumentNullException(nameof(type)); - return (StyledPropertyMetadata)base.GetMetadata(type); - } - - /// - /// Overrides the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject - { - OverrideDefaultValue(typeof(T), defaultValue); - } - - /// - /// Overrides the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public void OverrideDefaultValue(Type type, TValue defaultValue) - { - OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); - } - - /// - /// Overrides the metadata for the property on the specified type. - /// - /// The type. - /// The metadata. - public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject - { - base.OverrideMetadata(typeof(T), metadata); - } - - /// - /// Overrides the metadata for the property on the specified type. - /// - /// The type. - /// The metadata. - public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) - { - if (ValidateValue != null) - { - if (!ValidateValue(metadata.DefaultValue)) - { - throw new ArgumentException( - $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); - } - } - - HasCoercion |= metadata.CoerceValue != null; - - base.OverrideMetadata(type, metadata); - } - - /// - /// Gets the string representation of the property. - /// - /// The property's string representation. - public override string ToString() - { - return Name; - } - - /// - object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); - - bool IStyledPropertyAccessor.ValidateValue(object? value) - { - if (value is null && !typeof(TValue).IsValueType) - return ValidateValue?.Invoke(default!) ?? true; - if (value is TValue typed) - return ValidateValue?.Invoke(typed) ?? true; - return false; - } - - internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) - { - return new EffectiveValue(o, this); - } - - /// - internal override void RouteClearValue(AvaloniaObject o) - { - o.ClearValue(this); - } - - /// - internal override object? RouteGetValue(AvaloniaObject o) - { - return o.GetValue(this); - } - - /// - internal override object? RouteGetBaseValue(AvaloniaObject o) - { - var value = o.GetBaseValue(this); - return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; - } - - /// - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] - internal override IDisposable? RouteSetValue( - AvaloniaObject target, - object? value, - BindingPriority priority) - { - if (value == BindingOperations.DoNothing) - { - return null; - } - else if (value == UnsetValue) - { - target.ClearValue(this); - return null; - } - else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) - { - return target.SetValue(this, (TValue)converted!, priority); - } - else - { - var type = value?.GetType().FullName ?? "(null)"; - throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); - } - } - - internal override IDisposable RouteBind( - AvaloniaObject target, - IObservable source, - BindingPriority priority) - { - return target.Bind(this, source, priority); - } - - private object? GetDefaultBoxedValue(Type type) - { - _ = type ?? throw new ArgumentNullException(nameof(type)); - return GetMetadata(type).DefaultValue; - } - } -} diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index 68a9b8aafe..af5540ecf0 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling ISetterInstance { private readonly StyledElement _target; - private readonly StyledPropertyBase? _styledProperty; + private readonly StyledProperty? _styledProperty; private readonly DirectPropertyBase? _directProperty; private readonly T _value; private IDisposable? _subscription; @@ -22,7 +22,7 @@ namespace Avalonia.Styling public PropertySetterInstance( StyledElement target, - StyledPropertyBase property, + StyledProperty property, T value) { _target = target; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index ff241dce7a..3bd566c622 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -581,12 +581,21 @@ namespace Avalonia.Controls /// The event args. private void HandleInput(RawInputEventArgs e) { - if (e is RawPointerEventArgs pointerArgs) + if (PlatformImpl != null) { - pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position); - } + if (e is RawPointerEventArgs pointerArgs) + { + pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position); + } - _inputManager?.ProcessInput(e); + _inputManager?.ProcessInput(e); + } + else + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log( + this, + "PlatformImpl is null, couldn't handle input."); + } } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) @@ -606,6 +615,13 @@ namespace Avalonia.Controls KeyboardDevice.Instance?.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None); } + protected override bool BypassFlowDirectionPolicies => true; + + public override void InvalidateMirrorTransform() + { + // Do nothing becuase TopLevel should't apply MirrorTransform on himself. + } + ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; } diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 3f539ce198..f2b42b0b7e 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -226,7 +226,7 @@ namespace Avalonia.Controls { if (toIndex < 0) toIndex = count - 1; - else if (toIndex >= count - 1) + else if (toIndex >= count) toIndex = 0; } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 572ff1c876..514d3b3e07 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -120,7 +120,11 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun( + IGlyphTypeface glyphTypeface, + double fontRenderingEmSize, + IReadOnlyList glyphInfos, + Point baselineOrigin) { return new HeadlessGlyphRunStub(); } 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 aab6239a35..0b61316603 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers AvaloniaObjectSetStyledPropertyValue = AvaloniaObject .FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "SetValue" && m.Parameters.Count == 3 - && m.Parameters[0].Name == "StyledPropertyBase`1" + && m.Parameters[0].Name == "StyledProperty`1" && m.Parameters[2].Equals(BindingPriority)); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6630f0707e..b4297a7c33 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -201,7 +201,11 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(size, dpi, format, alphaFormat); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun( + IGlyphTypeface glyphTypeface, + double fontRenderingEmSize, + IReadOnlyList glyphInfos, + Point baselineOrigin) { if (glyphTypeface == null) { @@ -252,7 +256,6 @@ namespace Avalonia.Skia var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; var height = glyphTypeface.Metrics.LineSpacing * scale; - var baselineOrigin = new Point(0, -glyphTypeface.Metrics.Ascent * scale); return new GlyphRunImpl(builder.Build(), new Size(width, height), baselineOrigin); } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 461950b728..fbf8097ece 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -158,7 +158,8 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, + IReadOnlyList glyphInfos, Point baselineOrigin) { var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; @@ -207,7 +208,6 @@ namespace Avalonia.Direct2D1 var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; var height = glyphTypeface.Metrics.LineSpacing * scale; - var baselineOrigin = new Point(0, -glyphTypeface.Metrics.Ascent * scale); return new GlyphRunImpl(run, new Size(width, height), baselineOrigin); } @@ -257,7 +257,7 @@ namespace Avalonia.Direct2D1 sink.Close(); } - var (baselineOriginX, baselineOriginY) = glyphRun.BaselineOrigin; + var (baselineOriginX, baselineOriginY) = glyphRun.PlatformImpl.Item.BaselineOrigin; var transformedGeometry = new SharpDX.Direct2D1.TransformedGeometry( Direct2D1Factory, diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index a05bfbea4c..43feb75c08 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Base.UnitTests.Media glyphInfos[i] = new GlyphInfo(0, glyphClusters[i], glyphAdvances[i]); } - return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, bidiLevel); + return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, biDiLevel: bidiLevel); } } } diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index ee501a86c1..76c7fe97fc 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -77,7 +77,8 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, + IReadOnlyList glyphInfos, Point baselineOrigin) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 9b148c798b..37b79855db 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -123,7 +123,8 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, + IReadOnlyList glyphInfos, Point baselineOrigin) { return new MockGlyphRun(glyphInfos); } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 3f1a3b6342..8170545f68 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -759,6 +759,7 @@ namespace Avalonia.Controls.UnitTests var lbItems = target.GetLogicalChildren().OfType().ToArray(); var first = lbItems.First(); + var beforeLast = lbItems[^2]; var last = lbItems.Last(); first.Focus(); @@ -769,6 +770,12 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(target, Key.Up); Assert.Equal(true, last.IsSelected); + RaiseKeyEvent(target, Key.Up); + Assert.Equal(true, beforeLast.IsSelected); + + RaiseKeyEvent(target, Key.Down); + Assert.Equal(true, last.IsSelected); + RaiseKeyEvent(target, Key.Down); Assert.Equal(true, first.IsSelected); diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs index 59c7ac3786..bfe03030c6 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs @@ -217,7 +217,7 @@ namespace Avalonia.Skia.UnitTests.Media shapedBuffer.FontRenderingEmSize, shapedBuffer.Text, shapedBuffer.GlyphInfos, - shapedBuffer.BidiLevel); + biDiLevel: shapedBuffer.BidiLevel); if(shapedBuffer.BidiLevel == 1) { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 30f949ccb8..93073faefb 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -149,7 +149,8 @@ namespace Avalonia.UnitTests throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, + IReadOnlyList glyphInfos, Point baselineOrigin) { return new MockGlyphRun(glyphInfos); }