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/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/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.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index fc88055e01..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) 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/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.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); }