Browse Source

Merge remote-tracking branch 'upstream/master'

pull/8368/head
Benedikt Stebner 4 years ago
parent
commit
68f083e74f
  1. 7
      native/Avalonia.Native/src/OSX/AvnView.mm
  2. 4
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  3. 37
      src/Avalonia.Base/GeometryCollection.cs
  4. 18
      src/Avalonia.Base/Media/DrawingCollection.cs
  5. 420
      src/Avalonia.Base/Media/DrawingGroup.cs
  6. 129
      src/Avalonia.Base/Media/FormattedText.cs
  7. 45
      src/Avalonia.Base/Media/GeometryCollection.cs
  8. 12
      src/Avalonia.Base/Media/GeometryDrawing.cs
  9. 58
      src/Avalonia.Base/Media/GeometryGroup.cs
  10. 10
      src/Avalonia.Base/Media/GlyphRun.cs
  11. 3
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  12. 95
      src/Avalonia.Base/Styling/Styles.cs
  13. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  14. 26
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  15. 22
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  16. 14
      tests/Avalonia.Base.UnitTests/Media/GeometryGroupTests.cs
  17. 2
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  18. 2
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  19. 35
      tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
  20. 4
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  21. BIN
      tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png
  22. BIN
      tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

7
native/Avalonia.Native/src/OSX/AvnView.mm

@ -439,7 +439,12 @@
if(_parent != nullptr)
{
_lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
auto handled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
if (key != LeftCtrl && key != RightCtrl) {
_lastKeyHandled = handled;
} else {
_lastKeyHandled = false;
}
}
}

4
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@ -55,6 +55,10 @@ namespace RenderDemo.Pages
formattedText.SetFontStyle(FontStyle.Italic, 28, 28);
context.DrawText(formattedText, new Point(10, 0));
var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(gradient, null, geometry);
}
}
}

37
src/Avalonia.Base/GeometryCollection.cs

@ -1,37 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using Avalonia.Animation;
#nullable enable
namespace Avalonia.Media
{
public class GeometryCollection : Animatable, IList<Geometry>, IReadOnlyList<Geometry>
{
private List<Geometry> _inner;
public GeometryCollection() => _inner = new List<Geometry>();
public GeometryCollection(IEnumerable<Geometry> collection) => _inner = new List<Geometry>(collection);
public GeometryCollection(int capacity) => _inner = new List<Geometry>(capacity);
public Geometry this[int index]
{
get => _inner[index];
set => _inner[index] = value;
}
public int Count => _inner.Count;
public bool IsReadOnly => false;
public void Add(Geometry item) => _inner.Add(item);
public void Clear() => _inner.Clear();
public bool Contains(Geometry item) => _inner.Contains(item);
public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex);
public IEnumerator<Geometry> GetEnumerator() => _inner.GetEnumerator();
public int IndexOf(Geometry item) => _inner.IndexOf(item);
public void Insert(int index, Geometry item) => _inner.Insert(index, item);
public bool Remove(Geometry item) => _inner.Remove(item);
public void RemoveAt(int index) => _inner.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

18
src/Avalonia.Base/Media/DrawingCollection.cs

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia.Media
{
public sealed class DrawingCollection : AvaloniaList<Drawing>
{
public DrawingCollection()
{
ResetBehavior = ResetBehavior.Remove;
}
public DrawingCollection(IEnumerable<Drawing> items) : base(items)
{
ResetBehavior = ResetBehavior.Remove;
}
}
}

420
src/Avalonia.Base/Media/DrawingGroup.cs

@ -1,6 +1,10 @@
using Avalonia.Collections;
using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -18,6 +22,14 @@ namespace Avalonia.Media
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
nameof(Children),
o => o.Children,
(o, v) => o.Children = v);
private DrawingCollection _children = new DrawingCollection();
public double Opacity
{
get => GetValue(OpacityProperty);
@ -42,8 +54,23 @@ namespace Avalonia.Media
set => SetValue(OpacityMaskProperty, value);
}
/// <summary>
/// Gets or sets the collection that contains the child geometries.
/// </summary>
[Content]
public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
public DrawingCollection Children
{
get => _children;
set
{
SetAndRaise(ChildrenProperty, ref _children, value);
}
}
public DrawingContext Open()
{
return new DrawingContext(new DrawingGroupDrawingContext(this));
}
public override void Draw(DrawingContext context)
{
@ -75,5 +102,394 @@ namespace Avalonia.Media
return rect;
}
private class DrawingGroupDrawingContext : IDrawingContextImpl
{
private readonly DrawingGroup _drawingGroup;
private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
private Matrix _transform;
private bool _disposed;
// Root drawing created by this DrawingContext.
//
// If there is only a single child of the root DrawingGroup, _rootDrawing
// will reference the single child, and the root _currentDrawingGroup
// value will be null. Otherwise, _rootDrawing will reference the
// root DrawingGroup, and be the same value as the root _currentDrawingGroup.
//
// Either way, _rootDrawing always references the root drawing.
protected Drawing? _rootDrawing;
// Current DrawingGroup that new children are added to
protected DrawingGroup? _currentDrawingGroup;
// Previous values of _currentDrawingGroup
private Stack<DrawingGroup?>? _previousDrawingGroupStack;
public DrawingGroupDrawingContext(DrawingGroup drawingGroup)
{
_drawingGroup = drawingGroup;
}
public Matrix Transform
{
get => _transform;
set
{
_transform = value;
PushTransform(new MatrixTransform(value));
}
}
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
if ((brush == null) && (pen == null))
{
return;
}
// Instantiate the geometry
var geometry = _platformRenderInterface.CreateEllipseGeometry(rect);
// Add Drawing to the Drawing graph
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
}
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
if (((brush == null) && (pen == null)) || (geometry == null))
{
return;
}
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
}
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
if (foreground == null || glyphRun == null)
{
return;
}
// Add a GlyphRunDrawing to the Drawing graph
GlyphRunDrawing glyphRunDrawing = new GlyphRunDrawing
{
Foreground = foreground,
GlyphRun = glyphRun,
};
// Add Drawing to the Drawing graph
AddDrawing(glyphRunDrawing);
}
public void DrawLine(IPen pen, Point p1, Point p2)
{
if (pen == null)
{
return;
}
// Instantiate the geometry
var geometry = _platformRenderInterface.CreateLineGeometry(p1, p2);
// Add Drawing to the Drawing graph
AddNewGeometryDrawing(null, pen, new PlatformGeometry(geometry));
}
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
{
if ((brush == null) && (pen == null))
{
return;
}
// Instantiate the geometry
var geometry = _platformRenderInterface.CreateRectangleGeometry(rect.Rect);
// Add Drawing to the Drawing graph
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
}
public void Clear(Color color)
{
throw new NotImplementedException();
}
public IDrawingContextLayerImpl CreateLayer(Size size)
{
throw new NotImplementedException();
}
public void Custom(ICustomDrawOperation custom)
{
throw new NotImplementedException();
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{
throw new NotImplementedException();
}
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
throw new NotImplementedException();
}
public void PopBitmapBlendMode()
{
throw new NotImplementedException();
}
public void PopClip()
{
throw new NotImplementedException();
}
public void PopGeometryClip()
{
throw new NotImplementedException();
}
public void PopOpacity()
{
throw new NotImplementedException();
}
public void PopOpacityMask()
{
throw new NotImplementedException();
}
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
throw new NotImplementedException();
}
public void PushClip(Rect clip)
{
throw new NotImplementedException();
}
public void PushClip(RoundedRect clip)
{
throw new NotImplementedException();
}
public void PushGeometryClip(IGeometryImpl clip)
{
throw new NotImplementedException();
}
public void PushOpacity(double opacity)
{
throw new NotImplementedException();
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
throw new NotImplementedException();
}
public void Dispose()
{
// Dispose may be called multiple times without throwing
// an exception.
if (!_disposed)
{
// Match any outstanding Push calls with a Pop
if (_previousDrawingGroupStack != null)
{
int stackCount = _previousDrawingGroupStack.Count;
for (int i = 0; i < stackCount; i++)
{
Pop();
}
}
// Call CloseCore with the root DrawingGroup's children
DrawingCollection rootChildren;
if (_currentDrawingGroup != null)
{
// If we created a root DrawingGroup because multiple elements
// exist at the root level, provide it's Children collection
// directly.
rootChildren = _currentDrawingGroup.Children;
}
else
{
// Create a new DrawingCollection if we didn't create a
// root DrawingGroup because the root level only contained
// a single child.
//
// This collection is needed by DrawingGroup.Open because
// Open always replaces it's Children collection. It isn't
// strictly needed for Append, but always using a collection
// simplifies the TransactionalAppend implementation (i.e.,
// a seperate implemention isn't needed for a single element)
rootChildren = new DrawingCollection();
//
// We may need to opt-out of inheritance through the new Freezable.
// This is controlled by this.CanBeInheritanceContext.
//
if (_rootDrawing != null)
{
rootChildren.Add(_rootDrawing);
}
}
// Inform our derived classes that Close was called
_drawingGroup.Children = rootChildren;
_disposed = true;
}
}
/// <summary>
/// Pop
/// </summary>
private void Pop()
{
// Verify that Pop hasn't been called too many times
if ((_previousDrawingGroupStack == null) || (_previousDrawingGroupStack.Count == 0))
{
throw new InvalidOperationException("DrawingGroupStack count missmatch.");
}
// Restore the previous value of the current drawing group
_currentDrawingGroup = _previousDrawingGroupStack.Pop();
}
/// <summary>
/// PushTransform -
/// Push a Transform which will apply to all drawing operations until the corresponding
/// Pop.
/// </summary>
/// <param name="transform"> The Transform to push. </param>
private void PushTransform(Transform transform)
{
// Instantiate a new drawing group and set it as the _currentDrawingGroup
var drawingGroup = PushNewDrawingGroup();
// Set the transform on the new DrawingGroup
drawingGroup.Transform = transform;
}
/// <summary>
/// Creates a new DrawingGroup for a Push* call by setting the
/// _currentDrawingGroup to a newly instantiated DrawingGroup,
/// and saving the previous _currentDrawingGroup value on the
/// _previousDrawingGroupStack.
/// </summary>
private DrawingGroup PushNewDrawingGroup()
{
// Instantiate a new drawing group
DrawingGroup drawingGroup = new DrawingGroup();
// Add it to the drawing graph, like any other Drawing
AddDrawing(drawingGroup);
// Lazily allocate the stack when it is needed because many uses
// of DrawingDrawingContext will have a depth of one.
if (null == _previousDrawingGroupStack)
{
_previousDrawingGroupStack = new Stack<DrawingGroup?>(2);
}
// Save the previous _currentDrawingGroup value.
//
// If this is the first call, the value of _currentDrawingGroup
// will be null because AddDrawing doesn't create a _currentDrawingGroup
// for the first drawing. Having null on the stack is valid, and simply
// denotes that this new DrawingGroup is the first child in the root
// DrawingGroup. It is also possible for the first value on the stack
// to be non-null, which means that the root DrawingGroup has other
// children.
_previousDrawingGroupStack.Push(_currentDrawingGroup);
// Set this drawing group as the current one so that subsequent drawing's
// are added as it's children until Pop is called.
_currentDrawingGroup = drawingGroup;
return drawingGroup;
}
/// <summary>
/// Contains the functionality common to GeometryDrawing operations of
/// instantiating the GeometryDrawing, setting it's Freezable state,
/// and Adding it to the Drawing Graph.
/// </summary>
private void AddNewGeometryDrawing(IBrush? brush, IPen? pen, Geometry? geometry)
{
if (geometry == null)
{
throw new ArgumentNullException(nameof(geometry));
}
// Instantiate the GeometryDrawing
GeometryDrawing geometryDrawing = new GeometryDrawing
{
// We may need to opt-out of inheritance through the new Freezable.
// This is controlled by this.CanBeInheritanceContext.
Brush = brush,
Pen = pen,
Geometry = geometry
};
// Add it to the drawing graph
AddDrawing(geometryDrawing);
}
/// <summary>
/// Adds a new Drawing to the DrawingGraph.
///
/// This method avoids creating a DrawingGroup for the common case
/// where only a single child exists in the root DrawingGroup.
/// </summary>
private void AddDrawing(Drawing newDrawing)
{
if (newDrawing == null)
{
throw new ArgumentNullException(nameof(newDrawing));
}
if (_rootDrawing == null)
{
// When a DrawingGroup is set, it should be made the root if
// a root drawing didnt exist.
Contract.Requires<NotSupportedException>(_currentDrawingGroup == null);
// If this is the first Drawing being added, avoid creating a DrawingGroup
// and set this drawing as the root drawing. This optimizes the common
// case where only a single child exists in the root DrawingGroup.
_rootDrawing = newDrawing;
}
else if (_currentDrawingGroup == null)
{
// When the second drawing is added at the root level, set a
// DrawingGroup as the root and add both drawings to it.
// Instantiate the DrawingGroup
_currentDrawingGroup = new DrawingGroup();
// Add both Children
_currentDrawingGroup.Children.Add(_rootDrawing);
_currentDrawingGroup.Children.Add(newDrawing);
// Set the new DrawingGroup as the current
_rootDrawing = _currentDrawingGroup;
}
else
{
// If there already is a current drawing group, then simply add
// the new drawing too it.
_currentDrawingGroup.Children.Add(newDrawing);
}
}
}
}
}

129
src/Avalonia.Base/Media/FormattedText.cs

@ -1223,7 +1223,7 @@ namespace Avalonia.Media
public double OverhangTrailing
{
get
{
{
return BlackBoxMetrics.OverhangTrailing;
}
}
@ -1252,6 +1252,46 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Obtains geometry for the text, including underlines and strikethroughs.
/// </summary>
/// <param name="origin">The left top origin of the resulting geometry.</param>
/// <returns>The geometry returned contains the combined geometry
/// of all of the glyphs, underlines and strikeThroughs that represent the formatted text.
/// Overlapping contours are merged by performing a Boolean union operation.</returns>
public Geometry? BuildGeometry(Point origin)
{
GeometryGroup? accumulatedGeometry = null;
var lineOrigin = origin;
DrawingGroup drawing = new DrawingGroup();
using (var ctx = drawing.Open())
{
using (var enumerator = GetEnumerator())
{
while (enumerator.MoveNext())
{
var currentLine = enumerator.Current;
if (currentLine != null)
{
currentLine.Draw(ctx, lineOrigin);
AdvanceLineOrigin(ref lineOrigin, currentLine);
}
}
}
}
Transform? transform = new TranslateTransform(origin.X, origin.Y);
// recursively go down the DrawingGroup to build up the geometry
CombineGeometryRecursive(drawing, ref transform, ref accumulatedGeometry);
return accumulatedGeometry;
}
/// <summary>
/// Draws the text object
/// </summary>
@ -1284,6 +1324,93 @@ namespace Avalonia.Media
}
}
private void CombineGeometryRecursive(Drawing drawing, ref Transform? transform, ref GeometryGroup? accumulatedGeometry)
{
if (drawing is DrawingGroup group)
{
transform = group.Transform;
if (group.Children is DrawingCollection children)
{
// recursively go down for DrawingGroup
foreach (var child in children)
{
CombineGeometryRecursive(child, ref transform, ref accumulatedGeometry);
}
}
}
else
{
if (drawing is GlyphRunDrawing glyphRunDrawing)
{
// process glyph run
var glyphRun = glyphRunDrawing.GlyphRun;
if (glyphRun != null)
{
var glyphRunGeometry = glyphRun.BuildGeometry();
glyphRunGeometry.Transform = transform;
if (accumulatedGeometry == null)
{
accumulatedGeometry = new GeometryGroup
{
FillRule = FillRule.NonZero
};
}
accumulatedGeometry.Children.Add(glyphRunGeometry);
}
}
else
{
if (drawing is GeometryDrawing geometryDrawing)
{
// process geometry (i.e. TextDecoration on the line)
var geometry = geometryDrawing.Geometry;
if (geometry != null)
{
geometry.Transform = transform;
if (geometry is LineGeometry lineGeometry)
{
// For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no
// bounding area. So this line won't show up. Work aroud it by increase the Bounding rect
// to be Pen's thickness
var bounds = lineGeometry.Bounds;
if (bounds.Height == 0)
{
bounds = bounds.WithHeight(geometryDrawing.Pen?.Thickness ?? 0);
}
else if (bounds.Width == 0)
{
bounds = bounds.WithWidth(geometryDrawing.Pen?.Thickness ?? 0);
}
// convert the line geometry into a rectangle geometry
// we lost line cap info here
geometry = new RectangleGeometry(bounds);
}
if (accumulatedGeometry == null)
{
accumulatedGeometry = new GeometryGroup
{
FillRule = FillRule.NonZero
};
}
accumulatedGeometry.Children.Add(geometry);
}
}
}
}
}
private CachedMetrics DrawAndCalculateMetrics(DrawingContext? drawingContext, Point drawingOffset, bool getBlackBoxMetrics)
{
var metrics = new CachedMetrics();

45
src/Avalonia.Base/Media/GeometryCollection.cs

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
#nullable enable
namespace Avalonia.Media
{
public sealed class GeometryCollection : AvaloniaList<Geometry>
{
public GeometryCollection()
{
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
x =>
{
Parent?.Invalidate();
},
x =>
{
Parent?.Invalidate();
},
() => throw new NotSupportedException());
}
public GeometryCollection(IEnumerable<Geometry> items) : base(items)
{
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
x =>
{
Parent?.Invalidate();
},
x =>
{
Parent?.Invalidate();
},
() => throw new NotSupportedException());
}
public GeometryGroup? Parent { get; set; }
}
}

12
src/Avalonia.Base/Media/GeometryDrawing.cs

@ -21,14 +21,14 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Brush"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> BrushProperty =
AvaloniaProperty.Register<GeometryDrawing, IBrush>(nameof(Brush), Brushes.Transparent);
public static readonly StyledProperty<IBrush?> BrushProperty =
AvaloniaProperty.Register<GeometryDrawing, IBrush?>(nameof(Brush), Brushes.Transparent);
/// <summary>
/// Defines the <see cref="Pen"/> property.
/// </summary>
public static readonly StyledProperty<Pen> PenProperty =
AvaloniaProperty.Register<GeometryDrawing, Pen>(nameof(Pen));
public static readonly StyledProperty<Pen?> PenProperty =
AvaloniaProperty.Register<GeometryDrawing, Pen?>(nameof(Pen));
/// <summary>
/// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.
@ -43,7 +43,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the <see cref="Avalonia.Media.IBrush"/> used to fill the interior of the shape described by this <see cref="GeometryDrawing"/>.
/// </summary>
public IBrush Brush
public IBrush? Brush
{
get => GetValue(BrushProperty);
set => SetValue(BrushProperty, value);
@ -52,7 +52,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the <see cref="Avalonia.Media.IPen"/> used to stroke this <see cref="GeometryDrawing"/>.
/// </summary>
public IPen Pen
public IPen? Pen
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);

58
src/Avalonia.Base/GeometryGroup.cs → src/Avalonia.Base/Media/GeometryGroup.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Metadata;
using Avalonia.Metadata;
using Avalonia.Platform;
#nullable enable
@ -13,29 +10,36 @@ namespace Avalonia.Media
/// </summary>
public class GeometryGroup : Geometry
{
public static readonly DirectProperty<GeometryGroup, GeometryCollection?> ChildrenProperty =
AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection?> (
public static readonly DirectProperty<GeometryGroup, GeometryCollection> ChildrenProperty =
AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection> (
nameof(Children),
o => o.Children,
(o, v) => o.Children = v);
(o, v)=> o.Children = v);
public static readonly StyledProperty<FillRule> FillRuleProperty =
AvaloniaProperty.Register<GeometryGroup, FillRule>(nameof(FillRule));
private GeometryCollection? _children;
private bool _childrenSet;
private GeometryCollection _children;
public GeometryGroup()
{
_children = new GeometryCollection
{
Parent = this
};
}
/// <summary>
/// Gets or sets the collection that contains the child geometries.
/// </summary>
[Content]
public GeometryCollection? Children
public GeometryCollection Children
{
get => _children ??= (!_childrenSet ? new GeometryCollection() : null);
get => _children;
set
{
SetAndRaise(ChildrenProperty, ref _children, value);
_childrenSet = true;
OnChildrenChanged(_children, value);
SetAndRaise(ChildrenProperty, ref _children, value);
}
}
@ -52,16 +56,28 @@ namespace Avalonia.Media
public override Geometry Clone()
{
var result = new GeometryGroup { FillRule = FillRule, Transform = Transform };
if (_children?.Count > 0)
if (_children.Count > 0)
{
result.Children = new GeometryCollection(_children);
}
return result;
}
protected void OnChildrenChanged(GeometryCollection oldChildren, GeometryCollection newChildren)
{
oldChildren.Parent = null;
newChildren.Parent = this;
}
protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_children?.Count > 0)
if (_children.Count > 0)
{
var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
return factory.CreateGeometryGroup(FillRule, _children);
}
@ -72,10 +88,18 @@ namespace Avalonia.Media
{
base.OnPropertyChanged(change);
if (change.Property == ChildrenProperty || change.Property == FillRuleProperty)
switch (change.Property.Name)
{
InvalidateGeometry();
case nameof(FillRule):
case nameof(Children):
InvalidateGeometry();
break;
}
}
internal void Invalidate()
{
InvalidateGeometry();
}
}
}

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

@ -202,15 +202,9 @@ namespace Avalonia.Media
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this, out var scale);
var geometryImpl = platformRenderInterface.BuildGlyphRunGeometry(this);
var geometry = new PlatformGeometry(geometryImpl);
var transform = new MatrixTransform(Matrix.CreateTranslation(geometry.Bounds.Left, -geometry.Bounds.Top) * scale);
geometry.Transform = transform;
return geometry;
return new PlatformGeometry(geometryImpl);
}
/// <summary>

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

@ -62,9 +62,8 @@ namespace Avalonia.Platform
/// Created a geometry implementation for the glyph run.
/// </summary>
/// <param name="glyphRun">The glyph run to build a geometry from.</param>
/// <param name="scale">The scaling of the produces geometry.</param>
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale);
IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun);
/// <summary>
/// Creates a renderer.

95
src/Avalonia.Base/Styling/Styles.cs

@ -17,7 +17,7 @@ namespace Avalonia.Styling
IStyle,
IResourceProvider
{
private readonly AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
private readonly AvaloniaList<IStyle> _styles = new();
private IResourceHost? _owner;
private IResourceDictionary? _resources;
private StyleCache? _cache;
@ -62,16 +62,18 @@ namespace Avalonia.Styling
{
value = value ?? throw new ArgumentNullException(nameof(Resources));
if (Owner is object)
var currentOwner = Owner;
if (currentOwner is not null)
{
_resources?.RemoveOwner(Owner);
_resources?.RemoveOwner(currentOwner);
}
_resources = value;
if (Owner is object)
if (currentOwner is not null)
{
_resources.AddOwner(Owner);
_resources.AddOwner(currentOwner);
}
}
}
@ -89,7 +91,7 @@ namespace Avalonia.Styling
foreach (var i in this)
{
if (i is IResourceProvider p && p.HasResources)
if (i is IResourceProvider { HasResources: true })
{
return true;
}
@ -190,7 +192,7 @@ namespace Avalonia.Styling
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner != null)
if (Owner is not null)
{
throw new InvalidOperationException("The Styles already has a owner.");
}
@ -227,70 +229,81 @@ namespace Avalonia.Styling
}
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
private static IReadOnlyList<T> ToReadOnlyList<T>(ICollection list)
{
static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
if (list is IReadOnlyList<T> readOnlyList)
{
if (list is IReadOnlyList<T>)
{
return (IReadOnlyList<T>)list;
}
else
{
var result = new T[list.Count];
list.CopyTo(result, 0);
return result;
}
return readOnlyList;
}
void Add(IList items)
var result = new T[list.Count];
list.CopyTo(result, 0);
return result;
}
private static void InternalAdd(IList items, IResourceHost? owner, ref StyleCache? cache)
{
if (owner is not null)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i]!;
if (Owner is object && style is IResourceProvider resourceProvider)
if (items[i] is IResourceProvider provider)
{
resourceProvider.AddOwner(Owner);
provider.AddOwner(owner);
}
_cache = null;
}
(Owner as IStyleHost)?.StylesAdded(ToReadOnlyList<IStyle>(items));
(owner as IStyleHost)?.StylesAdded(ToReadOnlyList<IStyle>(items));
}
if (items.Count > 0)
{
cache = null;
}
}
void Remove(IList items)
private static void InternalRemove(IList items, IResourceHost? owner, ref StyleCache? cache)
{
if (owner is not null)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i]!;
if (Owner is object && style is IResourceProvider resourceProvider)
if (items[i] is IResourceProvider provider)
{
resourceProvider.RemoveOwner(Owner);
provider.RemoveOwner(owner);
}
_cache = null;
}
(Owner as IStyleHost)?.StylesRemoved(ToReadOnlyList<IStyle>(items));
(owner as IStyleHost)?.StylesRemoved(ToReadOnlyList<IStyle>(items));
}
if (items.Count > 0)
{
cache = null;
}
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
throw new InvalidOperationException("Reset should not be called on Styles.");
}
var currentOwner = Owner;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add(e.NewItems!);
InternalAdd(e.NewItems!, currentOwner, ref _cache);
break;
case NotifyCollectionChangedAction.Remove:
Remove(e.OldItems!);
InternalRemove(e.OldItems!, currentOwner, ref _cache);
break;
case NotifyCollectionChangedAction.Replace:
Remove(e.OldItems!);
Add(e.NewItems!);
InternalRemove(e.OldItems!, currentOwner, ref _cache);
InternalAdd(e.NewItems!, currentOwner, ref _cache);
break;
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException("Reset should not be called on Styles.");
}
CollectionChanged?.Invoke(this, e);

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -114,10 +114,8 @@ namespace Avalonia.Headless
return new HeadlessGlyphRunStub();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
scale = Matrix.Identity;
return new HeadlessGeometryStub(new Rect(glyphRun.Size));
}

26
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -62,7 +62,7 @@ namespace Avalonia.Skia
return new CombinedGeometryImpl(combineMode, g1, g2);
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
{
@ -79,21 +79,29 @@ namespace Avalonia.Skia
};
SKPath path = new SKPath();
var matrix = SKMatrix.Identity;
var currentX = 0f;
var (currentX, currentY) = glyphRun.BaselineOrigin;
foreach (var glyph in glyphRun.GlyphIndices)
for (var i = 0; i < glyphRun.GlyphIndices.Count; i++)
{
var p = skFont.GetGlyphPath(glyph);
var glyph = glyphRun.GlyphIndices[i];
var glyphPath = skFont.GetGlyphPath(glyph);
path.AddPath(p, currentX, 0);
if (!glyphPath.IsEmpty)
{
path.AddPath(glyphPath, (float)currentX, (float)currentY);
}
currentX += p.Bounds.Right;
if (glyphRun.GlyphAdvances != null)
{
currentX += glyphRun.GlyphAdvances[i];
}
else
{
currentX += glyphPath.Bounds.Right;
}
}
scale = Matrix.CreateScale(matrix.ScaleX, matrix.ScaleY);
return new StreamGeometryImpl(path);
}

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

@ -13,6 +13,7 @@ using Avalonia.Media.Imaging;
using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop;
namespace Avalonia
{
@ -159,7 +160,7 @@ 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 IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
{
@ -182,10 +183,23 @@ namespace Avalonia.Direct2D1
sink.Close();
}
scale = Matrix.Identity;
var (baselineOriginX, baselineOriginY) = glyphRun.BaselineOrigin;
return new StreamGeometryImpl(pathGeometry);
}
var transformedGeometry = new SharpDX.Direct2D1.TransformedGeometry(
Direct2D1Factory,
pathGeometry,
new RawMatrix3x2(1.0f, 0.0f, 0.0f, 1.0f, (float)baselineOriginX, (float)baselineOriginY));
return new TransformedGeometryWrapper(transformedGeometry);
}
private class TransformedGeometryWrapper : GeometryImpl
{
public TransformedGeometryWrapper(SharpDX.Direct2D1.TransformedGeometry geometry) : base(geometry)
{
}
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(string fileName)

14
tests/Avalonia.Base.UnitTests/Media/GeometryGroupTests.cs

@ -14,13 +14,21 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Children_Can_Be_Set_To_Null()
public void Children_Change_Should_Raise_Changed()
{
var target = new GeometryGroup();
target.Children = null;
var children = new GeometryCollection();
Assert.Null(target.Children);
target.Children = children;
var isCalled = false;
target.Changed += (s, e) => isCalled = true;
children.Add(new StreamGeometry());
Assert.True(isCalled);
}
}
}

2
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -121,7 +121,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
throw new NotImplementedException();
}

2
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -117,7 +117,7 @@ namespace Avalonia.Benchmarks
return new NullGlyphRun();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
throw new NotImplementedException();
}

35
tests/Avalonia.RenderTests/Media/GlyphRunTests.cs

@ -23,26 +23,28 @@ namespace Avalonia.Direct2D1.RenderTests.Media
[Fact]
public async Task Should_Render_GlyphRun_Geometry()
{
Decorator target = new Decorator
var control = new GlyphRunGeometryControl
{
Padding = new Thickness(8),
Width = 200,
Height = 100,
Child = new GlyphRunGeometryControl
[TextElement.ForegroundProperty] = new LinearGradientBrush
{
[TextElement.ForegroundProperty] = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops =
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
}
}
};
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 190,
Height = 120,
Child = control
};
await RenderToFile(target);
CompareImages();
@ -50,8 +52,6 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public class GlyphRunGeometryControl : Control
{
private readonly Geometry _geometry;
public GlyphRunGeometryControl()
{
var glyphTypeface = new Typeface(TestFontFamily).GlyphTypeface;
@ -62,19 +62,16 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var glyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices);
_geometry = glyphRun.BuildGeometry();
Geometry = glyphRun.BuildGeometry();
}
protected override Size MeasureOverride(Size availableSize)
{
return _geometry.Bounds.Size;
}
public Geometry Geometry { get; }
public override void Render(DrawingContext context)
{
var foreground = TextElement.GetForeground(this);
context.DrawGeometry(foreground, null, _geometry);
context.DrawGeometry(foreground, null, Geometry);
}
}
}

4
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -122,10 +122,8 @@ namespace Avalonia.UnitTests
return Mock.Of<IGlyphRunImpl>();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun, out Matrix scale)
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
scale = Matrix.Identity;
return Mock.Of<IGeometryImpl>();
}

BIN
tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Geometry.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Loading…
Cancel
Save