diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 4a7a329fc6..477aaec6a8 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -21,6 +21,7 @@
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
"src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj",
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
+ "src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Avalonia.Headless\\Avalonia.Headless.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index 56847bae31..e66b73de0e 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -244,6 +244,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -555,9 +557,14 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -566,10 +573,10 @@ Global
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
- {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
- {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
- {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -635,8 +642,8 @@ Global
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
- {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml
index 5735da19ab..7221fe4657 100644
--- a/azure-pipelines-integrationtests.yml
+++ b/azure-pipelines-integrationtests.yml
@@ -1,11 +1,3 @@
-# Starter pipeline
-# Start with a minimal pipeline that you can customize to build and deploy your code.
-# Add steps that build, run tests, deploy, and more:
-# https://aka.ms/yaml
-
-trigger:
-- master
-
jobs:
- job: Mac
pool:
@@ -25,6 +17,7 @@ jobs:
- script: system_profiler SPDisplaysDataType |grep Resolution
- script: |
+ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
pkill node
appium &
pkill IntegrationTestApp
diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props
index 6f21971d3d..26908b1081 100644
--- a/packages/Avalonia/Avalonia.props
+++ b/packages/Avalonia/Avalonia.props
@@ -6,4 +6,9 @@
false
+
+
+
+
+
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index c223bfe1a9..0e84b3d182 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -29,6 +29,7 @@
+
diff --git a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
index 938c45a4e2..db74be7c08 100644
--- a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
+++ b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
@@ -16,10 +16,10 @@ namespace ControlCatalog.Pages
private Point _cursorPoint;
- public StyledProperty ScaleProperty = AvaloniaProperty.Register(nameof(Scale), 1.0d);
+ public static readonly StyledProperty ScaleProperty = AvaloniaProperty.Register(nameof(Scale), 1.0d);
public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
- public StyledProperty RotationProperty = AvaloniaProperty.Register(nameof(Rotation));
+ public static readonly StyledProperty RotationProperty = AvaloniaProperty.Register(nameof(Rotation));
///
/// Rotation, measured in Radians!
///
@@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
}
}
- public StyledProperty ViewportCenterYProperty = AvaloniaProperty.Register(nameof(ViewportCenterY), 0.0d);
+ public static readonly StyledProperty ViewportCenterYProperty = AvaloniaProperty.Register(nameof(ViewportCenterY), 0.0d);
public double ViewportCenterY { get => GetValue(ViewportCenterYProperty); set => SetValue(ViewportCenterYProperty, value); }
- public StyledProperty ViewportCenterXProperty = AvaloniaProperty.Register(nameof(ViewportCenterX), 0.0d);
+ public static readonly StyledProperty ViewportCenterXProperty = AvaloniaProperty.Register(nameof(ViewportCenterX), 0.0d);
public double ViewportCenterX { get => GetValue(ViewportCenterXProperty); set => SetValue(ViewportCenterXProperty, value); }
private IPen _pen;
diff --git a/samples/ControlCatalog/Pages/PointerCanvas.cs b/samples/ControlCatalog/Pages/PointerCanvas.cs
index da1ff5442d..32e46af9dd 100644
--- a/samples/ControlCatalog/Pages/PointerCanvas.cs
+++ b/samples/ControlCatalog/Pages/PointerCanvas.cs
@@ -93,7 +93,7 @@ public class PointerCanvas : Control
}
private int _threadSleep;
- public static DirectProperty ThreadSleepProperty =
+ public static readonly DirectProperty ThreadSleepProperty =
AvaloniaProperty.RegisterDirect(nameof(ThreadSleep), c => c.ThreadSleep, (c, v) => c.ThreadSleep = v);
public int ThreadSleep
@@ -103,7 +103,7 @@ public class PointerCanvas : Control
}
private bool _drawOnlyPoints;
- public static DirectProperty DrawOnlyPointsProperty =
+ public static readonly DirectProperty DrawOnlyPointsProperty =
AvaloniaProperty.RegisterDirect(nameof(DrawOnlyPoints), c => c.DrawOnlyPoints, (c, v) => c.DrawOnlyPoints = v);
public bool DrawOnlyPoints
@@ -113,8 +113,8 @@ public class PointerCanvas : Control
}
private string? _status;
- public static DirectProperty StatusProperty =
- AvaloniaProperty.RegisterDirect(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v,
+ public static readonly DirectProperty StatusProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Status), c => c.Status, (c, v) => c.Status = v,
defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
public string? Status
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
index 0a761d70ba..1356eeb526 100644
--- a/samples/IntegrationTestApp/IntegrationTestApp.csproj
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -19,6 +19,7 @@
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 58e8aef1e9..090cf23b33 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -25,6 +25,7 @@
WindowState:
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 087f25666b..19eb1d64b0 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -1,19 +1,17 @@
-using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Media;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
using Avalonia.VisualTree;
using Microsoft.CodeAnalysis;
-using Avalonia.Controls.Primitives;
-using Avalonia.Threading;
-using Avalonia.Controls.Primitives.PopupPositioning;
namespace IntegrationTestApp
{
@@ -25,6 +23,10 @@ namespace IntegrationTestApp
InitializeViewMenu();
InitializeGesturesTab();
this.AttachDevTools();
+
+ var overlayPopups = this.Get("AppOverlayPopups");
+ overlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
+
AddHandler(Button.ClickEvent, OnButtonClick);
ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
DataContext = this;
diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs
index c09b249cfa..6603450b85 100644
--- a/samples/IntegrationTestApp/Program.cs
+++ b/samples/IntegrationTestApp/Program.cs
@@ -1,17 +1,31 @@
using System;
+using System.Linq;
using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
namespace IntegrationTestApp
{
class Program
{
+ public static bool OverlayPopups { get; private set; }
+
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
- public static void Main(string[] args) => BuildAvaloniaApp()
- .StartWithClassicDesktopLifetime(args);
+ public static void Main(string[] args)
+ {
+ OverlayPopups = args.Contains("--overlayPopups");
+
+ BuildAvaloniaApp()
+ .With(new Win32PlatformOptions
+ {
+ OverlayPopups = OverlayPopups,
+ })
+ .With(new AvaloniaNativePlatformOptions
+ {
+ OverlayPopups = OverlayPopups,
+ })
+ .StartWithClassicDesktopLifetime(args);
+ }
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 59c66ed505..13ca140565 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using Avalonia.Interactivity;
-using Avalonia.VisualTree;
+using Avalonia.LogicalTree;
namespace Avalonia.Input
{
@@ -190,7 +189,7 @@ namespace Avalonia.Input
// If the menu is open, only match controls in the menu's visual tree.
if (menuIsOpen)
{
- matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsVisualAncestorOf((Visual)x));
+ matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsLogicalAncestorOf((Visual)x));
}
var match = matches.FirstOrDefault();
diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs
index 31a16dc69c..f2106f2f86 100644
--- a/src/Avalonia.Base/Media/DrawingContext.cs
+++ b/src/Avalonia.Base/Media/DrawingContext.cs
@@ -277,7 +277,6 @@ namespace Avalonia.Media
private readonly record struct RestoreState : IDisposable
{
private readonly DrawingContext _context;
- private readonly Matrix _matrix;
private readonly PushedStateType _type;
public enum PushedStateType
diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs
index 812d315912..a41054202e 100644
--- a/src/Avalonia.Base/Media/DrawingGroup.cs
+++ b/src/Avalonia.Base/Media/DrawingGroup.cs
@@ -107,8 +107,6 @@ namespace Avalonia.Media
private readonly DrawingGroup _drawingGroup;
private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService();
- private Matrix _transform;
-
private bool _disposed;
// Root drawing created by this DrawingContext.
diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs
index ac2dce1e42..abfd2e33ac 100644
--- a/src/Avalonia.Base/Media/GeometryDrawing.cs
+++ b/src/Avalonia.Base/Media/GeometryDrawing.cs
@@ -27,8 +27,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty PenProperty =
- AvaloniaProperty.Register(nameof(Pen));
+ public static readonly StyledProperty PenProperty =
+ AvaloniaProperty.Register(nameof(Pen));
///
/// Gets or sets the that describes the shape of this .
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 2966ceee8d..d795cca894 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -151,9 +151,9 @@ namespace Avalonia.Media
}
///
- /// Gets or sets the conservative bounding box of the .
+ /// Gets the conservative bounding box of the .
///
- public Size Size => PlatformImpl.Item.Size;
+ public Rect Bounds => PlatformImpl.Item.Bounds;
///
///
@@ -252,7 +252,7 @@ namespace Avalonia.Media
if (characterIndex > Metrics.LastCluster)
{
- return Size.Width;
+ return Bounds.Width;
}
var glyphIndex = FindGlyphIndex(characterIndex);
@@ -287,7 +287,7 @@ namespace Avalonia.Media
if (characterIndex <= Metrics.FirstCluster)
{
- return Size.Width;
+ return Bounds.Width;
}
for (var i = glyphIndex + 1; i < _glyphInfos.Count; i++)
@@ -295,7 +295,7 @@ namespace Avalonia.Media
distance += _glyphInfos[i].GlyphAdvance;
}
- return Size.Width - distance;
+ return Bounds.Width - distance;
}
}
@@ -321,7 +321,7 @@ namespace Avalonia.Media
}
//After
- if (distance >= Size.Width)
+ if (distance >= Bounds.Width)
{
isInside = false;
@@ -354,7 +354,7 @@ namespace Avalonia.Media
}
else
{
- currentX = Size.Width;
+ currentX = Bounds.Width;
for (var index = _glyphInfos.Count - 1; index >= 0; index--)
{
diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs
index 06d92fd81c..961203e30e 100644
--- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs
+++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs
@@ -32,7 +32,7 @@
public override Rect GetBounds()
{
- return GlyphRun != null ? new Rect(GlyphRun.Size) : default;
+ return GlyphRun != null ? GlyphRun.Bounds : default;
}
}
}
diff --git a/src/Avalonia.Base/Media/PolyLineSegment.cs b/src/Avalonia.Base/Media/PolyLineSegment.cs
index 55bfb33041..5c48c11e19 100644
--- a/src/Avalonia.Base/Media/PolyLineSegment.cs
+++ b/src/Avalonia.Base/Media/PolyLineSegment.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Avalonia.Collections;
namespace Avalonia.Media
{
@@ -20,7 +19,7 @@ namespace Avalonia.Media
///
/// The points.
///
- public AvaloniaList Points
+ public Points Points
{
get => GetValue(PointsProperty);
set => SetValue(PointsProperty, value);
diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs
index b74b7df9c5..e89a7d8826 100644
--- a/src/Avalonia.Base/Media/TextDecoration.cs
+++ b/src/Avalonia.Base/Media/TextDecoration.cs
@@ -223,7 +223,7 @@ namespace Avalonia.Media
if (intersections.Count > 0)
{
var last = baselineOrigin.X;
- var finalPos = last + glyphRun.Size.Width;
+ var finalPos = last + glyphRun.Bounds.Width;
var end = last;
var points = new List();
diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
index c1b9b77401..75249ff7e7 100644
--- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
@@ -89,13 +89,13 @@ namespace Avalonia.Media.TextFormatting
var offset = Math.Max(0, currentPosition - glyphRun.Metrics.FirstCluster);
var glyphIndex = glyphRun.FindGlyphIndex(characterIndex - offset);
- var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex];
+ var glyphInfo = shapedBuffer[glyphIndex];
- shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex,
+ shapedBuffer[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex,
glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
}
- glyphRun.GlyphInfos = shapedBuffer.GlyphInfos;
+ glyphRun.GlyphInfos = shapedBuffer;
}
currentPosition += textRun.Length;
diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
index f29bdd4459..3f26d081b0 100644
--- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
@@ -2,6 +2,7 @@
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
@@ -9,12 +10,13 @@ namespace Avalonia.Media.TextFormatting
public sealed class ShapedBuffer : IReadOnlyList, IDisposable
{
private GlyphInfo[]? _rentedBuffer;
+ private ArraySlice _glyphInfos;
public ShapedBuffer(ReadOnlyMemory text, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
- _rentedBuffer = ArrayPool.Shared.Rent(bufferLength);
Text = text;
- GlyphInfos = new ArraySlice(_rentedBuffer, 0, bufferLength);
+ _rentedBuffer = ArrayPool.Shared.Rent(bufferLength);
+ _glyphInfos = new ArraySlice(_rentedBuffer, 0, bufferLength);
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BidiLevel = bidiLevel;
@@ -23,27 +25,70 @@ namespace Avalonia.Media.TextFormatting
internal ShapedBuffer(ReadOnlyMemory text, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
Text = text;
- GlyphInfos = glyphInfos;
+ _glyphInfos = glyphInfos;
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BidiLevel = bidiLevel;
}
- internal ArraySlice GlyphInfos { get; private set; }
-
- public int Length
- => GlyphInfos.Length;
+ ///
+ /// The buffer's length.
+ ///
+ public int Length => _glyphInfos.Length;
+ ///
+ /// The buffer's glyph typeface.
+ ///
public IGlyphTypeface GlyphTypeface { get; }
+ ///
+ /// The buffers font rendering em size.
+ ///
public double FontRenderingEmSize { get; }
+ ///
+ /// The buffer's bidi level.
+ ///
public sbyte BidiLevel { get; }
+ ///
+ /// The buffer's reading direction.
+ ///
public bool IsLeftToRight => (BidiLevel & 1) == 0;
+ ///
+ /// The text that is represended by this buffer.
+ ///
public ReadOnlyMemory Text { get; }
+ ///
+ /// Reverses the buffer.
+ ///
+ public void Reverse()
+ {
+ _glyphInfos.Span.Reverse();
+ }
+
+ public void Dispose()
+ {
+ if (_rentedBuffer is not null)
+ {
+ ArrayPool.Shared.Return(_rentedBuffer);
+ _rentedBuffer = null;
+ _glyphInfos = ArraySlice.Empty; // ensure we don't misuse the returned array
+ }
+ }
+
+ public GlyphInfo this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _glyphInfos[index];
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => _glyphInfos[index] = value;
+ }
+
+ public IEnumerator GetEnumerator() => _glyphInfos.GetEnumerator();
+
///
/// Finds a glyph index for given character index.
///
@@ -53,20 +98,19 @@ namespace Avalonia.Media.TextFormatting
///
private int FindGlyphIndex(int characterIndex)
{
- if (characterIndex < GlyphInfos[0].GlyphCluster)
+ if (characterIndex < _glyphInfos[0].GlyphCluster)
{
return 0;
}
- if (characterIndex > GlyphInfos[GlyphInfos.Length - 1].GlyphCluster)
+ if (characterIndex > _glyphInfos[_glyphInfos.Length - 1].GlyphCluster)
{
- return GlyphInfos.Length - 1;
+ return _glyphInfos.Length - 1;
}
-
var comparer = GlyphInfo.ClusterAscendingComparer;
- var glyphInfos = GlyphInfos.Span;
+ var glyphInfos = _glyphInfos.Span;
var searchValue = new GlyphInfo(default, characterIndex, default);
@@ -109,42 +153,24 @@ namespace Avalonia.Media.TextFormatting
return new SplitResult(this, null);
}
- var firstCluster = GlyphInfos[0].GlyphCluster;
- var lastCluster = GlyphInfos[GlyphInfos.Length - 1].GlyphCluster;
+ var firstCluster = _glyphInfos[0].GlyphCluster;
+ var lastCluster = _glyphInfos[_glyphInfos.Length - 1].GlyphCluster;
var start = firstCluster < lastCluster ? firstCluster : lastCluster;
var glyphCount = FindGlyphIndex(start + length);
var first = new ShapedBuffer(Text.Slice(0, length),
- GlyphInfos.Take(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
+ _glyphInfos.Take(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
var second = new ShapedBuffer(Text.Slice(length),
- GlyphInfos.Skip(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
+ _glyphInfos.Skip(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
return new SplitResult(first, second);
}
- int IReadOnlyCollection.Count => GlyphInfos.Length;
-
- public GlyphInfo this[int index]
- {
- get => GlyphInfos[index];
- set => GlyphInfos[index] = value;
- }
-
- public IEnumerator GetEnumerator() => GlyphInfos.GetEnumerator();
+ int IReadOnlyCollection.Count => _glyphInfos.Length;
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- public void Dispose()
- {
- if (_rentedBuffer is not null)
- {
- ArrayPool.Shared.Return(_rentedBuffer);
- _rentedBuffer = null;
- GlyphInfos = ArraySlice.Empty; // ensure we don't misuse the returned array
- }
- }
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
index 568148e15c..2f28c3f954 100644
--- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
@@ -38,14 +38,14 @@ namespace Avalonia.Media.TextFormatting
public override double Baseline => -TextMetrics.Ascent;
- public override Size Size => GlyphRun.Size;
+ public override Size Size => GlyphRun.Bounds.Size;
public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
///
public override void Draw(DrawingContext drawingContext, Point origin)
{
- using (drawingContext.PushPreTransform(Matrix.CreateTranslation(origin)))
+ using (drawingContext.PushTransform(Matrix.CreateTranslation(origin)))
{
if (GlyphRun.GlyphInfos.Count == 0)
{
@@ -85,7 +85,7 @@ namespace Avalonia.Media.TextFormatting
{
_glyphRun = null;
- ShapedBuffer.GlyphInfos.Span.Reverse();
+ ShapedBuffer.Reverse();
IsReversed = !IsReversed;
}
@@ -106,7 +106,7 @@ namespace Avalonia.Media.TextFormatting
for (var i = 0; i < ShapedBuffer.Length; i++)
{
- var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance;
+ var advance = ShapedBuffer[i].GlyphAdvance;
if (currentWidth + advance > availableWidth)
{
@@ -130,7 +130,7 @@ namespace Avalonia.Media.TextFormatting
for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
{
- var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance;
+ var advance = ShapedBuffer[i].GlyphAdvance;
if (width + advance > availableWidth)
{
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
index 6422f23dcd..8b6d576c6e 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Media.TextFormatting
var collapsedLength = 0;
var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
- if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
+ if (properties.Width < shapedSymbol.GlyphRun.Bounds.Width)
{
//Not enough space to fit in the symbol
return Array.Empty();
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
index 7f74f49982..12efb3c383 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
@@ -583,7 +583,7 @@ namespace Avalonia.Media.TextFormatting
{
if (shapedTextCharacters.ShapedBuffer.Length > 0)
{
- var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphInfos[0].GlyphCluster;
+ var firstCluster = shapedTextCharacters.ShapedBuffer[0].GlyphCluster;
var lastCluster = firstCluster;
for (var j = 0; j < shapedTextCharacters.ShapedBuffer.Length; j++)
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
index a21a5d45e9..41d451b9e3 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
@@ -60,7 +60,7 @@ namespace Avalonia.Media.TextFormatting
var currentWidth = 0.0;
var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
- if (Width < shapedSymbol.GlyphRun.Size.Width)
+ if (Width < shapedSymbol.GlyphRun.Bounds.Width)
{
return Array.Empty();
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index b3321d4d9f..f426a20b2c 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -423,7 +423,7 @@ namespace Avalonia.Media.TextFormatting
{
if (currentGlyphRun != null)
{
- currentDistance -= currentGlyphRun.Size.Width;
+ currentDistance -= currentGlyphRun.Bounds.Width;
}
return currentDistance + distance;
@@ -477,7 +477,7 @@ namespace Avalonia.Media.TextFormatting
{
if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
{
- distance = currentGlyphRun.Size.Width;
+ distance = currentGlyphRun.Bounds.Width;
}
return true;
@@ -1483,7 +1483,7 @@ namespace Avalonia.Media.TextFormatting
trailingWhitespaceLength += glyphRunMetrics.TrailingWhitespaceLength;
- var whitespaceWidth = glyphRun.Size.Width - glyphRunMetrics.Width;
+ var whitespaceWidth = glyphRun.Bounds.Width - glyphRunMetrics.Width;
width -= whitespaceWidth;
}
diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs
index 659cfb75df..7df446e854 100644
--- a/src/Avalonia.Base/Platform/AssetLoader.cs
+++ b/src/Avalonia.Base/Platform/AssetLoader.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -62,7 +63,7 @@ namespace Avalonia.Platform
/// True if the asset could be found; otherwise false.
public bool Exists(Uri uri, Uri? baseUri = null)
{
- return GetAsset(uri, baseUri) != null;
+ return TryGetAsset(uri, baseUri, out _);
}
///
@@ -94,21 +95,27 @@ namespace Avalonia.Platform
///
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{
- var asset = GetAsset(uri, baseUri);
-
- if (asset == null)
+ if (TryGetAsset(uri, baseUri, out var assetDescriptor))
{
- throw new FileNotFoundException($"The resource {uri} could not be found.");
+ return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
}
- return (asset.GetStream(), asset.Assembly);
+ throw new FileNotFoundException($"The resource {uri} could not be found.");
}
public Assembly? GetAssembly(Uri uri, Uri? baseUri)
{
if (!uri.IsAbsoluteUri && baseUri != null)
+ {
uri = new Uri(baseUri, uri);
- return GetAssembly(uri)?.Assembly;
+ }
+
+ if (TryGetAssembly(uri, out var assemblyDescriptor))
+ {
+ return assemblyDescriptor.Assembly;
+ }
+
+ return null;
}
///
@@ -121,99 +128,145 @@ namespace Avalonia.Platform
{
if (uri.IsAbsoluteResm())
{
- var assembly = GetAssembly(uri);
+ if (!TryGetAssembly(uri, out var assembly))
+ {
+ assembly = _defaultResmAssembly;
+ }
return assembly?.Resources?
- .Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0)
- .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
- Enumerable.Empty();
+ .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
+ .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
+ Enumerable.Empty();
}
uri = uri.EnsureAbsolute(baseUri);
+
if (uri.IsAvares())
{
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm == null)
+ if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
{
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
+ return Enumerable.Empty();
}
- if (asm.AvaloniaResources == null)
+ if (assembly?.AvaloniaResources == null)
+ {
return Enumerable.Empty();
+ }
- if (path[path.Length - 1] != '/')
+ if (path.Length > 0 && path[path.Length - 1] != '/')
+ {
path += '/';
+ }
- return asm.AvaloniaResources
+ return assembly.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
- .Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
+ .Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
}
return Enumerable.Empty();
}
-
- private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri)
- {
+
+ private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
+ {
+ assetDescriptor = null;
+
if (uri.IsAbsoluteResm())
{
- var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
-
- if (asm == null)
+ if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
{
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
+ assembly = _defaultResmAssembly;
}
- var resourceKey = uri.AbsolutePath;
- IAssetDescriptor? rv = null;
- asm.Resources?.TryGetValue(resourceKey, out rv);
- return rv;
+ if (assembly?.Resources != null)
+ {
+ var resourceKey = uri.AbsolutePath;
+
+ if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
+ {
+ return true;
+ }
+ }
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm.AvaloniaResources == null)
- return null;
- asm.AvaloniaResources.TryGetValue(path, out var desc);
- return desc;
+ if (TryGetResAsmAndPath(uri, out var assembly, out var path))
+ {
+ if (assembly.AvaloniaResources == null)
+ {
+ return false;
+ }
+
+ if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
+ {
+ return true;
+ }
+ }
}
- throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
+ return false;
}
- private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
+ private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
{
- var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority);
- return (asm, uri.GetUnescapeAbsolutePath());
+ path = uri.GetUnescapeAbsolutePath();
+
+ if (TryLoadAssembly(uri.Authority, out assembly))
+ {
+ return true;
+ }
+
+ return false;
}
-
- private static IAssemblyDescriptor? GetAssembly(Uri? uri)
+
+ private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
+ assembly = null;
+
if (uri != null)
{
if (!uri.IsAbsoluteUri)
- return null;
- if (uri.IsAvares())
- return GetResAsmAndPath(uri).asm;
+ {
+ return false;
+ }
+
+ if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
+ {
+ return true;
+ }
if (uri.IsResm())
{
var assemblyName = uri.GetAssemblyNameFromQuery();
- if (assemblyName.Length > 0)
- return s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+ if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
+ {
+ return true;
+ }
}
}
- return null;
+ return false;
+ }
+
+ private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
+ {
+ assembly = null;
+
+ try
+ {
+ assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+ return true;
+ }
+ catch (Exception) { }
+
+ return false;
}
#endif
-
+
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
diff --git a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs
index 46b065b04e..fccea27c43 100644
--- a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs
+++ b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs
@@ -10,11 +10,10 @@ namespace Avalonia.Platform
[Unstable]
public interface IGlyphRunImpl : IDisposable
{
-
///
/// Gets the conservative bounding box of the glyph run./>.
///
- Size Size { get; }
+ Rect Bounds { get; }
///
/// Gets the baseline origin of the glyph run./>.
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index 0887f11ec9..53cd3ff307 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -924,7 +924,7 @@ namespace Avalonia.PropertyStore
{
_effectiveValues.GetKeyValue(i, out var key, out var e);
- if (e.Priority == BindingPriority.Unset)
+ if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue)
{
RemoveEffectiveValue(key, i);
e.DisposeAndRaiseUnset(this, key);
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index 01299e4ffa..df3a70b3e6 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -225,7 +225,7 @@ public class CompositingRenderer : IRendererWithCompositor
sortedChildren.Dispose();
}
else
- foreach (var ch in v.GetVisualChildren())
+ foreach (var ch in visualChildren)
{
var compositionChild = ch.CompositionVisual;
if (compositionChild != null)
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs b/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
index b01fb46aa3..04e40e8744 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition.Server
for (var c = FirstChar; c <= LastChar; c++)
{
- var height = _runs[c - FirstChar].Size.Height;
+ var height = _runs[c - FirstChar].Bounds.Height;
if (height > maxHeight)
{
maxHeight = height;
@@ -51,8 +51,8 @@ namespace Avalonia.Rendering.Composition.Server
{
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
var run = _runs[effectiveChar - FirstChar];
- width += run.Size.Width;
- height = Math.Max(height, run.Size.Height);
+ width += run.Bounds.Width;
+ height = Math.Max(height, run.Bounds.Height);
}
return new Size(width, height);
@@ -69,7 +69,7 @@ namespace Avalonia.Rendering.Composition.Server
var run = _runs[effectiveChar - FirstChar];
context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
context.DrawGlyphRun(foreground, run.PlatformImpl);
- offset += run.Size.Width;
+ offset += run.Bounds.Width;
}
context.Transform = originalTransform;
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
index 4d8759f545..381c63f430 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
@@ -16,12 +16,11 @@ namespace Avalonia.Rendering.SceneGraph
/// The transform.
/// The foreground brush.
/// The glyph run to draw.
- /// Auxiliary data required to draw the brush.
public GlyphRunNode(
Matrix transform,
IImmutableBrush foreground,
IRef glyphRun)
- : base(new Rect(glyphRun.Item.Size), transform, foreground)
+ : base(glyphRun.Item.Bounds, transform, foreground)
{
GlyphRun = glyphRun.Clone();
}
@@ -54,7 +53,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public override bool HitTest(Point p) => new Rect(GlyphRun.Item.Size).ContainsExclusive(p);
+ public override bool HitTest(Point p) => GlyphRun.Item.Bounds.ContainsExclusive(p);
public override void Dispose()
{
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index 82e948eea8..cbdf3c3c1e 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -803,8 +803,11 @@ namespace Avalonia
if (theme.HasChildren)
{
- foreach (var child in theme.Children)
- ApplyStyle(child, null, type);
+ var children = theme.Children;
+ for (var i = 0; i < children.Count; i++)
+ {
+ ApplyStyle(children[i], null, type);
+ }
}
}
@@ -816,8 +819,11 @@ namespace Avalonia
if (host.IsStylesInitialized)
{
- foreach (var style in host.Styles)
- ApplyStyle(style, host, FrameType.Style);
+ var styles = host.Styles;
+ for (var i = 0; i < styles.Count; ++i)
+ {
+ ApplyStyle(styles[i], host, FrameType.Style);
+ }
}
}
@@ -826,8 +832,11 @@ namespace Avalonia
if (style is Style s)
s.TryAttach(this, host, type);
- foreach (var child in style.Children)
- ApplyStyle(child, host, type);
+ var children = style.Children;
+ for (var i = 0; i < children.Count; i++)
+ {
+ ApplyStyle(children[i], host, type);
+ }
}
private void ReevaluateImplicitTheme()
diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs
index c5ef9a0b2b..3b0aa03492 100644
--- a/src/Avalonia.Base/Styling/OrSelector.cs
+++ b/src/Avalonia.Base/Styling/OrSelector.cs
@@ -71,9 +71,9 @@ namespace Avalonia.Styling
var activators = new OrActivatorBuilder();
var neverThisInstance = false;
- foreach (var selector in _selectors)
+ for (var i = 0; i < _selectors.Count; i++)
{
- var match = selector.Match(control, parent, subscribe);
+ var match = _selectors[i].Match(control, parent, subscribe);
switch (match.Result)
{
@@ -108,16 +108,19 @@ namespace Avalonia.Styling
internal override void ValidateNestingSelector(bool inControlTheme)
{
- foreach (var selector in _selectors)
- selector.ValidateNestingSelector(inControlTheme);
+ for (var i = 0; i < _selectors.Count; i++)
+ {
+ _selectors[i].ValidateNestingSelector(inControlTheme);
+ }
}
private Type? EvaluateTargetType()
{
Type? result = null;
- foreach (var selector in _selectors)
+ for (var i = 0; i < _selectors.Count; i++)
{
+ var selector = _selectors[i];
if (selector.TargetType == null)
{
return null;
diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
index 7883fd23ab..f8670cfdb3 100644
--- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
+++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Avalonia.Controls;
using Avalonia.Styling.Activators;
using Avalonia.Utilities;
@@ -15,7 +14,7 @@ namespace Avalonia.Styling
internal class TypeNameAndClassSelector : Selector
{
private readonly Selector? _previous;
- private readonly Lazy> _classes = new Lazy>(() => new List());
+ private List? _classes;
private Type? _targetType;
private string? _selectorString;
@@ -81,7 +80,7 @@ namespace Avalonia.Styling
///
/// The style classes which the selector matches.
///
- public IList Classes => _classes.Value;
+ public IList Classes => _classes ??= new();
///
public override string ToString(Style? owner)
@@ -122,16 +121,16 @@ namespace Avalonia.Styling
return SelectorMatch.NeverThisInstance;
}
- if (_classes.IsValueCreated && _classes.Value.Count > 0)
+ if (_classes is { Count: > 0 })
{
if (subscribe)
{
- var observable = new StyleClassActivator((Classes)control.Classes, _classes.Value);
+ var observable = new StyleClassActivator(control.Classes, _classes);
return new SelectorMatch(observable);
}
- if (!StyleClassActivator.AreClassesMatching(control.Classes, Classes))
+ if (!StyleClassActivator.AreClassesMatching(control.Classes, _classes))
{
return SelectorMatch.NeverThisInstance;
}
@@ -172,9 +171,9 @@ namespace Avalonia.Styling
builder.Append(Name);
}
- if (_classes.IsValueCreated && _classes.Value.Count > 0)
+ if (_classes is { Count: > 0 })
{
- foreach (var c in Classes)
+ foreach (var c in _classes)
{
if (!c.StartsWith(":"))
{
diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
index 5cac2ef658..0589abb2dd 100644
--- a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
+++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
@@ -304,13 +304,9 @@ namespace Avalonia.Utilities
{
if (_entryCount == _entries!.Length)
{
- const double growthFactor = 1.2;
- var newSize = (int)(_entryCount * growthFactor);
-
- if (newSize == _entryCount)
- {
- newSize++;
- }
+ var newSize = _entryCount == DefaultInitialCapacity ?
+ DefaultInitialCapacity * 2 :
+ (int)(_entryCount * 1.5);
var destEntries = new Entry[newSize];
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 8b0cc06136..df0c5b100f 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -732,6 +732,23 @@ namespace Avalonia
}
}
+ internal override void OnTemplatedParentControlThemeChanged()
+ {
+ base.OnTemplatedParentControlThemeChanged();
+
+ var count = VisualChildren.Count;
+ var templatedParent = TemplatedParent;
+
+ for (var i = 0; i < count; ++i)
+ {
+ if (VisualChildren[i] is StyledElement child &&
+ child.TemplatedParent == templatedParent)
+ {
+ child.OnTemplatedParentControlThemeChanged();
+ }
+ }
+ }
+
///
/// Computes the value according to the
/// and
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
index 76e44e4daf..464a12770e 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
@@ -202,7 +202,7 @@
@@ -129,7 +129,7 @@
-
+
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf
new file mode 100644
index 0000000000..c74cc0c6c1
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf
new file mode 100644
index 0000000000..6dc068dc90
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf
new file mode 100644
index 0000000000..2a83ae168d
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf
new file mode 100644
index 0000000000..ca7bfcd434
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf
new file mode 100644
index 0000000000..84e6a61c3c
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf
new file mode 100644
index 0000000000..daf4c4413f
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf b/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf
new file mode 100644
index 0000000000..22592e045c
Binary files /dev/null and b/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf differ
diff --git a/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj b/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj
new file mode 100644
index 0000000000..c81a13558c
--- /dev/null
+++ b/src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj
@@ -0,0 +1,13 @@
+
+
+ net6.0;netstandard2.0
+ Avalonia.Fonts.Inter
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs b/src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..53047b1f70
--- /dev/null
+++ b/src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using Avalonia.Metadata;
+
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Fonts.Inter")]
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 31aaebcdc7..f8100d3832 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -118,7 +118,7 @@ namespace Avalonia.Headless
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
- return new HeadlessGeometryStub(new Rect(glyphRun.Size));
+ return new HeadlessGeometryStub(glyphRun.Bounds);
}
public IGlyphRunImpl CreateGlyphRun(
@@ -132,7 +132,7 @@ namespace Avalonia.Headless
class HeadlessGlyphRunStub : IGlyphRunImpl
{
- public Size Size => new Size(8, 12);
+ public Rect Bounds => new Rect(new Size(8, 12));
public Point BaselineOrigin => new Point(0, 8);
diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
index 7512fa4cfa..82e48851b5 100644
--- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
@@ -3,7 +3,7 @@
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
- avares://Avalonia.Themes.Fluent/Assets#Inter
+ avares://Avalonia.Fonts.Inter/Assets#Inter, $Default
14
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
deleted file mode 100644
index a372c5fcca..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
deleted file mode 100644
index 13ef261fff..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
deleted file mode 100644
index 9f75ff2780..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
deleted file mode 100644
index 2c5e25453c..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
deleted file mode 100644
index c28fe4d744..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
deleted file mode 100644
index d085eb4994..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
deleted file mode 100644
index 016a13482b..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
index fb52b3b147..8d266ce82f 100644
--- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
+++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
@@ -9,8 +9,7 @@
-
-
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
index 67e70d154f..6a2651e3f5 100644
--- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
@@ -4,6 +4,7 @@
+
diff --git a/src/Avalonia.Themes.Simple/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml
index 0c1354e475..0a06927034 100644
--- a/src/Avalonia.Themes.Simple/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/Base.xaml
@@ -17,6 +17,7 @@
#FF808080
#FF000000
#FF086F9E
+ #FF096085
@@ -33,6 +34,8 @@
+
+
@@ -51,6 +54,7 @@
#FF505050
#FFDEDEDE
#FF119EDA
+ #FF096085
@@ -66,11 +70,13 @@
+
+ avares://Avalonia.Fonts.Inter/Assets#Inter, $Default
#CC119EDA
#99119EDA
#66119EDA
diff --git a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
index 1f4e48e4a8..8b751187d6 100644
--- a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
@@ -3,6 +3,7 @@
+
diff --git a/src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml b/src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
index da8ec03cd4..ef8c6625bd 100644
--- a/src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
@@ -30,7 +30,6 @@
Background="Transparent">
diff --git a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
index 3d52464858..3ea1515cc3 100644
--- a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
@@ -5,6 +5,7 @@
+
diff --git a/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
index d1f5585c02..4c7864473b 100644
--- a/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
@@ -4,9 +4,7 @@
TargetType="OverlayPopupHost">
-
-
-
+
diff --git a/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
index a5593e3a68..33c8d0764e 100644
--- a/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
@@ -6,9 +6,7 @@
-
-
-
+
diff --git a/src/Avalonia.Themes.Simple/Controls/Window.xaml b/src/Avalonia.Themes.Simple/Controls/Window.xaml
index 00b108343e..0a0363c2d0 100644
--- a/src/Avalonia.Themes.Simple/Controls/Window.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/Window.xaml
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
index 6150265cf8..736e764aa7 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
@@ -146,13 +146,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
- var ancestorTypeName = relativeSourceObject.Children
+ var ancestorType = relativeSourceObject.Children
.OfType()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
- ?.Values[0] as XamlAstTextNode;
+ ?.Values[0] switch
+ {
+ XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(
+ context,
+ textNode.Text,
+ false,
+ textNode,
+ true).GetClrType(),
+ XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
+ null => null,
+ _ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
+ };
- IXamlType ancestorType = null;
- if (ancestorTypeName is null)
+ if (ancestorType is null)
{
if (treeType == "Visual")
{
@@ -174,15 +184,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
}
- else
- {
- ancestorType = TypeReferenceResolver.ResolveType(
- context,
- ancestorTypeName.Text,
- false,
- ancestorTypeName,
- true).GetClrType();
- }
if (treeType == "Visual")
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
index 2b62d33349..f196231a6b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data.Core;
@@ -13,13 +12,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public class CompiledBindingPath
{
- private readonly List _elements = new List();
+ private readonly ICompiledBindingPathElement[] _elements;
- public CompiledBindingPath() { }
+ public CompiledBindingPath()
+ => _elements = Array.Empty();
- internal CompiledBindingPath(IEnumerable bindingPath, object rawSource)
+ internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource)
{
- _elements = new List(bindingPath);
+ _elements = elements;
RawSource = rawSource;
}
@@ -78,13 +78,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal IEnumerable Elements => _elements;
- internal SourceMode SourceMode => _elements.OfType().Any()
+ internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
? SourceMode.Control : SourceMode.Data;
internal object RawSource { get; }
public override string ToString()
- => string.Concat(_elements);
+ => string.Concat((IEnumerable) _elements);
}
public class CompiledBindingPathBuilder
@@ -169,7 +169,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
- public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource);
+ public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray(), _rawSource);
}
public interface ICompiledBindingPathElement
diff --git a/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs b/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
index 170cc9d420..1a4f467f40 100644
--- a/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
@@ -8,11 +8,25 @@ namespace Avalonia.Skia
///
internal class CombinedGeometryImpl : GeometryImpl
{
- public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+ public CombinedGeometryImpl(SKPath? stroke, SKPath? fill)
{
- var path1 = (g1.PlatformImpl as GeometryImpl)?.EffectivePath;
- var path2 = (g2.PlatformImpl as GeometryImpl)?.EffectivePath;
+ StrokePath = stroke;
+ FillPath = fill;
+ Bounds = (stroke ?? fill)?.TightBounds.ToAvaloniaRect() ?? default;
+ }
+
+ public static CombinedGeometryImpl ForceCreate(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+ {
+ if (g1.PlatformImpl is GeometryImpl i1
+ && g2.PlatformImpl is GeometryImpl i2
+ && TryCreate(combineMode, i1, i2) is { } result)
+ return result;
+
+ return new(null, null);
+ }
+ public static CombinedGeometryImpl? TryCreate(GeometryCombineMode combineMode, GeometryImpl g1, GeometryImpl g2)
+ {
var op = combineMode switch
{
GeometryCombineMode.Intersect => SKPathOp.Intersect,
@@ -21,13 +35,28 @@ namespace Avalonia.Skia
_ => SKPathOp.Union
};
- var path = path1?.Op(path2, op);
+ var stroke =
+ g1.StrokePath != null && g2.StrokePath != null
+ ? g1.StrokePath.Op(g2.StrokePath, op)
+ : null;
+
+ SKPath? fill = null;
+ if (g1.FillPath != null && g2.FillPath != null)
+ {
+ // Reuse stroke if fill paths are the same
+ if (ReferenceEquals(g1.FillPath, g1.StrokePath) && ReferenceEquals(g2.FillPath, g2.StrokePath))
+ fill = stroke;
+ else
+ fill = g1.FillPath.Op(g2.FillPath, op);
+ }
- EffectivePath = path;
- Bounds = path?.Bounds.ToAvaloniaRect() ?? default;
+ if (stroke == null && fill == null)
+ return null;
+ return new CombinedGeometryImpl(stroke, fill);
}
public override Rect Bounds { get; }
- public override SKPath? EffectivePath { get; }
+ public override SKPath? StrokePath { get; }
+ public override SKPath? FillPath { get; }
}
}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index db7b068543..e3e2f664c3 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -230,20 +230,21 @@ namespace Avalonia.Skia
var impl = (GeometryImpl) geometry;
var size = geometry.Bounds.Size;
- if (brush is not null)
+ if (brush is not null && impl.FillPath != null)
{
using (var fill = CreatePaint(_fillPaint, brush, size))
{
- Canvas.DrawPath(impl.EffectivePath, fill.Paint);
+ Canvas.DrawPath(impl.FillPath, fill.Paint);
}
}
if (pen is not null
+ && impl.StrokePath != null
&& TryCreatePaint(_strokePaint, pen, size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
{
using (stroke)
{
- Canvas.DrawPath(impl.EffectivePath, stroke.Paint);
+ Canvas.DrawPath(impl.StrokePath, stroke.Paint);
}
}
}
@@ -515,7 +516,7 @@ namespace Avalonia.Skia
return;
}
- using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
+ using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Bounds.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
@@ -639,7 +640,7 @@ namespace Avalonia.Skia
{
CheckLease();
Canvas.Save();
- Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true);
+ Canvas.ClipPath(((GeometryImpl)clip).FillPath, SKClipOperation.Intersect, true);
}
///
diff --git a/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs b/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
index ac05691c67..64976d4eea 100644
--- a/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
@@ -8,14 +8,15 @@ namespace Avalonia.Skia
internal class EllipseGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
- public override SKPath EffectivePath { get; }
+ public override SKPath StrokePath { get; }
+ public override SKPath FillPath => StrokePath;
public EllipseGeometryImpl(Rect rect)
{
var path = new SKPath();
path.AddOval(rect.ToSKRect());
- EffectivePath = path;
+ StrokePath = path;
Bounds = rect;
}
}
diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
index d53dcd2df3..90a2f9169b 100644
--- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs
+++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs
@@ -99,7 +99,20 @@ namespace Avalonia.Skia
{
SKTypeface? skTypeface = null;
- if (typeface.FontFamily.Key is null)
+ if(typeface.FontFamily.Key is not null)
+ {
+ var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
+
+ skTypeface = fontCollection.Get(typeface);
+
+ if (skTypeface is null && !typeface.FontFamily.FamilyNames.HasFallbacks)
+ {
+ throw new InvalidOperationException(
+ $"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
+ }
+ }
+
+ if (skTypeface is null)
{
var defaultName = SKTypeface.Default.FamilyName;
@@ -128,13 +141,7 @@ namespace Avalonia.Skia
skTypeface ??= _skFontManager.MatchTypeface(SKTypeface.Default, fontStyle)
?? SKTypeface.Default;
}
- else
- {
- var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
-
- skTypeface = fontCollection.Get(typeface);
- }
-
+
if (skTypeface == null)
{
throw new InvalidOperationException(
diff --git a/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
index 2828f9a9c1..01be42bad0 100644
--- a/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
@@ -11,26 +11,51 @@ namespace Avalonia.Skia
{
public GeometryGroupImpl(FillRule fillRule, IReadOnlyList children)
{
- var path = new SKPath
+ var fillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd;
+ var count = children.Count;
+
+ var stroke = new SKPath
{
- FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd,
+ FillType = fillType
};
-
- var count = children.Count;
+ bool requiresFillPass = false;
for (var i = 0; i < count; ++i)
{
- if (children[i].PlatformImpl is GeometryImpl { EffectivePath: { } effectivePath })
+ if (children[i].PlatformImpl is GeometryImpl geo)
{
- path.AddPath(effectivePath);
+ if (geo.StrokePath != null)
+ stroke.AddPath(geo.StrokePath);
+ if (!ReferenceEquals(geo.StrokePath, geo.FillPath))
+ requiresFillPass = true;
}
}
+
+ StrokePath = stroke;
+
+ if (requiresFillPass)
+ {
+ var fill = new SKPath
+ {
+ FillType = fillType
+ };
+
+ for (var i = 0; i < count; ++i)
+ {
+ if (children[i].PlatformImpl is GeometryImpl { FillPath: { } fillPath })
+ fill.AddPath(fillPath);
+ }
+
+ FillPath = fill;
+ }
+ else
+ FillPath = stroke;
- EffectivePath = path;
- Bounds = path.Bounds.ToAvaloniaRect();
+ Bounds = stroke.TightBounds.ToAvaloniaRect();
}
public override Rect Bounds { get; }
- public override SKPath EffectivePath { get; }
+ public override SKPath StrokePath { get; }
+ public override SKPath FillPath { get; }
}
}
diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs
index 34270c2078..aee84d1346 100644
--- a/src/Skia/Avalonia.Skia/GeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Skia
private PathCache _pathCache;
private SKPathMeasure? _cachedPathMeasure;
- private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(EffectivePath!);
+ private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(StrokePath!);
///
public abstract Rect Bounds { get; }
@@ -24,19 +24,20 @@ namespace Avalonia.Skia
{
get
{
- if (EffectivePath is null)
+ if (StrokePath is null)
return 0;
return CachedPathMeasure.Length;
}
}
- public abstract SKPath? EffectivePath { get; }
+ public abstract SKPath? StrokePath { get; }
+ public abstract SKPath? FillPath { get; }
///
public bool FillContains(Point point)
{
- return PathContainsCore(EffectivePath, point);
+ return PathContainsCore(FillPath, point);
}
///
@@ -74,7 +75,7 @@ namespace Avalonia.Skia
var paint = SKPaintCache.Shared.Get();
paint.IsStroke = true;
paint.StrokeWidth = strokeWidth;
- paint.GetFillPath(EffectivePath, strokePath);
+ paint.GetFillPath(StrokePath, strokePath);
SKPaintCache.Shared.ReturnReset(paint);
@@ -96,14 +97,10 @@ namespace Avalonia.Skia
///
public IGeometryImpl? Intersect(IGeometryImpl geometry)
{
- if (EffectivePath is { } path
- && (geometry as GeometryImpl)?.EffectivePath is { } otherPath
- && path.Op(otherPath, SKPathOp.Intersect) is { } result)
- {
- return new StreamGeometryImpl(result);
- }
-
- return null;
+ var other = geometry as GeometryImpl;
+ if (other == null)
+ return null;
+ return CombinedGeometryImpl.TryCreate(GeometryCombineMode.Intersect, this, other);
}
///
@@ -128,7 +125,7 @@ namespace Avalonia.Skia
///
public bool TryGetPointAtDistance(double distance, out Point point)
{
- if (EffectivePath is null)
+ if (StrokePath is null)
{
point = new Point();
return false;
@@ -142,7 +139,7 @@ namespace Avalonia.Skia
///
public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
{
- if (EffectivePath is null)
+ if (StrokePath is null)
{
point = new Point();
tangent = new Point();
@@ -158,7 +155,7 @@ namespace Avalonia.Skia
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
[NotNullWhen(true)] out IGeometryImpl? segmentGeometry)
{
- if (EffectivePath is null)
+ if (StrokePath is null)
{
segmentGeometry = null;
return false;
@@ -172,7 +169,7 @@ namespace Avalonia.Skia
if (res)
{
- segmentGeometry = new StreamGeometryImpl(_skPathSegment);
+ segmentGeometry = new StreamGeometryImpl(_skPathSegment, null);
}
return res;
diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
index 079eea7bef..0521e238f3 100644
--- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
+++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Skia
{
TextBlob = textBlob ?? throw new ArgumentNullException(nameof(textBlob));
- Size = size;
+ Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
BaselineOrigin = baselineOrigin;
}
@@ -21,7 +21,7 @@ namespace Avalonia.Skia
///
public SKTextBlob TextBlob { get; }
- public Size Size { get; }
+ public Rect Bounds { get; }
public Point BaselineOrigin { get; }
diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
index ec24f8f624..b774ddf411 100644
--- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
+++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
@@ -13,7 +13,6 @@ namespace Avalonia.Skia.Helpers
///
///
///
- ///
/// DrawingContext
public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi)
{
diff --git a/src/Skia/Avalonia.Skia/LineGeometryImpl.cs b/src/Skia/Avalonia.Skia/LineGeometryImpl.cs
index b102a4c119..2ac0a2e18c 100644
--- a/src/Skia/Avalonia.Skia/LineGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/LineGeometryImpl.cs
@@ -9,7 +9,8 @@ namespace Avalonia.Skia
internal class LineGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
- public override SKPath EffectivePath { get; }
+ public override SKPath StrokePath { get; }
+ public override SKPath? FillPath => null;
public LineGeometryImpl(Point p1, Point p2)
{
@@ -17,7 +18,7 @@ namespace Avalonia.Skia
path.MoveTo(p1.ToSKPoint());
path.LineTo(p2.ToSKPoint());
- EffectivePath = path;
+ StrokePath = path;
Bounds = new Rect(
new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)),
new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)));
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index ab1c6b8816..9c4b326f14 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -65,7 +65,7 @@ namespace Avalonia.Skia
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
{
- return new CombinedGeometryImpl(combineMode, g1, g2);
+ return CombinedGeometryImpl.ForceCreate(combineMode, g1, g2);
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
@@ -104,7 +104,7 @@ namespace Avalonia.Skia
SKFontCache.Shared.Return(skFont);
- return new StreamGeometryImpl(path);
+ return new StreamGeometryImpl(path, path);
}
///
diff --git a/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs b/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
index 93d453e8f0..2d127e07c9 100644
--- a/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
@@ -8,14 +8,15 @@ namespace Avalonia.Skia
internal class RectangleGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
- public override SKPath EffectivePath { get; }
+ public override SKPath StrokePath { get; }
+ public override SKPath? FillPath => StrokePath;
public RectangleGeometryImpl(Rect rect)
{
var path = new SKPath();
path.AddRect(rect.ToSKRect());
- EffectivePath = path;
+ StrokePath = path;
Bounds = rect;
}
}
diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
index 0c3289767e..eb081c4f50 100644
--- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
@@ -10,36 +11,39 @@ namespace Avalonia.Skia
internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
{
private Rect _bounds;
- private readonly SKPath _effectivePath;
+ private readonly SKPath _strokePath;
+ private SKPath? _fillPath;
///
/// Initializes a new instance of the class.
///
- /// An existing Skia .
+ /// An existing Skia for the stroke.
+ /// An existing Skia for the fill, can also be null or the same as the stroke
/// Precomputed path bounds.
- public StreamGeometryImpl(SKPath path, Rect bounds)
+ public StreamGeometryImpl(SKPath stroke, SKPath? fill, Rect? bounds = null)
{
- _effectivePath = path;
- _bounds = bounds;
+ _strokePath = stroke;
+ _fillPath = fill;
+ _bounds = bounds ?? stroke.TightBounds.ToAvaloniaRect();
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// An existing Skia .
- public StreamGeometryImpl(SKPath path) : this(path, path.TightBounds.ToAvaloniaRect())
+ private StreamGeometryImpl(SKPath path) : this(path, path, default(Rect))
{
+
}
///
/// Initializes a new instance of the class.
///
- public StreamGeometryImpl() : this(CreateEmptyPath(), default)
+ public StreamGeometryImpl() : this(CreateEmptyPath())
{
}
-
+
///
- public override SKPath EffectivePath => _effectivePath;
+ public override SKPath? StrokePath => _strokePath;
+
+ ///
+ public override SKPath? FillPath => _fillPath;
///
public override Rect Bounds => _bounds;
@@ -47,7 +51,9 @@ namespace Avalonia.Skia
///
public IStreamGeometryImpl Clone()
{
- return new StreamGeometryImpl(_effectivePath.Clone(), Bounds);
+ var stroke = _strokePath.Clone();
+ var fill = _fillPath == _strokePath ? stroke : _fillPath.Clone();
+ return new StreamGeometryImpl(stroke, fill, Bounds);
}
///
@@ -74,7 +80,10 @@ namespace Avalonia.Skia
private class StreamContext : IStreamGeometryContextImpl
{
private readonly StreamGeometryImpl _geometryImpl;
- private readonly SKPath _path;
+ private SKPath Stroke => _geometryImpl._strokePath;
+ private SKPath Fill => _geometryImpl._fillPath ??= new();
+ private bool _isFilled;
+ private bool Duplicate => _isFilled && !ReferenceEquals(_geometryImpl._fillPath, Stroke);
///
/// Initializes a new instance of the class.
@@ -83,52 +92,79 @@ namespace Avalonia.Skia
public StreamContext(StreamGeometryImpl geometryImpl)
{
_geometryImpl = geometryImpl;
- _path = _geometryImpl._effectivePath;
}
///
/// Will update bounds of passed geometry.
public void Dispose()
{
- _geometryImpl._bounds = _path.TightBounds.ToAvaloniaRect();
+ _geometryImpl._bounds = Stroke.TightBounds.ToAvaloniaRect();
_geometryImpl.InvalidateCaches();
}
///
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
- _path.ArcTo(
+ var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small;
+ var sweep = sweepDirection == SweepDirection.Clockwise
+ ? SKPathDirection.Clockwise
+ : SKPathDirection.CounterClockwise;
+ Stroke.ArcTo(
(float)size.Width,
(float)size.Height,
(float)rotationAngle,
- isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small,
- sweepDirection == SweepDirection.Clockwise ? SKPathDirection.Clockwise : SKPathDirection.CounterClockwise,
+ arc,
+ sweep,
(float)point.X,
(float)point.Y);
+ if(Duplicate)
+ Fill.ArcTo(
+ (float)size.Width,
+ (float)size.Height,
+ (float)rotationAngle,
+ arc,
+ sweep,
+ (float)point.X,
+ (float)point.Y);
}
///
public void BeginFigure(Point startPoint, bool isFilled)
{
- _path.MoveTo((float)startPoint.X, (float)startPoint.Y);
+ if (!isFilled)
+ {
+ if (Stroke == Fill)
+ _geometryImpl._fillPath = Stroke.Clone();
+ }
+
+ _isFilled = isFilled;
+ Stroke.MoveTo((float)startPoint.X, (float)startPoint.Y);
+ if(Duplicate)
+ Fill.MoveTo((float)startPoint.X, (float)startPoint.Y);
}
///
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
- _path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+ Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+ if(Duplicate)
+ Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
}
///
public void QuadraticBezierTo(Point point1, Point point2)
{
- _path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+ Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+ if(Duplicate)
+ Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
}
///
public void LineTo(Point point)
{
- _path.LineTo((float)point.X, (float)point.Y);
+ Stroke.LineTo((float)point.X, (float)point.Y);
+ if(Duplicate)
+ Fill.LineTo((float)point.X, (float)point.Y);
}
///
@@ -136,14 +172,16 @@ namespace Avalonia.Skia
{
if (isClosed)
{
- _path.Close();
+ Stroke.Close();
+ if (Duplicate)
+ Fill.Close();
}
}
///
public void SetFillRule(FillRule fillRule)
{
- _path.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
+ Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
}
}
}
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index e7dd4fb6da..153e4f0720 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
@@ -56,8 +56,6 @@ namespace Avalonia.Skia
var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
- var targetInfos = shapedBuffer.GlyphInfos;
-
var glyphInfos = buffer.GetGlyphInfoSpan();
var glyphPositions = buffer.GetGlyphPositionSpan();
@@ -83,7 +81,7 @@ namespace Avalonia.Skia
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
- targetInfos[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
+ shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
}
return shapedBuffer;
diff --git a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
index fb3c2e403f..2dee8c318c 100644
--- a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
@@ -17,16 +17,27 @@ namespace Avalonia.Skia
{
SourceGeometry = source;
Transform = transform;
+ var matrix = transform.ToSKMatrix();
- var transformedPath = source.EffectivePath.Clone();
- transformedPath?.Transform(transform.ToSKMatrix());
-
- EffectivePath = transformedPath;
+ var transformedPath = StrokePath = source.StrokePath.Clone();
+ transformedPath?.Transform(matrix);
+
Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default;
+
+ if (ReferenceEquals(source.StrokePath, source.FillPath))
+ FillPath = transformedPath;
+ else if (source.FillPath != null)
+ {
+ FillPath = transformedPath = source.FillPath.Clone();
+ transformedPath.Transform(matrix);
+ }
}
///
- public override SKPath? EffectivePath { get; }
+ public override SKPath? StrokePath { get; }
+
+ ///
+ public override SKPath? FillPath { get; }
///
public IGeometryImpl SourceGeometry { get; }
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index f9b5953e3f..87fa963871 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -387,7 +387,7 @@ namespace Avalonia.Direct2D1.Media
/// The glyph run.
public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
{
- using (var brush = CreateBrush(foreground, glyphRun.Item.Size))
+ using (var brush = CreateBrush(foreground, glyphRun.Item.Bounds.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
index 446db47d92..2e7a4b67f6 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
@@ -9,12 +9,12 @@ namespace Avalonia.Direct2D1.Media
{
public GlyphRunImpl(GlyphRun glyphRun, Size size, Point baselineOrigin)
{
- Size = size;
+ Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
BaselineOrigin = baselineOrigin;
GlyphRun = glyphRun;
}
- public Size Size { get; }
+ public Rect Bounds{ get; }
public Point BaselineOrigin { get; }
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
index ff0fff6b14..d42bc3c5cf 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
@@ -52,8 +52,6 @@ namespace Avalonia.Direct2D1.Media
var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
- var targetInfos = shapedBuffer.GlyphInfos;
-
var glyphInfos = buffer.GetGlyphInfoSpan();
var glyphPositions = buffer.GetGlyphPositionSpan();
@@ -79,7 +77,7 @@ namespace Avalonia.Direct2D1.Media
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
- targetInfos[i] = new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
+ shapedBuffer[i] = new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
}
return shapedBuffer;
diff --git a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
index f5b2d462ab..1ab4c0d2dc 100644
--- a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
+++ b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
@@ -4,7 +4,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input
{
- static class KeyInterop
+ public static class KeyInterop
{
private static readonly Dictionary s_virtualKeyFromKey = new Dictionary
{
diff --git a/src/tools/DevAnalyzers/DevAnalyzers.csproj b/src/tools/DevAnalyzers/DevAnalyzers.csproj
index 2d9331b5dc..6044eb6d2d 100644
--- a/src/tools/DevAnalyzers/DevAnalyzers.csproj
+++ b/src/tools/DevAnalyzers/DevAnalyzers.csproj
@@ -3,6 +3,7 @@
netstandard2.0
enable
+ True
diff --git a/src/tools/DevGenerators/DevGenerators.csproj b/src/tools/DevGenerators/DevGenerators.csproj
index 7e63987d1b..ff5a4aa81d 100644
--- a/src/tools/DevGenerators/DevGenerators.csproj
+++ b/src/tools/DevGenerators/DevGenerators.csproj
@@ -4,6 +4,7 @@
netstandard2.0
enable
false
+ True
diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs
index 0a27602604..0c0b487dba 100644
--- a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs
+++ b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs
@@ -396,7 +396,7 @@ public partial class AvaloniaPropertyAnalyzer
hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration
}
- var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef };
+ var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef };
// assume that the property is owned by its containing type at the point of assignment, until we find its registration
result.SetAssignment(s, new(s.ContainingType, Location.None));
@@ -570,7 +570,7 @@ public partial class AvaloniaPropertyAnalyzer
if (_allGetSetMethods.Contains(originalMethod))
{
- if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } &&
+ if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } &&
GetReferencedProperty(invocation.Arguments[0]) is { } refProp &&
refProp.description.AssignedTo.TryGetValue(refProp.storageSymbol, out var ownerType) &&
!DerivesFrom(context.ContainingSymbol.ContainingType, ownerType.Type) &&
@@ -694,9 +694,14 @@ public partial class AvaloniaPropertyAnalyzer
void VerifyAccessor(IMethodSymbol? method, string verb, string methodName)
{
- if (method == null)
+ if (method is null)
{
- context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName));
+ if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public ||
+ (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Protected
+ && avaloniaPropertyStorage.ContainingSymbol.DeclaredAccessibility == Accessibility.Public))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName));
+ }
}
else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility)
{
diff --git a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
index 28fb19e119..894b6578e3 100644
--- a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
+++ b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Base.UnitTests;
public class AssetLoaderTests : IDisposable
{
- public class MockAssembly : Assembly {}
+ public class MockAssembly : Assembly { }
private const string AssemblyNameWithWhitespace = "Awesome Library";
@@ -50,6 +50,17 @@ public class AssetLoaderTests : IDisposable
Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName);
}
+ [Fact]
+ public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable()
+ {
+ var uri = new Uri($"avares://InvalidAssembly");
+ var loader = new AssetLoader();
+
+ var assemblyActual = loader.GetAssets(uri, null);
+
+ Assert.Empty(assemblyActual);
+ }
+
private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName)
{
var assembly = Mock.Of();
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs
index 8ad36a583e..c850fbdb08 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs
@@ -1,6 +1,9 @@
using System;
+using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Diagnostics;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
using Xunit;
using Observable = Avalonia.Reactive.Observable;
@@ -275,6 +278,79 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("style", target.Foo);
}
+ [Fact]
+ public void SetCurrent_Value_Persists_When_Toggling_Style_1()
+ {
+ var target = new Class1();
+ var root = new TestRoot(target)
+ {
+ Styles =
+ {
+ new Style(x => x.OfType().Class("foo"))
+ {
+ Setters = { new Setter(Class1.BarProperty, "bar") },
+ }
+ }
+ };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ target.SetCurrentValue(Class1.FooProperty, "current");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bardefault", target.Bar);
+
+ target.Classes.Add("foo");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bar", target.Bar);
+
+ target.Classes.Remove("foo");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bardefault", target.Bar);
+ }
+
+ [Fact]
+ public void SetCurrent_Value_Persists_When_Toggling_Style_2()
+ {
+ var target = new Class1();
+ var root = new TestRoot(target)
+ {
+ Styles =
+ {
+ new Style(x => x.OfType().Class("foo"))
+ {
+ Setters =
+ {
+ new Setter(Class1.BarProperty, "bar"),
+ new Setter(Class1.InheritedProperty, "inherited"),
+ },
+ }
+ }
+ };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ target.SetCurrentValue(Class1.FooProperty, "current");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bardefault", target.Bar);
+ Assert.Equal("inheriteddefault", target.Inherited);
+
+ target.Classes.Add("foo");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bar", target.Bar);
+ Assert.Equal("inherited", target.Inherited);
+
+ target.Classes.Remove("foo");
+
+ Assert.Equal("current", target.Foo);
+ Assert.Equal("bardefault", target.Bar);
+ Assert.Equal("inheriteddefault", target.Inherited);
+ }
+
private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property)
{
return target.GetDiagnostic(property).Priority;
@@ -285,16 +361,19 @@ namespace Avalonia.Base.UnitTests
return target.GetDiagnostic(property).IsOverriddenCurrentValue;
}
- private class Class1 : AvaloniaObject
+ private class Class1 : Control
{
public static readonly StyledProperty FooProperty =
AvaloniaProperty.Register(nameof(Foo), "foodefault");
+ public static readonly StyledProperty BarProperty =
+ AvaloniaProperty.Register(nameof(Bar), "bardefault");
public static readonly StyledProperty InheritedProperty =
AvaloniaProperty.Register(nameof(Inherited), "inheriteddefault", inherits: true);
public static readonly StyledProperty CoercedProperty =
AvaloniaProperty.Register(nameof(Coerced), coerce: Coerce);
public string Foo => GetValue(FooProperty);
+ public string Bar => GetValue(BarProperty);
public string Inherited => GetValue(InheritedProperty);
public double Coerced => GetValue(CoercedProperty);
public double CoerceMax { get; set; } = 100;
diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
index 60603937d9..6ba648af78 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
@@ -569,6 +569,46 @@ public class StyledElementTests_Theming
Assert.Equal(Brushes.Green, border.Background);
}
+ [Fact]
+ public void TemplatedParent_Theme_Change_Applies_Recursively_To_VisualChildren()
+ {
+ var theme = CreateDerivedTheme();
+ var target = CreateTarget();
+
+ Assert.Null(target.Theme);
+ Assert.Null(target.Template);
+
+ var root = CreateRoot(target, theme.BasedOn);
+
+ Assert.NotNull(target.Theme);
+ Assert.NotNull(target.Template);
+
+ root.Styles.Add(new Style(x => x.OfType().Class("foo"))
+ {
+ Setters = { new Setter(StyledElement.ThemeProperty, theme) }
+ });
+
+ root.LayoutManager.ExecuteLayoutPass();
+
+ var border = Assert.IsType(target.VisualChild);
+ var inner = Assert.IsType(border.Child);
+
+ Assert.Equal(Brushes.Red, border.Background);
+ Assert.Equal(Brushes.Red, inner.Background);
+
+ Assert.Equal(null, inner.BorderBrush);
+ Assert.Equal(null, inner.BorderBrush);
+
+ target.Classes.Add("foo");
+ root.LayoutManager.ExecuteLayoutPass();
+
+ Assert.Equal(Brushes.Green, border.Background);
+ Assert.Equal(Brushes.Green, inner.Background);
+
+ Assert.Equal(Brushes.Cyan, inner.BorderBrush);
+ Assert.Equal(Brushes.Cyan, inner.BorderBrush);
+ }
+
private static ThemedControl CreateTarget()
{
return new ThemedControl();
@@ -595,7 +635,8 @@ public class StyledElementTests_Theming
private static ControlTheme CreateTheme(string tag = "theme")
{
- var template = new FuncControlTemplate((o, n) => new Border());
+ var template = new FuncControlTemplate(
+ (o, n) => new Border() { Child = new Border() });
return new ControlTheme
{
diff --git a/tests/Avalonia.Benchmarks/NullGlyphRun.cs b/tests/Avalonia.Benchmarks/NullGlyphRun.cs
index c4707c78c8..5b584f302d 100644
--- a/tests/Avalonia.Benchmarks/NullGlyphRun.cs
+++ b/tests/Avalonia.Benchmarks/NullGlyphRun.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Benchmarks
{
internal class NullGlyphRun : IGlyphRunImpl
{
- public Size Size => default;
+ public Rect Bounds => default;
public Point BaselineOrigin => default;
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index f074972cef..3aaf62f0bf 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -73,6 +73,19 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target, target.Presenter.Panel.TemplatedParent);
}
+ [Fact]
+ public void Panel_Should_Have_ItemsHost_Set_To_True()
+ {
+ var target = new ItemsControl();
+
+ target.Template = GetTemplate();
+ target.Items = new[] { "Foo" };
+ target.ApplyTemplate();
+ target.Presenter!.ApplyTemplate();
+
+ Assert.True(target.Presenter.Panel!.IsItemsHost);
+ }
+
[Fact]
public void Container_Should_Have_TemplatedParent_Set_To_Null()
{
@@ -634,7 +647,7 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
- var item = target.Presenter.Panel.LogicalChildren[0];
+ var item = target.LogicalChildren[0];
Assert.Null(NameScope.GetNameScope((TextBlock)item));
}
diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
index a31f0dd4c2..82f133d533 100644
--- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
@@ -141,5 +141,20 @@ namespace Avalonia.Controls.UnitTests
var panel = new Panel();
Assert.Throws(() => panel.Children.Add(null!));
}
+
+ [Fact]
+ public void Adding_Control_To_Items_Host_Panel_Should_Not_Affect_Logical_Children()
+ {
+ var child = new Control();
+ var realParent = new ContentControl { Content = child };
+ var panel = new Panel { IsItemsHost = true };
+
+ panel.Children.Add(child);
+
+ Assert.Empty(panel.LogicalChildren);
+ Assert.Same(child.Parent, realParent);
+ Assert.Same(child.GetLogicalParent(), realParent);
+ Assert.Same(child.GetVisualParent(), panel);
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
index 15ff6e68e3..e5c96dcab6 100644
--- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
@@ -11,9 +11,6 @@ namespace Avalonia.Controls.UnitTests.Platform
{
public class DefaultMenuInteractionHandlerTests
{
- static PointerEventArgs CreateArgs(RoutedEvent ev, object source)
- => new PointerEventArgs(ev, source, new FakePointer(), (Visual)source, default, 0, PointerPointProperties.None, default);
-
static PointerPressedEventArgs CreatePressed(object source) => new PointerPressedEventArgs(source,
new FakePointer(), (Visual)source, default,0, new PointerPointProperties (RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
default);
@@ -171,7 +168,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = new Mock();
var item = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, isSubMenuOpen: true, parent: menu.Object);
var nextItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu.Object);
- var e = CreateArgs(MenuItem.PointerEnteredItemEvent, nextItem.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerEnteredItemEvent, nextItem.Object);
menu.SetupGet(x => x.SelectedItem).Returns(item.Object);
@@ -191,7 +188,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock();
var item = CreateMockMenuItem(isTopLevel: true, parent: menu.Object);
- var e = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
menu.SetupGet(x => x.SelectedItem).Returns(item.Object);
target.PointerExited(item, e);
@@ -206,7 +203,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock();
var item = CreateMockMenuItem(isTopLevel: true, parent: menu.Object);
- var e = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
menu.SetupGet(x => x.IsOpen).Returns(true);
menu.SetupGet(x => x.SelectedItem).Returns(item.Object);
@@ -365,7 +362,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerEnteredItemEvent, item.Object);
target.PointerEntered(item.Object, e);
@@ -381,7 +378,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(hasSubMenu: true, parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerEnteredItemEvent, item.Object);
target.PointerEntered(item.Object, e);
item.Verify(x => x.Open(), Times.Never);
@@ -401,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(parent: parentItem.Object);
var sibling = CreateMockMenuItem(hasSubMenu: true, isSubMenuOpen: true, parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerEnteredItemEvent, item.Object);
parentItem.SetupGet(x => x.SubItems).Returns(new[] { item.Object, sibling.Object });
@@ -421,7 +418,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
parentItem.SetupGet(x => x.SelectedItem).Returns(item.Object);
target.PointerExited(item, e);
@@ -438,7 +435,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(parent: parentItem.Object);
var sibling = CreateMockMenuItem(parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
parentItem.SetupGet(x => x.SelectedItem).Returns(sibling.Object);
target.PointerExited(item, e);
@@ -454,7 +451,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(hasSubMenu: true, parent: parentItem.Object);
- var e = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var e = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
item.Setup(x => x.IsPointerOverSubMenu).Returns(true);
target.PointerExited(item, e);
@@ -488,8 +485,8 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = CreateMockMenuItem(isTopLevel: true, hasSubMenu: true, parent: menu);
var item = CreateMockMenuItem(hasSubMenu: true, parent: parentItem.Object);
var childItem = CreateMockMenuItem(parent: item.Object);
- var enter = CreateArgs(MenuItem.PointerEnteredItemEvent, item.Object);
- var leave = CreateArgs(MenuItem.PointerExitedItemEvent, item.Object);
+ var enter = new RoutedEventArgs(MenuItem.PointerEnteredItemEvent, item.Object);
+ var leave = new RoutedEventArgs(MenuItem.PointerExitedItemEvent, item.Object);
// Pointer enters item; item is selected.
target.PointerEntered(item, enter);
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 4804b29fee..bc1225e0e8 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -18,6 +18,7 @@ using Avalonia.Input;
using Avalonia.Rendering;
using System.Threading.Tasks;
using Avalonia.Threading;
+using Avalonia.Interactivity;
namespace Avalonia.Controls.UnitTests.Primitives
{
@@ -1048,6 +1049,30 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
+ [Fact]
+ public void Events_Should_Be_Routed_To_Popup_Parent()
+ {
+ using (CreateServices())
+ {
+ var popupContent = new Border();
+ var popup = new Popup { Child = popupContent };
+ var popupParent = new Border { Child = popup };
+ var root = PreparedWindow(popupParent);
+ var raised = 0;
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+ popup.Open();
+ root.LayoutManager.ExecuteLayoutPass();
+
+ var ev = new RoutedEventArgs(Button.ClickEvent);
+
+ popupParent.AddHandler(Button.ClickEvent, (s, e) => ++raised);
+ popupContent.RaiseEvent(ev);
+
+ Assert.Equal(1, raised);
+ }
+ }
+
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 4c5397db60..0f72b2101a 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -327,7 +327,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
- var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
+ var logicalChildren = target.GetLogicalChildren();
var result = logicalChildren
.OfType()
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index 1a9fb33b7e..2ca716fa8f 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -1178,7 +1178,7 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
- var item = target.Presenter.Panel.LogicalChildren[0];
+ var item = target.LogicalChildren[0];
Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs
index bad015506f..4d8760ad61 100644
--- a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs
@@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public AutomationTests(TestAppFixture fixture)
+ public AutomationTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
index 6c630ae782..c0a5414ee3 100644
--- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
@@ -9,7 +9,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public ButtonTests(TestAppFixture fixture)
+ public ButtonTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs
index 02e7ac60c4..6c154fa268 100644
--- a/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs
@@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public CheckBoxTests(TestAppFixture fixture)
+ public CheckBoxTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs b/tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs
new file mode 100644
index 0000000000..1e9fa22d9e
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs
@@ -0,0 +1,14 @@
+using Xunit;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ [CollectionDefinition("Default")]
+ public class DefaultCollection : ICollectionFixture
+ {
+ }
+
+ [CollectionDefinition("OverlayPopups")]
+ public class OverlayPopupsCollection : ICollectionFixture
+ {
+ }
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs
index 8df7873582..9e35d366d2 100644
--- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs
@@ -4,12 +4,11 @@ using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
- [Collection("Default")]
- public class ComboBoxTests
+ public abstract class ComboBoxTests
{
private readonly AppiumDriver _session;
- public ComboBoxTests(TestAppFixture fixture)
+ public ComboBoxTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
@@ -153,5 +152,17 @@ namespace Avalonia.IntegrationTests.Appium
Assert.Equal(string.Empty, comboBox.GetComboBoxValue());
}
+
+ [Collection("Default")]
+ public class Default : ComboBoxTests
+ {
+ public Default(DefaultAppFixture fixture) : base(fixture) { }
+ }
+
+ [Collection("OverlayPopups")]
+ public class OverlayPopups : ComboBoxTests
+ {
+ public OverlayPopups(OverlayPopupsAppFixture fixture) : base(fixture) { }
+ }
}
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs
similarity index 62%
rename from tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs
rename to tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs
index d71f9e9bcc..bb08cc0514 100644
--- a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs
@@ -9,25 +9,21 @@ using OpenQA.Selenium.Appium.Windows;
namespace Avalonia.IntegrationTests.Appium
{
- public class TestAppFixture : IDisposable
+ public class DefaultAppFixture : IDisposable
{
private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net7.0\IntegrationTestApp.exe";
private const string TestAppBundleId = "net.avaloniaui.avalonia.integrationtestapp";
- public TestAppFixture()
+ public DefaultAppFixture()
{
- var opts = new AppiumOptions();
- var path = Path.GetFullPath(TestAppPath);
+ var options = new AppiumOptions();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- opts.AddAdditionalCapability(MobileCapabilityType.App, path);
- opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.Windows);
- opts.AddAdditionalCapability(MobileCapabilityType.DeviceName, "WindowsPC");
-
+ ConfigureWin32Options(options);
Session = new WindowsDriver(
new Uri("http://127.0.0.1:4723"),
- opts);
+ options);
// https://github.com/microsoft/WinAppDriver/issues/1025
SetForegroundWindow(new IntPtr(int.Parse(
@@ -36,14 +32,10 @@ namespace Avalonia.IntegrationTests.Appium
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- opts.AddAdditionalCapability("appium:bundleId", TestAppBundleId);
- opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS);
- opts.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2");
- opts.AddAdditionalCapability("appium:showServerLogs", true);
-
+ ConfigureMacOptions(options);
Session = new MacDriver(
new Uri("http://127.0.0.1:4723/wd/hub"),
- opts);
+ options);
}
else
{
@@ -51,6 +43,22 @@ namespace Avalonia.IntegrationTests.Appium
}
}
+ protected virtual void ConfigureWin32Options(AppiumOptions options)
+ {
+ var path = Path.GetFullPath(TestAppPath);
+ options.AddAdditionalCapability(MobileCapabilityType.App, path);
+ options.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.Windows);
+ options.AddAdditionalCapability(MobileCapabilityType.DeviceName, "WindowsPC");
+ }
+
+ protected virtual void ConfigureMacOptions(AppiumOptions options)
+ {
+ options.AddAdditionalCapability("appium:bundleId", TestAppBundleId);
+ options.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS);
+ options.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2");
+ options.AddAdditionalCapability("appium:showServerLogs", true);
+ }
+
public AppiumDriver Session { get; }
public void Dispose()
diff --git a/tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs b/tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs
deleted file mode 100644
index bb2dd1fbec..0000000000
--- a/tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Xunit;
-
-namespace Avalonia.IntegrationTests.Appium
-{
- [CollectionDefinition("Default")]
- public class DefaultCollection : ICollectionFixture
- {
- }
-}
diff --git a/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs b/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
index 9745f993cb..65864cc649 100644
--- a/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
@@ -11,7 +11,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public GestureTests(TestAppFixture fixture)
+ public GestureTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs
index e2943b3349..5c81c20af1 100644
--- a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs
@@ -11,7 +11,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public ListBoxTests(TestAppFixture fixture)
+ public ListBoxTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
index 3f1fe7de12..5f57dfbc19 100644
--- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
@@ -7,11 +7,11 @@ using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
[Collection("Default")]
- public class MenuTests
+ public abstract class MenuTests
{
private readonly AppiumDriver _session;
- public MenuTests(TestAppFixture fixture)
+ public MenuTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
@@ -181,5 +181,17 @@ namespace Avalonia.IntegrationTests.Appium
var tab = tabs.FindElementByName("Menu");
tab.MovePointerOver();
}
+
+ [Collection("Default")]
+ public class Default : MenuTests
+ {
+ public Default(DefaultAppFixture fixture) : base(fixture) { }
+ }
+
+ [Collection("OverlayPopups")]
+ public class OverlayPopups : MenuTests
+ {
+ public OverlayPopups(OverlayPopupsAppFixture fixture) : base(fixture) { }
+ }
}
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs
index 7858c4cc81..20594a9774 100644
--- a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs
@@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public NativeMenuTests(TestAppFixture fixture)
+ public NativeMenuTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs b/tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs
new file mode 100644
index 0000000000..1f8646888d
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs
@@ -0,0 +1,19 @@
+using OpenQA.Selenium.Appium;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ public class OverlayPopupsAppFixture : DefaultAppFixture
+ {
+ protected override void ConfigureWin32Options(AppiumOptions options)
+ {
+ base.ConfigureWin32Options(options);
+ options.AddAdditionalCapability("appArguments", "--overlayPopups");
+ }
+
+ protected override void ConfigureMacOptions(AppiumOptions options)
+ {
+ base.ConfigureMacOptions(options);
+ options.AddAdditionalCapability("appium:arguments", new[] { "--overlayPopups" });
+ }
+ }
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
index 5bd0a05155..26a8577cb0 100644
--- a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using OpenQA.Selenium.Appium;
+using OpenQA.Selenium.Appium;
using Xunit;
namespace Avalonia.IntegrationTests.Appium
@@ -14,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public RadioButtonTests(TestAppFixture fixture)
+ public RadioButtonTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
index 7fa5eb83ee..9371a49ade 100644
--- a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
@@ -10,7 +10,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public SliderTests(TestAppFixture fixture)
+ public SliderTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
index ec24caa18c..a2bfb618d6 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -19,7 +19,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public WindowTests(TestAppFixture fixture)
+ public WindowTests(DefaultAppFixture fixture)
{
_session = fixture.Session;
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
index 55812d8df7..2eaaf2e0a8 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
@@ -16,7 +16,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver _session;
- public WindowTests_MacOS(TestAppFixture fixture)
+ public WindowTests_MacOS(DefaultAppFixture fixture)
{
var retry = 0;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 0b33cd9d97..27634b457b 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -847,6 +847,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("test", target.Text);
}
}
+
+ [Fact]
+ public void ResolvesRelativeSourceBindingEvenLongerForm()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("text");
+
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+ target.ApplyTemplate();
+
+ Assert.Equal("test", target.Text);
+ }
+ }
[Fact]
public void ResolvesRelativeSourceBindingFromTemplate()
@@ -1735,7 +1759,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
+ X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true });
var compiledPath = ((CompiledBindingExtension)control.X).Path;
@@ -1745,6 +1769,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ [Fact]
+ public void Should_Bind_To_Nested_Generic_Property()
+ {
+ // See https://github.com/AvaloniaUI/Avalonia/issues/10485
+ // This code works fine with SRE, and test is passing, but it fails on Cecil.
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var comboBox = window.FindControl("comboBox");
+
+ var dataContext = new TestDataContext();
+ dataContext.GenericProperty.Add(123);
+ dataContext.GenericProperty.CurrentItem = 123;
+ window.DataContext = dataContext;
+
+ Assert.Equal(123, comboBox.SelectedItem);
+ }
+ }
+
static void Throws(string type, Action cb)
{
try
@@ -1837,8 +1888,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public string ExplicitProperty => "Bye";
- public static string StaticProperty => "World";
+ public static string StaticProperty => "World";
+ public ListItemCollectionView GenericProperty { get; } = new();
+
public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived
{
private readonly Dictionary _storage = new Dictionary();
@@ -1858,6 +1911,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ public class ListItemCollectionView : List
+ {
+ public T CurrentItem { get; set; }
+ }
+
public class MethodDataContext
{
public void Action() { }
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
index c8be1c6d19..1b795a304c 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
@@ -702,9 +702,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
// EndInit should be second-to-last operation, as last operation will be
// caused by styling being applied on EndInit.
- Assert.Equal("EndInit 0", tracker.Order[tracker.Order.Count - 2]);
+ Assert.Equal("EndInit 0", tracker.Order[tracker.Order.Count - 3]);
// Caused by styling.
+ Assert.Equal("Property FontFamily Changed", tracker.Order[tracker.Order.Count - 2]);
Assert.Equal("Property Foreground Changed", tracker.Order[tracker.Order.Count - 1]);
}
}
diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs
index bac16cca88..bf375121de 100644
--- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs
+++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs
@@ -376,5 +376,63 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
await RenderToFile(target);
CompareImages();
}
+
+ [Fact]
+ public async Task BeginFigure_IsFilled_Is_Respected()
+ {
+ var target = new Border
+ {
+ Width = 200,
+ Height = 200,
+ Background = Brushes.White,
+ Child = new Path
+ {
+ Fill = Brushes.Black,
+ Stroke = Brushes.Black,
+ StrokeThickness = 10,
+ Data = new PathGeometry()
+ {
+ Figures = new()
+ {
+ new PathFigure
+ {
+ IsFilled = false, IsClosed = false,
+ StartPoint = new Point(170,170),
+ Segments = new ()
+ {
+ new LineSegment
+ {
+ Point = new Point(60, 170)
+ },
+ new LineSegment
+ {
+ Point = new Point(60, 60)
+ }
+ }
+ },
+ new PathFigure
+ {
+ IsFilled = true, IsClosed = true,
+ StartPoint = new Point(60,20),
+ Segments = new ()
+ {
+ new LineSegment
+ {
+ Point = new Point(20, 60)
+ },
+ new LineSegment
+ {
+ Point = new Point(100, 60)
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
index bfe03030c6..acdc242f0d 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
@@ -110,7 +110,7 @@ namespace Avalonia.Skia.UnitTests.Media
if (glyphRun.IsLeftToRight)
{
var characterHit =
- glyphRun.GetCharacterHitFromDistance(glyphRun.Size.Width, out _);
+ glyphRun.GetCharacterHitFromDistance(glyphRun.Bounds.Width, out _);
Assert.Equal(glyphRun.Characters.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
}
@@ -157,9 +157,9 @@ namespace Avalonia.Skia.UnitTests.Media
private static List BuildRects(GlyphRun glyphRun)
{
- var height = glyphRun.Size.Height;
+ var height = glyphRun.Bounds.Height;
- var currentX = glyphRun.IsLeftToRight ? 0d : glyphRun.Size.Width;
+ var currentX = glyphRun.IsLeftToRight ? 0d : glyphRun.Bounds.Width;
var rects = new List(glyphRun.GlyphInfos!.Count);
@@ -216,12 +216,12 @@ namespace Avalonia.Skia.UnitTests.Media
shapedBuffer.GlyphTypeface,
shapedBuffer.FontRenderingEmSize,
shapedBuffer.Text,
- shapedBuffer.GlyphInfos,
+ shapedBuffer,
biDiLevel: shapedBuffer.BidiLevel);
if(shapedBuffer.BidiLevel == 1)
{
- shapedBuffer.GlyphInfos.Span.Reverse();
+ shapedBuffer.Reverse();
}
return glyphRun;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index 9a7460c218..a24da38fdd 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -199,7 +199,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
=> textLayout.TextLines
.Select(line => string.Join('|', line.TextRuns
.Cast()
- .SelectMany(run => run.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphIndex)))
+ .SelectMany(run => run.ShapedBuffer, (_, glyph) => glyph.GlyphIndex)))
.ToList();
}
@@ -457,7 +457,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var glyphRun = shapedRun.GlyphRun;
- var width = glyphRun.Size.Width;
+ var width = glyphRun.Bounds.Width;
var characterHit = glyphRun.GetCharacterHitFromDistance(width, out _);
@@ -489,7 +489,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var shapedRun = (ShapedTextRun)textRun;
- var glyphClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster).ToArray();
+ var glyphClusters = shapedRun.ShapedBuffer.Select(glyph => glyph.GlyphCluster).ToArray();
var expected = clusters.Skip(index).Take(glyphClusters.Length).ToArray();
@@ -522,11 +522,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(expectedLength, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphInfos.Count);
- Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphInfos[5].GlyphCluster);
+ Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer[5].GlyphCluster);
if (expectedLength == 7)
{
- Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphInfos[6].GlyphCluster);
+ Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer[6].GlyphCluster);
}
}
}
@@ -783,7 +783,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var rects = layout.TextLines
.SelectMany(x => x.TextRuns.Cast())
- .SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphAdvance)
+ .SelectMany(x => x.ShapedBuffer, (_, glyph) => glyph.GlyphAdvance)
.ToArray();
for (var i = 0; i < SingleLineText.Length; i++)
@@ -939,7 +939,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var firstRun = (ShapedTextRun)textLine.TextRuns[0];
- var firstCluster = firstRun.ShapedBuffer.GlyphInfos[0].GlyphCluster;
+ var firstCluster = firstRun.ShapedBuffer[0].GlyphCluster;
var characterHit = textLine.GetCharacterHitFromDistance(0);
@@ -953,7 +953,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
- var firstAdvance = firstRun.ShapedBuffer.GlyphInfos[0].GlyphAdvance;
+ var firstAdvance = firstRun.ShapedBuffer[0].GlyphAdvance;
Assert.Equal(firstAdvance, distance, 5);
@@ -996,9 +996,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var shapedRuns = textLine.TextRuns.Cast().ToList();
- var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphCluster).ToList();
+ var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer, (_, glyph) => glyph.GlyphCluster).ToList();
- var glyphAdvances = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphAdvance).ToList();
+ var glyphAdvances = shapedRuns.SelectMany(x => x.ShapedBuffer, (_, glyph) => glyph.GlyphAdvance).ToList();
var currentX = 0.0;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index bd64d72a4d..0cfa78d007 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -95,7 +95,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var shapedRun = (ShapedTextRun)textRun;
- var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster);
+ var runClusters = shapedRun.ShapedBuffer.Select(glyph => glyph.GlyphCluster);
clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters);
}
@@ -142,7 +142,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var shapedRun = (ShapedTextRun)textRun;
- var runClusters = shapedRun.ShapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster);
+ var runClusters = shapedRun.ShapedBuffer.Select(glyph => glyph.GlyphCluster);
clusters.AddRange(shapedRun.IsReversed ? runClusters.Reverse() : runClusters);
}
@@ -249,7 +249,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = textLine.TextRuns
.Cast()
- .SelectMany(x => x.ShapedBuffer.GlyphInfos, (_, glyph) => glyph.GlyphCluster)
+ .SelectMany(x => x.ShapedBuffer, (_, glyph) => glyph.GlyphCluster)
.ToArray();
var previousCharacterHit = new CharacterHit(text.Length);
@@ -752,7 +752,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var shapedBuffer = textRun.ShapedBuffer;
- var currentClusters = shapedBuffer.GlyphInfos.Select(glyph => glyph.GlyphCluster).ToList();
+ var currentClusters = shapedBuffer.Select(glyph => glyph.GlyphCluster).ToList();
foreach (var currentCluster in currentClusters)
{
@@ -785,11 +785,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var shapedBuffer = textRun.ShapedBuffer;
- for (var index = 0; index < shapedBuffer.GlyphInfos.Length; index++)
+ for (var index = 0; index < shapedBuffer.Length; index++)
{
- var currentCluster = shapedBuffer.GlyphInfos[index].GlyphCluster;
+ var currentCluster = shapedBuffer[index].GlyphCluster;
- var advance = shapedBuffer.GlyphInfos[index].GlyphAdvance;
+ var advance = shapedBuffer[index].GlyphAdvance;
if (lastCluster != currentCluster)
{
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs
index 3f02867aa9..92778793c2 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs
@@ -19,10 +19,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
Assert.Equal(shapedBuffer.Length, text.Length);
- Assert.Equal(shapedBuffer.GlyphInfos.Length, text.Length);
- Assert.Equal(0, shapedBuffer.GlyphInfos[0].GlyphCluster);
- Assert.Equal(1, shapedBuffer.GlyphInfos[1].GlyphCluster);
- Assert.Equal(1, shapedBuffer.GlyphInfos[2].GlyphCluster);
+ Assert.Equal(shapedBuffer.Length, text.Length);
+ Assert.Equal(0, shapedBuffer[0].GlyphCluster);
+ Assert.Equal(1, shapedBuffer[1].GlyphCluster);
+ Assert.Equal(1, shapedBuffer[2].GlyphCluster);
}
}
@@ -36,7 +36,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
Assert.Equal(shapedBuffer.Length, text.Length);
- Assert.Equal(100, shapedBuffer.GlyphInfos[0].GlyphAdvance);
+ Assert.Equal(100, shapedBuffer[0].GlyphAdvance);
}
}
diff --git a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
index 0448ecd41f..375f87259d 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
@@ -52,8 +52,6 @@ namespace Avalonia.UnitTests
var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
- var targetInfos = shapedBuffer.GlyphInfos;
-
var glyphInfos = buffer.GetGlyphInfoSpan();
var glyphPositions = buffer.GetGlyphPositionSpan();
@@ -79,7 +77,7 @@ namespace Avalonia.UnitTests
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
- targetInfos[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
+ shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
}
return shapedBuffer;
diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs
index 0319803a5e..4561d3b3f2 100644
--- a/tests/Avalonia.UnitTests/MockGlyphRun.cs
+++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs
@@ -16,10 +16,10 @@ namespace Avalonia.UnitTests
width += glyphInfos[i].GlyphAdvance;
}
- Size = new Size(width, 10);
+ Bounds = new Rect(new Size(width, 10));
}
- public Size Size { get; }
+ public Rect Bounds { get; }
public Point BaselineOrigin => new Point(0, 8);
diff --git a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs
index b810caabd9..b5f4777192 100644
--- a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs
+++ b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs
@@ -13,7 +13,6 @@ namespace Avalonia.UnitTests
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidiLevel;
var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
- var targetInfos = shapedBuffer.GlyphInfos;
var textSpan = text.Span;
var textStartIndex = TextTestHelper.GetStartCharIndex(text);
@@ -27,7 +26,7 @@ namespace Avalonia.UnitTests
for (var j = 0; j < count; ++j)
{
- targetInfos[i + j] = new GlyphInfo(glyphIndex, glyphCluster, 10);
+ shapedBuffer[i + j] = new GlyphInfo(glyphIndex, glyphCluster, 10);
}
i += count;
diff --git a/tests/TestFiles/Direct2D1/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png b/tests/TestFiles/Direct2D1/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png
new file mode 100644
index 0000000000..d67d71c520
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png b/tests/TestFiles/Skia/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png
new file mode 100644
index 0000000000..d67d71c520
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Path/BeginFigure_IsFilled_Is_Respected.expected.png differ