Browse Source

Merge branch 'master' into storageprovider-api-update

pull/9960/head
Max Katz 4 years ago
committed by GitHub
parent
commit
b14bc0e5b0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog/MainView.xaml.cs
  2. 35
      samples/ControlCatalog/Pages/GesturePage.cs
  3. 4
      src/Avalonia.Base/AttachedProperty.cs
  4. 16
      src/Avalonia.Base/AvaloniaObject.cs
  5. 8
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  6. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  7. 2
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  8. 13
      src/Avalonia.Base/DirectProperty.cs
  9. 6
      src/Avalonia.Base/DirectPropertyBase.cs
  10. 5
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  11. 6
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  12. 2
      src/Avalonia.Base/Logging/TraceLogSink.cs
  13. 2
      src/Avalonia.Base/Media/Brush.cs
  14. 37
      src/Avalonia.Base/Media/GlyphRun.cs
  15. 11
      src/Avalonia.Base/Media/TextFormatting/FormattingObjectPool.cs
  16. 2
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  17. 20
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  18. 154
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  19. 167
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  20. 12
      src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  21. 3
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  22. 20
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  23. 4
      src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs
  24. 8
      src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
  25. 6
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  26. 4
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  27. 4
      src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
  28. 6
      src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
  29. 2
      src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs
  30. 18
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  31. 208
      src/Avalonia.Base/StyledProperty.cs
  32. 250
      src/Avalonia.Base/StyledPropertyBase.cs
  33. 4
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  34. 24
      src/Avalonia.Controls/TopLevel.cs
  35. 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  36. 6
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  37. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  38. 7
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  39. 6
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  40. 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  41. 3
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  42. 3
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  43. 7
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  44. 2
      tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
  45. 3
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

2
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;
}
};

35
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)

4
src/Avalonia.Base/AttachedProperty.cs

@ -24,11 +24,9 @@ namespace Avalonia
Func<TValue, bool>? validate = null)
: base(name, ownerType, metadata, inherits, validate)
{
IsAttached = true;
}
/// <inheritdoc/>
public override bool IsAttached => true;
/// <summary>
/// Attaches the property as a non-attached property on the specified type.
/// </summary>

16
src/Avalonia.Base/AvaloniaObject.cs

@ -132,7 +132,7 @@ namespace Avalonia
switch (property)
{
case StyledPropertyBase<T> styled:
case StyledProperty<T> styled:
ClearValue(styled);
break;
case DirectPropertyBase<T> direct:
@ -147,7 +147,7 @@ namespace Avalonia
/// Clears a <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
public void ClearValue<T>(StyledPropertyBase<T> property)
public void ClearValue<T>(StyledProperty<T> property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -220,7 +220,7 @@ namespace Avalonia
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public T GetValue<T>(StyledPropertyBase<T> property)
public T GetValue<T>(StyledProperty<T> property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -243,7 +243,7 @@ namespace Avalonia
}
/// <inheritdoc/>
public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -309,7 +309,7 @@ namespace Avalonia
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
public IDisposable? SetValue<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -373,7 +373,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -396,7 +396,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -419,7 +419,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority = BindingPriority.LocalValue)
{

8
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -146,7 +146,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
StyledProperty<T> styled => target.Bind(styled, source, priority),
DirectPropertyBase<T> direct => target.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
@ -170,7 +170,7 @@ namespace Avalonia
{
return property switch
{
StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
StyledProperty<T> styled => target.Bind(styled, source, priority),
DirectPropertyBase<T> direct => target.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
@ -231,7 +231,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.GetValue(styled),
StyledProperty<T> styled => target.GetValue(styled),
DirectPropertyBase<T> direct => target.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
@ -280,7 +280,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.GetBaseValue(styled),
StyledProperty<T> styled => target.GetBaseValue(styled),
DirectPropertyBase<T> direct => target.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -107,22 +107,22 @@ namespace Avalonia
/// <summary>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
public virtual bool Inherits => false;
public bool Inherits { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is an attached property.
/// </summary>
public virtual bool IsAttached => false;
public bool IsAttached { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is a direct property.
/// </summary>
public virtual bool IsDirect => false;
public bool IsDirect { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is a readonly property.
/// </summary>
public virtual bool IsReadOnly => false;
public bool IsReadOnly { get; private protected set; }
/// <summary>
/// Gets an observable that is fired when this property changes on any

2
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()
{

13
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;
}
/// <summary>
@ -51,17 +53,10 @@ namespace Avalonia
{
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter;
IsDirect = true;
IsReadOnly = setter is null;
}
/// <inheritdoc/>
public override bool IsDirect => true;
/// <inheritdoc/>
public override bool IsReadOnly => Setter == null;
/// <inheritdoc/>
public override Type Owner => typeof(TOwner);
/// <summary>
/// Gets the getter function.
/// </summary>

6
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;
}
/// <summary>
@ -42,12 +41,13 @@ namespace Avalonia
AvaloniaPropertyMetadata metadata)
: base(source, ownerType, metadata)
{
Owner = ownerType;
}
/// <summary>
/// Gets the type that registered the property.
/// </summary>
public abstract Type Owner { get; }
public Type Owner { get; }
/// <summary>
/// Gets the value of the property on the instance.

5
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;
}
}
}

6
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;
}
}

2
src/Avalonia.Base/Logging/TraceLogSink.cs

@ -141,7 +141,7 @@ namespace Avalonia.Logging
result.Append(')');
}
return result.ToString();
return StringBuilderCache.GetStringAndRelease(result);
}
}
}

2
src/Avalonia.Base/Media/Brush.cs

@ -11,7 +11,7 @@ namespace Avalonia.Media
/// Describes how an area is painted.
/// </summary>
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush : Animatable
public abstract class Brush : Animatable, IBrush
{
/// <summary>
/// Defines the <see cref="Opacity"/> property.

37
src/Avalonia.Base/Media/GlyphRun.cs

@ -13,14 +13,22 @@ namespace Avalonia.Media
/// </summary>
public sealed class GlyphRun : IDisposable
{
private readonly static IPlatformRenderInterface s_renderInterface;
private IRef<IGlyphRunImpl>? _platformImpl;
private double _fontRenderingEmSize;
private int _biDiLevel;
private GlyphRunMetrics? _glyphRunMetrics;
private ReadOnlyMemory<char> _characters;
private IReadOnlyList<GlyphInfo> _glyphInfos;
private Point? _baselineOrigin;
private bool _hasOneCharPerCluster; // if true, character index and cluster are similar
static GlyphRun()
{
s_renderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
}
/// <summary>
/// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
/// </summary>
@ -28,15 +36,17 @@ namespace Avalonia.Media
/// <param name="fontRenderingEmSize">The rendering em size.</param>
/// <param name="characters">The characters.</param>
/// <param name="glyphIndices">The glyph indices.</param>
/// <param name="baselineOrigin">The baseline origin of the run.</param>
/// <param name="biDiLevel">The bidi level.</param>
public GlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlyMemory<char> characters,
IReadOnlyList<ushort> 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
/// <param name="fontRenderingEmSize">The rendering em size.</param>
/// <param name="characters">The characters.</param>
/// <param name="glyphInfos">The list of glyphs used.</param>
/// <param name="baselineOrigin">The baseline origin of the run.</param>
/// <param name="biDiLevel">The bidi level.</param>
public GlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlyMemory<char> characters,
IReadOnlyList<GlyphInfo> 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<GlyphInfo>();
GlyphTypeface = Typeface.Default.GlyphTypeface;
_platformImpl = platformImpl;
_baselineOrigin = platformImpl.Item.BaselineOrigin;
}
private static IReadOnlyList<GlyphInfo> CreateGlyphInfos(IReadOnlyList<ushort> glyphIndices,
@ -147,9 +162,13 @@ namespace Avalonia.Media
=> _glyphRunMetrics ??= CreateGlyphRunMetrics();
/// <summary>
/// Gets the baseline origin of the<see cref="GlyphRun"/>.
/// Gets or sets the baseline origin of the<see cref="GlyphRun"/>.
/// </summary>
public Point BaselineOrigin => PlatformImpl.Item.BaselineOrigin;
public Point BaselineOrigin
{
get => _baselineOrigin ?? default;
set => Set(ref _baselineOrigin, value);
}
/// <summary>
/// Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
@ -204,9 +223,7 @@ namespace Avalonia.Media
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
public Geometry BuildGeometry()
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this);
var geometryImpl = s_renderInterface.BuildGlyphRunGeometry(this);
return new PlatformGeometry(geometryImpl);
}
@ -802,9 +819,11 @@ namespace Avalonia.Media
private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
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);

11
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!");
}
}
}

2
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()

20
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);
}
}
}
}

154
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<TextRun>? shapedTextRuns;
RentedList<TextRun>? 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);
}
}
/// <summary>
@ -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

167
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();
}
}
/// <summary>

12
src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@ -86,7 +86,6 @@ namespace Avalonia.Media.TextFormatting
RentedList<TextRun>? rentedPreSplitRuns = null;
RentedList<TextRun>? 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 };

3
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -168,8 +168,9 @@ namespace Avalonia.Platform
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="glyphInfos">The list of glyphs.</param>
/// <param name="baselineOrigin">The baseline origin of the run. Can be null.</param>
/// <returns>An <see cref="IGlyphRunImpl"/>.</returns>
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos);
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin);
/// <summary>
/// Creates a backend-specific object using a low-level API graphics context

20
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<T> property)
public EffectiveValue(AvaloniaObject owner, StyledProperty<T> 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<T>)value.Property, GetValue(value), priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority);
}
public void SetLocalValueAndRaise(
ValueStore owner,
StyledPropertyBase<T> property,
StyledProperty<T> 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<T>)property;
var p = (StyledProperty<T>)property;
var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : _metadata.DefaultValue;
var n = newValue is not null ? ((EffectiveValue<T>)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<T>)property, _baseValue!, BasePriority);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@ -107,7 +107,7 @@ namespace Avalonia.PropertyStore
return;
SetAndRaiseCore(
owner,
(StyledPropertyBase<T>)property,
(StyledProperty<T>)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<T>)property);
DisposeAndRaiseUnset(owner, (StyledProperty<T>)property);
}
public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property)
public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty<T> property)
{
BindingPriority priority;
T oldValue;
@ -156,7 +156,7 @@ namespace Avalonia.PropertyStore
private void SetAndRaiseCore(
ValueStore owner,
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value,
BindingPriority priority)
{
@ -203,7 +203,7 @@ namespace Avalonia.PropertyStore
private void SetAndRaiseCore(
ValueStore owner,
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value,
BindingPriority priority,
T baseValue,

4
src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs

@ -9,7 +9,7 @@ namespace Avalonia.PropertyStore
public ImmediateValueEntry(
ImmediateValueFrame owner,
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value)
{
_owner = owner;
@ -17,7 +17,7 @@ namespace Avalonia.PropertyStore
Property = property;
}
public StyledPropertyBase<T> Property { get; }
public StyledProperty<T> Property { get; }
public bool HasValue => true;
AvaloniaProperty IValueEntry.Property => Property;

8
src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs

@ -15,7 +15,7 @@ namespace Avalonia.PropertyStore
}
public TypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<BindingValue<T>> source)
{
var e = new TypedBindingEntry<T>(this, property, source);
@ -24,7 +24,7 @@ namespace Avalonia.PropertyStore
}
public TypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<T> source)
{
var e = new TypedBindingEntry<T>(this, property, source);
@ -33,7 +33,7 @@ namespace Avalonia.PropertyStore
}
public SourceUntypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<object?> source)
{
var e = new SourceUntypedBindingEntry<T>(this, property, source);
@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore
return e;
}
public ImmediateValueEntry<T> AddValue<T>(StyledPropertyBase<T> property, T value)
public ImmediateValueEntry<T> AddValue<T>(StyledProperty<T> property, T value)
{
var e = new ImmediateValueEntry<T>(this, property, value);
Add(e);

6
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<T> property)
public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property)
{
_owner = owner;
Property = property;
}
public StyledPropertyBase<T> Property { get;}
public StyledProperty<T> Property { get;}
public void Start(IObservable<T> source)
{
@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore
public void OnNext(T value)
{
static void Execute(ValueStore owner, StyledPropertyBase<T> property, T value)
static void Execute(ValueStore owner, StyledProperty<T> property, T value)
{
if (property.ValidateValue?.Invoke(value) != false)
owner.SetValue(property, value, BindingPriority.LocalValue);

4
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<T> property)
public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty<T> property)
{
_owner = owner;
Property = property;
}
public StyledPropertyBase<T> Property { get; }
public StyledProperty<T> Property { get; }
public void Start(IObservable<object?> source)
{

4
src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs

@ -13,14 +13,14 @@ namespace Avalonia.PropertyStore
public SourceUntypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<TTarget> property,
StyledProperty<TTarget> property,
IObservable<object?> source)
: base(frame, property, source)
{
_validate = property.ValidateValue;
}
public new StyledPropertyBase<TTarget> Property => (StyledPropertyBase<TTarget>)base.Property;
public new StyledProperty<TTarget> Property => (StyledProperty<TTarget>)base.Property;
protected override BindingValue<TTarget> ConvertAndValidate(object? value)
{

6
src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs

@ -11,7 +11,7 @@ namespace Avalonia.PropertyStore
{
public TypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<T> source)
: base(frame, property, source)
{
@ -19,13 +19,13 @@ namespace Avalonia.PropertyStore
public TypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<BindingValue<T>> source)
: base(frame, property, source)
{
}
public new StyledPropertyBase<T> Property => (StyledPropertyBase<T>)base.Property;
public new StyledProperty<T> Property => (StyledProperty<T>)base.Property;
protected override BindingValue<T> ConvertAndValidate(T value)
{

2
src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs

@ -26,7 +26,7 @@ namespace Avalonia.PropertyStore
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
public static bool TryConvertAndValidate<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
object? value,
[MaybeNullWhen(false)] out T result)
{

18
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -43,7 +43,7 @@ namespace Avalonia.PropertyStore
}
public IDisposable AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority)
{
@ -71,7 +71,7 @@ namespace Avalonia.PropertyStore
}
public IDisposable AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<T> source,
BindingPriority priority)
{
@ -99,7 +99,7 @@ namespace Avalonia.PropertyStore
}
public IDisposable AddBinding<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<object?> source,
BindingPriority priority)
{
@ -165,7 +165,7 @@ namespace Avalonia.PropertyStore
}
}
public IDisposable? SetValue<T>(StyledPropertyBase<T> property, T value, BindingPriority priority)
public IDisposable? SetValue<T>(StyledProperty<T> property, T value, BindingPriority priority)
{
if (property.ValidateValue?.Invoke(value) == false)
{
@ -219,7 +219,7 @@ namespace Avalonia.PropertyStore
return GetDefaultValue(property);
}
public T GetValue<T>(StyledPropertyBase<T> property)
public T GetValue<T>(StyledProperty<T> property)
{
if (_effectiveValues.TryGetValue(property, out var v))
return ((EffectiveValue<T>)v).Value;
@ -248,7 +248,7 @@ namespace Avalonia.PropertyStore
v.CoerceValue(this, property);
}
public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{
if (TryGetEffectiveValue(property, out var v) &&
((EffectiveValue<T>)v).TryGetBaseValue(out var baseValue))
@ -450,7 +450,7 @@ namespace Avalonia.PropertyStore
/// <param name="oldValue">The old value of the property.</param>
/// <param name="value">The effective value instance.</param>
public void OnInheritedEffectiveValueChanged<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
T oldValue,
EffectiveValue<T> value)
{
@ -475,7 +475,7 @@ namespace Avalonia.PropertyStore
/// </summary>
/// <param name="property">The property whose value changed.</param>
/// <param name="oldValue">The old value of the property.</param>
public void OnInheritedEffectiveValueDisposed<T>(StyledPropertyBase<T> property, T oldValue)
public void OnInheritedEffectiveValueDisposed<T>(StyledProperty<T> property, T oldValue)
{
Debug.Assert(property.Inherits);
@ -520,7 +520,7 @@ namespace Avalonia.PropertyStore
/// <param name="oldValue">The old value of the property.</param>
/// <param name="newValue">The new value of the property.</param>
public void OnAncestorInheritedValueChanged<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
T oldValue,
T newValue)
{

208
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
{
/// <summary>
/// A styled avalonia property.
/// </summary>
public class StyledProperty<TValue> : StyledPropertyBase<TValue>
public class StyledProperty<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// Initializes a new instance of the <see cref="StyledProperty{T}"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
@ -23,20 +27,30 @@ namespace Avalonia
bool inherits = false,
Func<TValue, bool>? validate = null,
Action<AvaloniaObject, bool>? 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}'.");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// Gets the value validation callback for the property.
/// </summary>
/// <param name="source">The property to add the owner to.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
internal StyledProperty(StyledPropertyBase<TValue> source, Type ownerType)
: base(source, ownerType)
{
}
public Func<TValue, bool>? ValidateValue { get; }
/// <summary>
/// Gets a value indicating whether this property has any value coercion callbacks defined
/// in its metadata.
/// </summary>
internal bool HasCoercion { get; private set; }
/// <summary>
/// Registers the property on another type.
/// </summary>
@ -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;
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
public TValue GetDefaultValue(Type type)
{
return GetMetadata(type).DefaultValue;
}
/// <summary>
/// Gets the property metadata for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public new StyledPropertyMetadata<TValue> GetMetadata(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
return (StyledPropertyMetadata<TValue>)base.GetMetadata(type);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue<T>(TValue defaultValue) where T : AvaloniaObject
{
OverrideDefaultValue(typeof(T), defaultValue);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, TValue defaultValue)
{
OverrideMetadata(type, new StyledPropertyMetadata<TValue>(defaultValue));
}
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{
base.OverrideMetadata(typeof(T), metadata);
}
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata(Type type, StyledPropertyMetadata<TValue> 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);
}
/// <summary>
/// Gets the string representation of the property.
/// </summary>
/// <returns>The property's string representation.</returns>
public override string ToString()
{
return Name;
}
/// <inheritdoc/>
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<TValue>(o, this);
}
/// <inheritdoc/>
internal override void RouteClearValue(AvaloniaObject o)
{
o.ClearValue<TValue>(this);
}
/// <inheritdoc/>
internal override object? RouteGetValue(AvaloniaObject o)
{
return o.GetValue<TValue>(this);
}
/// <inheritdoc/>
internal override object? RouteGetBaseValue(AvaloniaObject o)
{
var value = o.GetBaseValue<TValue>(this);
return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
}
/// <inheritdoc/>
[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<TValue>(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<object?> source,
BindingPriority priority)
{
return target.Bind<TValue>(this, source, priority);
}
private object? GetDefaultBoxedValue(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
return GetMetadata(type).DefaultValue;
}
}
}

250
src/Avalonia.Base/StyledPropertyBase.cs

@ -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
{
/// <summary>
/// Base class for styled properties.
/// </summary>
public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
{
private readonly bool _inherits;
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
protected StyledPropertyBase(
string name,
Type ownerType,
StyledPropertyMetadata<TValue> metadata,
bool inherits = false,
Func<TValue, bool>? validate = null,
Action<AvaloniaObject, bool>? 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}'.");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
/// </summary>
/// <param name="source">The property to add the owner to.</param>
/// <param name="ownerType">The type of the class that registers the property.</param>
protected StyledPropertyBase(StyledPropertyBase<TValue> source, Type ownerType)
: base(source, ownerType, null)
{
_inherits = source.Inherits;
}
/// <summary>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
/// <value>
/// A value indicating whether the property inherits its value.
/// </value>
public override bool Inherits => _inherits;
/// <summary>
/// Gets the value validation callback for the property.
/// </summary>
public Func<TValue, bool>? ValidateValue { get; }
/// <summary>
/// Gets a value indicating whether this property has any value coercion callbacks defined
/// in its metadata.
/// </summary>
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;
}
/// <summary>
/// Gets the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
public TValue GetDefaultValue(Type type)
{
return GetMetadata(type).DefaultValue;
}
/// <summary>
/// Gets the property metadata for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public new StyledPropertyMetadata<TValue> GetMetadata(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
return (StyledPropertyMetadata<TValue>)base.GetMetadata(type);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue<T>(TValue defaultValue) where T : AvaloniaObject
{
OverrideDefaultValue(typeof(T), defaultValue);
}
/// <summary>
/// Overrides the default value for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="defaultValue">The default value.</param>
public void OverrideDefaultValue(Type type, TValue defaultValue)
{
OverrideMetadata(type, new StyledPropertyMetadata<TValue>(defaultValue));
}
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{
base.OverrideMetadata(typeof(T), metadata);
}
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata(Type type, StyledPropertyMetadata<TValue> 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);
}
/// <summary>
/// Gets the string representation of the property.
/// </summary>
/// <returns>The property's string representation.</returns>
public override string ToString()
{
return Name;
}
/// <inheritdoc/>
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<TValue>(o, this);
}
/// <inheritdoc/>
internal override void RouteClearValue(AvaloniaObject o)
{
o.ClearValue<TValue>(this);
}
/// <inheritdoc/>
internal override object? RouteGetValue(AvaloniaObject o)
{
return o.GetValue<TValue>(this);
}
/// <inheritdoc/>
internal override object? RouteGetBaseValue(AvaloniaObject o)
{
var value = o.GetBaseValue<TValue>(this);
return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
}
/// <inheritdoc/>
[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<TValue>(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<object?> source,
BindingPriority priority)
{
return target.Bind<TValue>(this, source, priority);
}
private object? GetDefaultBoxedValue(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
return GetMetadata(type).DefaultValue;
}
}
}

4
src/Avalonia.Base/Styling/PropertySetterInstance.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
ISetterInstance
{
private readonly StyledElement _target;
private readonly StyledPropertyBase<T>? _styledProperty;
private readonly StyledProperty<T>? _styledProperty;
private readonly DirectPropertyBase<T>? _directProperty;
private readonly T _value;
private IDisposable? _subscription;
@ -22,7 +22,7 @@ namespace Avalonia.Styling
public PropertySetterInstance(
StyledElement target,
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value)
{
_target = target;

24
src/Avalonia.Controls/TopLevel.cs

@ -581,12 +581,21 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
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;
}

2
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;
}

6
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<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin)
{
return new HeadlessGlyphRunStub();
}

2
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");

7
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<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> 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);
}

6
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -158,7 +158,8 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> 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<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> 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,

2
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);
}
}
}

3
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<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
{
throw new NotImplementedException();
}

3
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -123,7 +123,8 @@ namespace Avalonia.Benchmarks
return new MockStreamGeometryImpl();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
{
return new MockGlyphRun(glyphInfos);
}

7
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -759,6 +759,7 @@ namespace Avalonia.Controls.UnitTests
var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().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);

2
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)
{

3
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -149,7 +149,8 @@ namespace Avalonia.UnitTests
throw new NotImplementedException();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
{
return new MockGlyphRun(glyphInfos);
}

Loading…
Cancel
Save