Browse Source

Merge branch 'master' into safe_insets

pull/10145/head
Max Katz 4 years ago
committed by GitHub
parent
commit
07708d2aad
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Avalonia.Desktop.slnf
  2. 17
      Avalonia.sln
  3. 9
      azure-pipelines-integrationtests.yml
  4. 5
      packages/Avalonia/Avalonia.props
  5. 1
      samples/ControlCatalog/ControlCatalog.csproj
  6. 8
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  7. 8
      samples/ControlCatalog/Pages/PointerCanvas.cs
  8. 1
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  9. 1
      samples/IntegrationTestApp/MainWindow.axaml
  10. 12
      samples/IntegrationTestApp/MainWindow.axaml.cs
  11. 22
      samples/IntegrationTestApp/Program.cs
  12. 5
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  13. 1
      src/Avalonia.Base/Media/DrawingContext.cs
  14. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  15. 4
      src/Avalonia.Base/Media/GeometryDrawing.cs
  16. 14
      src/Avalonia.Base/Media/GlyphRun.cs
  17. 2
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  18. 3
      src/Avalonia.Base/Media/PolyLineSegment.cs
  19. 2
      src/Avalonia.Base/Media/TextDecoration.cs
  20. 6
      src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
  21. 98
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  22. 10
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  23. 2
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  24. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  25. 2
      src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  26. 6
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  27. 155
      src/Avalonia.Base/Platform/AssetLoader.cs
  28. 3
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  29. 2
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  30. 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  31. 8
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  32. 5
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  33. 21
      src/Avalonia.Base/StyledElement.cs
  34. 13
      src/Avalonia.Base/Styling/OrSelector.cs
  35. 15
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  36. 10
      src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
  37. 17
      src/Avalonia.Base/Visual.cs
  38. 2
      src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
  39. 2
      src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs
  40. 9
      src/Avalonia.Controls/MaskedTextBox.cs
  41. 34
      src/Avalonia.Controls/MenuItem.cs
  42. 2
      src/Avalonia.Controls/NativeMenuItem.cs
  43. 35
      src/Avalonia.Controls/Panel.cs
  44. 4
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  45. 1
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  46. 3
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  47. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  48. 17
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  49. 6
      src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
  50. 2
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  51. 2
      src/Avalonia.Controls/TrayIcon.cs
  52. 2
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs
  53. 12
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  54. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  55. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  56. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf
  57. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf
  58. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf
  59. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf
  60. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf
  61. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf
  62. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf
  63. 13
      src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj
  64. 3
      src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs
  65. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  66. 2
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  67. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
  68. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
  69. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
  70. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
  71. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
  72. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
  73. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
  74. 3
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  75. 1
      src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
  76. 6
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  77. 1
      src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
  78. 1
      src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
  79. 1
      src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
  80. 4
      src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
  81. 4
      src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
  82. 1
      src/Avalonia.Themes.Simple/Controls/Window.xaml
  83. 27
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  84. 16
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  85. 43
      src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
  86. 11
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  87. 5
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  88. 23
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  89. 43
      src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
  90. 31
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  91. 4
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  92. 1
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  93. 5
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  94. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  95. 5
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  96. 90
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  97. 4
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  98. 21
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  99. 2
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  100. 4
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

1
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",

17
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}

9
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

5
packages/Avalonia/Avalonia.props

@ -6,4 +6,9 @@
<AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
<!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
<ItemGroup>
<ProjectCapability Include="Avalonia"/>
</ItemGroup>
</Project>

1
samples/ControlCatalog/ControlCatalog.csproj

@ -29,6 +29,7 @@
<ProjectReference Include="..\..\src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup>

8
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -16,10 +16,10 @@ namespace ControlCatalog.Pages
private Point _cursorPoint;
public StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
public StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
public static readonly StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
/// <summary>
/// Rotation, measured in Radians!
/// </summary>
@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
}
}
public StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
public static readonly StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
public double ViewportCenterY { get => GetValue(ViewportCenterYProperty); set => SetValue(ViewportCenterYProperty, value); }
public StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
public static readonly StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
public double ViewportCenterX { get => GetValue(ViewportCenterXProperty); set => SetValue(ViewportCenterXProperty, value); }
private IPen _pen;

8
samples/ControlCatalog/Pages/PointerCanvas.cs

@ -93,7 +93,7 @@ public class PointerCanvas : Control
}
private int _threadSleep;
public static DirectProperty<PointerCanvas, int> ThreadSleepProperty =
public static readonly DirectProperty<PointerCanvas, int> ThreadSleepProperty =
AvaloniaProperty.RegisterDirect<PointerCanvas, int>(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<PointerCanvas, bool> DrawOnlyPointsProperty =
public static readonly DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty =
AvaloniaProperty.RegisterDirect<PointerCanvas, bool>(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<PointerCanvas, string?> StatusProperty =
AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v,
public static readonly DirectProperty<PointerCanvas, string?> StatusProperty =
AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(Status), c => c.Status, (c, v) => c.Status = v,
defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
public string? Status

1
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -19,6 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />

1
samples/IntegrationTestApp/MainWindow.axaml

@ -25,6 +25,7 @@
<StackPanel DockPanel.Dock="Bottom" Margin="4" Orientation="Horizontal">
<TextBlock Margin="0,0,4,0">WindowState:</TextBlock>
<TextBlock Name="MainWindowState" Text="{Binding WindowState}"/>
<TextBlock Name="AppOverlayPopups" Margin="8 0"/>
</StackPanel>
<TabControl TabStripPlacement="Left" Name="MainTabs">

12
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<TextBlock>("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;

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

5
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();

1
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

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

@ -107,8 +107,6 @@ namespace Avalonia.Media
private readonly DrawingGroup _drawingGroup;
private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
private Matrix _transform;
private bool _disposed;
// Root drawing created by this DrawingContext.

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

@ -27,8 +27,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Pen"/> property.
/// </summary>
public static readonly StyledProperty<Pen?> PenProperty =
AvaloniaProperty.Register<GeometryDrawing, Pen?>(nameof(Pen));
public static readonly StyledProperty<IPen?> PenProperty =
AvaloniaProperty.Register<GeometryDrawing, IPen?>(nameof(Pen));
/// <summary>
/// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.

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

@ -151,9 +151,9 @@ namespace Avalonia.Media
}
/// <summary>
/// Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
/// Gets the conservative bounding box of the <see cref="GlyphRun"/>.
/// </summary>
public Size Size => PlatformImpl.Item.Size;
public Rect Bounds => PlatformImpl.Item.Bounds;
/// <summary>
///
@ -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--)
{

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

3
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
/// <value>
/// The points.
/// </value>
public AvaloniaList<Point> Points
public Points Points
{
get => GetValue(PointsProperty);
set => SetValue(PointsProperty, value);

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

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

98
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<GlyphInfo>, IDisposable
{
private GlyphInfo[]? _rentedBuffer;
private ArraySlice<GlyphInfo> _glyphInfos;
public ShapedBuffer(ReadOnlyMemory<char> text, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
_rentedBuffer = ArrayPool<GlyphInfo>.Shared.Rent(bufferLength);
Text = text;
GlyphInfos = new ArraySlice<GlyphInfo>(_rentedBuffer, 0, bufferLength);
_rentedBuffer = ArrayPool<GlyphInfo>.Shared.Rent(bufferLength);
_glyphInfos = new ArraySlice<GlyphInfo>(_rentedBuffer, 0, bufferLength);
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BidiLevel = bidiLevel;
@ -23,27 +25,70 @@ namespace Avalonia.Media.TextFormatting
internal ShapedBuffer(ReadOnlyMemory<char> text, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
Text = text;
GlyphInfos = glyphInfos;
_glyphInfos = glyphInfos;
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BidiLevel = bidiLevel;
}
internal ArraySlice<GlyphInfo> GlyphInfos { get; private set; }
public int Length
=> GlyphInfos.Length;
/// <summary>
/// The buffer's length.
/// </summary>
public int Length => _glyphInfos.Length;
/// <summary>
/// The buffer's glyph typeface.
/// </summary>
public IGlyphTypeface GlyphTypeface { get; }
/// <summary>
/// The buffers font rendering em size.
/// </summary>
public double FontRenderingEmSize { get; }
/// <summary>
/// The buffer's bidi level.
/// </summary>
public sbyte BidiLevel { get; }
/// <summary>
/// The buffer's reading direction.
/// </summary>
public bool IsLeftToRight => (BidiLevel & 1) == 0;
/// <summary>
/// The text that is represended by this buffer.
/// </summary>
public ReadOnlyMemory<char> Text { get; }
/// <summary>
/// Reverses the buffer.
/// </summary>
public void Reverse()
{
_glyphInfos.Span.Reverse();
}
public void Dispose()
{
if (_rentedBuffer is not null)
{
ArrayPool<GlyphInfo>.Shared.Return(_rentedBuffer);
_rentedBuffer = null;
_glyphInfos = ArraySlice<GlyphInfo>.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<GlyphInfo> GetEnumerator() => _glyphInfos.GetEnumerator();
/// <summary>
/// Finds a glyph index for given character index.
/// </summary>
@ -53,20 +98,19 @@ namespace Avalonia.Media.TextFormatting
/// </returns>
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<ShapedBuffer>(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<ShapedBuffer>(first, second);
}
int IReadOnlyCollection<GlyphInfo>.Count => GlyphInfos.Length;
public GlyphInfo this[int index]
{
get => GlyphInfos[index];
set => GlyphInfos[index] = value;
}
public IEnumerator<GlyphInfo> GetEnumerator() => GlyphInfos.GetEnumerator();
int IReadOnlyCollection<GlyphInfo>.Count => _glyphInfos.Length;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Dispose()
{
if (_rentedBuffer is not null)
{
ArrayPool<GlyphInfo>.Shared.Return(_rentedBuffer);
_rentedBuffer = null;
GlyphInfos = ArraySlice<GlyphInfo>.Empty; // ensure we don't misuse the returned array
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

10
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();
/// <inheritdoc/>
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)
{

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

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

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

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

155
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
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri? baseUri = null)
{
return GetAsset(uri, baseUri) != null;
return TryGetAsset(uri, baseUri, out _);
}
/// <summary>
@ -94,21 +95,27 @@ namespace Avalonia.Platform
/// </exception>
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;
}
/// <summary>
@ -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<Uri>();
.Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
.Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>();
}
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<Uri>();
}
if (asm.AvaloniaResources == null)
if (assembly?.AvaloniaResources == null)
{
return Enumerable.Empty<Uri>();
}
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<Uri>();
}
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"))

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

@ -10,11 +10,10 @@ namespace Avalonia.Platform
[Unstable]
public interface IGlyphRunImpl : IDisposable
{
/// <summary>
/// Gets the conservative bounding box of the glyph run./>.
/// </summary>
Size Size { get; }
Rect Bounds { get; }
/// <summary>
/// Gets the baseline origin of the glyph run./>.

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

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

8
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;

5
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@ -16,12 +16,11 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="transform">The transform.</param>
/// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run to draw.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public GlyphRunNode(
Matrix transform,
IImmutableBrush foreground,
IRef<IGlyphRunImpl> 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
}
/// <inheritdoc/>
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()
{

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

13
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;

15
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<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
private List<string>? _classes;
private Type? _targetType;
private string? _selectorString;
@ -81,7 +80,7 @@ namespace Avalonia.Styling
/// <summary>
/// The style classes which the selector matches.
/// </summary>
public IList<string> Classes => _classes.Value;
public IList<string> Classes => _classes ??= new();
/// <inheritdoc/>
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(":"))
{

10
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];

17
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();
}
}
}
/// <summary>
/// Computes the <see cref="HasMirrorTransform"/> value according to the
/// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>

2
src/Avalonia.Controls.DataGrid/Themes/Simple.xaml

@ -202,7 +202,7 @@
<Style Selector="^ /template/ Rectangle#BackgroundRectangle">
<Setter Property="IsVisible" Value="False" />
<Setter Property="Fill" Value="{DynamicResource HighlightBrush}" />
<Setter Property="Fill" Value="{DynamicResource HighlightBrush2}" />
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">

2
src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs

@ -26,7 +26,7 @@ namespace Avalonia.Layout
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
StackPanel.OrientationProperty.AddOwner<StackPanel>();
StackPanel.OrientationProperty.AddOwner<StackLayout>();
/// <summary>
/// Defines the <see cref="Spacing"/> property.

9
src/Avalonia.Controls/MaskedTextBox.cs

@ -31,9 +31,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<string?> MaskProperty =
AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
public static new readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PasswordChar), '\0');
public static readonly StyledProperty<char> PromptCharProperty =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');
@ -51,6 +48,12 @@ namespace Avalonia.Controls
private bool _resetOnSpace = true;
static MaskedTextBox()
{
PasswordCharProperty
.OverrideDefaultValue<MaskedTextBox>('\0');
}
public MaskedTextBox() { }
/// <summary>

34
src/Avalonia.Controls/MenuItem.cs

@ -13,6 +13,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -85,16 +86,16 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="PointerEnteredItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerEnteredItemEvent =
RoutedEvent.Register<MenuItem, PointerEventArgs>(
public static readonly RoutedEvent<RoutedEventArgs> PointerEnteredItemEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(
nameof(PointerEnteredItem),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerExitedItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerExitedItemEvent =
RoutedEvent.Register<MenuItem, PointerEventArgs>(
public static readonly RoutedEvent<RoutedEventArgs> PointerExitedItemEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(
nameof(PointerExitedItem),
RoutingStrategies.Bubble);
@ -184,7 +185,7 @@ namespace Avalonia.Controls
/// <remarks>
/// A bubbling version of the <see cref="InputElement.PointerEntered"/> event for menu items.
/// </remarks>
public event EventHandler<PointerEventArgs>? PointerEnteredItem
public event EventHandler<RoutedEventArgs>? PointerEnteredItem
{
add { AddHandler(PointerEnteredItemEvent, value); }
remove { RemoveHandler(PointerEnteredItemEvent, value); }
@ -196,7 +197,7 @@ namespace Avalonia.Controls
/// <remarks>
/// A bubbling version of the <see cref="InputElement.PointerExited"/> event for menu items.
/// </remarks>
public event EventHandler<PointerEventArgs>? PointerExitedItem
public event EventHandler<RoutedEventArgs>? PointerExitedItem
{
add { AddHandler(PointerExitedItemEvent, value); }
remove { RemoveHandler(PointerExitedItemEvent, value); }
@ -437,20 +438,14 @@ namespace Avalonia.Controls
protected override void OnPointerEntered(PointerEventArgs e)
{
base.OnPointerEntered(e);
var point = e.GetCurrentPoint(null);
RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position,
e.Timestamp, point.Properties, e.KeyModifiers));
RaiseEvent(new RoutedEventArgs(PointerEnteredItemEvent));
}
/// <inheritdoc/>
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
var point = e.GetCurrentPoint(null);
RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position,
e.Timestamp, point.Properties, e.KeyModifiers));
RaiseEvent(new RoutedEventArgs(PointerExitedItemEvent));
}
/// <summary>
@ -686,6 +681,12 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
private void PopupOpened(object? sender, EventArgs e)
{
// If we're using overlay popups, there's a chance we need to do a layout pass before
// the child items are added to the visual tree. If we don't do this here, then
// selection breaks.
if (Presenter?.IsAttachedToVisualTree == false)
UpdateLayout();
var selected = SelectedIndex;
if (selected != -1)
@ -705,6 +706,11 @@ namespace Avalonia.Controls
SelectedItem = null;
}
private void UpdateLayout()
{
(VisualRoot as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
}
void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
void IClickableControl.RaiseClick()

2
src/Avalonia.Controls/NativeMenuItem.cs

@ -137,7 +137,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object?> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
Button.CommandParameterProperty.AddOwner<NativeMenuItem>();
public static readonly DirectProperty<NativeMenuItem, bool> IsEnabledProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(IsEnabled), o => o.IsEnabled, (o, v) => o.IsEnabled = v, true);

35
src/Avalonia.Controls/Panel.cs

@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Reactive;
using Avalonia.Styling;
namespace Avalonia.Controls
{
@ -59,6 +58,11 @@ namespace Avalonia.Controls
set { SetValue(BackgroundProperty, value); }
}
/// <summary>
/// Gets whether the <see cref="Panel"/> hosts the items created by an <see cref="ItemsPresenter"/>.
/// </summary>
public bool IsItemsHost { get; internal set; }
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
{
add
@ -129,24 +133,29 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
List<Control> controls;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
controls = e.NewItems!.OfType<Control>().ToList();
LogicalChildren.InsertRange(e.NewStartingIndex, controls);
if (!IsItemsHost)
{
LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Control>().ToList());
}
VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
break;
case NotifyCollectionChangedAction.Move:
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
if (!IsItemsHost)
{
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
}
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems!.OfType<Control>().ToList();
LogicalChildren.RemoveAll(controls);
if (!IsItemsHost)
{
LogicalChildren.RemoveAll(e.OldItems!.OfType<Control>().ToList());
}
VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
break;
@ -155,7 +164,10 @@ namespace Avalonia.Controls
{
var index = i + e.OldStartingIndex;
var child = (Control)e.NewItems![i]!;
LogicalChildren[index] = child;
if (!IsItemsHost)
{
LogicalChildren[index] = child;
}
VisualChildren[index] = child;
}
break;
@ -200,6 +212,7 @@ namespace Avalonia.Controls
return child is Control control ? Children.IndexOf(control) : -1;
}
/// <inheritdoc />
public bool TryGetTotalCount(out int count)
{
count = Children.Count;

4
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -301,7 +301,7 @@ namespace Avalonia.Controls.Platform
e.Handled = true;
}
protected internal virtual void PointerEntered(object? sender, PointerEventArgs e)
protected internal virtual void PointerEntered(object? sender, RoutedEventArgs e)
{
var item = GetMenuItem(e.Source as Control);
@ -368,7 +368,7 @@ namespace Avalonia.Controls.Platform
}
}
protected internal virtual void PointerExited(object? sender, PointerEventArgs e)
protected internal virtual void PointerExited(object? sender, RoutedEventArgs e)
{
var item = GetMenuItem(e.Source as Control);

1
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -167,6 +167,7 @@ namespace Avalonia.Controls.Presenters
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);

3
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -2,7 +2,6 @@ using System;
using System.Collections.Specialized;
using Avalonia.Media;
using Avalonia.Reactive;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -38,7 +37,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="DefaultFocusAdorner"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<Control>?> DefaultFocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<Control>?>(nameof(DefaultFocusAdorner));
AvaloniaProperty.Register<AdornerLayer, ITemplate<Control>?>(nameof(DefaultFocusAdorner));
private static readonly AttachedProperty<AdornedElementInfo?> s_adornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo?>("AdornedElementInfo");

2
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -51,7 +51,7 @@ namespace Avalonia.Controls.Primitives
}
/// <inheritdoc />
protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
protected internal override Interactive? InteractiveParent => Parent as Interactive;
/// <inheritdoc />
public void Dispose() => Hide();

17
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -405,22 +405,5 @@ namespace Avalonia.Controls.Primitives
}
}
}
internal override void OnTemplatedParentControlThemeChanged()
{
base.OnTemplatedParentControlThemeChanged();
var count = VisualChildren.Count;
var templatedParent = TemplatedParent;
for (var i = 0; i < count; ++i)
{
if (VisualChildren[i] is TemplatedControl child &&
child.TemplatedParent == templatedParent)
{
child.OnTemplatedParentControlThemeChanged();
}
}
}
}
}

6
src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs

@ -17,16 +17,16 @@ namespace Avalonia.Controls.PullToRefresh
private double _interactionRatio;
private bool _entered;
public DirectProperty<RefreshInfoProvider, bool> IsInteractingForRefreshProperty =
public static readonly DirectProperty<RefreshInfoProvider, bool> IsInteractingForRefreshProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, bool>(nameof(IsInteractingForRefresh),
s => s.IsInteractingForRefresh, (s, o) => s.IsInteractingForRefresh = o);
public DirectProperty<RefreshInfoProvider, double> ExecutionRatioProperty =
public static readonly DirectProperty<RefreshInfoProvider, double> ExecutionRatioProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(ExecutionRatio),
s => s.ExecutionRatio);
public DirectProperty<RefreshInfoProvider, double> InteractionRatioProperty =
public static readonly DirectProperty<RefreshInfoProvider, double> InteractionRatioProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(InteractionRatio),
s => s.InteractionRatio, (s, o) => s.InteractionRatio = o);

2
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="RefreshInfoProvider"/> property.
/// </summary>
internal DirectProperty<RefreshVisualizer, RefreshInfoProvider?> RefreshInfoProviderProperty =
internal static readonly DirectProperty<RefreshVisualizer, RefreshInfoProvider?> RefreshInfoProviderProperty =
AvaloniaProperty.RegisterDirect<RefreshVisualizer, RefreshInfoProvider?>(nameof(RefreshInfoProvider),
s => s.RefreshInfoProvider, (s, o) => s.RefreshInfoProvider = o);

2
src/Avalonia.Controls/TrayIcon.cs

@ -95,7 +95,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object?> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
Button.CommandParameterProperty.AddOwner<TrayIcon>();
/// <summary>
/// Defines the <see cref="TrayIcons"/> attached property.

2
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@ -141,7 +141,7 @@ namespace Avalonia.Controls.Utils
else
{
var eCapture = e;
Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l));
Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l), DispatcherPriority.Send);
}
}
}

12
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@ -80,16 +80,8 @@ namespace Avalonia.Controls
remove => _scrollInvalidated -= value;
}
bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect)
{
throw new NotImplementedException();
}
Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from)
{
throw new NotImplementedException();
}
bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) => false;
Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) => null;
void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e) => _scrollInvalidated?.Invoke(this, e);
protected override Size MeasureOverride(Size availableSize)

2
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Controls
(o, v) => o.Header = v);
public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(Header), o => o.IsPresent,
AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(IsPresent), o => o.IsPresent,
(o, v) => o.IsPresent = v);
public static readonly DirectProperty<ThicknessEditor, double> LeftProperty =

4
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -28,7 +28,7 @@
</Style>
<Style Selector="ListBoxItem.handled" >
<Setter Property="Background" Value="#d9ffdc" />
<Setter Property="Background" Value="#34c940" />
<Setter Property="Foreground" Value="Black" />
</Style>
</UserControl.Styles>
@ -129,7 +129,7 @@
</DockPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3" Spacing="2" Margin="0,2">
<StackPanel Orientation="Horizontal" Grid.Row="3" Spacing="2" Margin="2">
<Button Content="Clear" Command="{Binding Clear}" />
</StackPanel>

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf

Binary file not shown.

13
src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<PackageId>Avalonia.Fonts.Inter</PackageId>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

3
src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs

@ -0,0 +1,3 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Fonts.Inter")]

4
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);

2
src/Avalonia.Themes.Fluent/Accents/Base.xaml

@ -3,7 +3,7 @@
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Themes.Fluent/Assets#Inter</FontFamily>
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Fonts.Inter/Assets#Inter, $Default</FontFamily>
<sys:Double x:Key="ControlContentThemeFontSize">14</sys:Double>
<SolidColorBrush x:Key="SystemControlTransparentBrush" Color="Transparent" />

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf

Binary file not shown.

3
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@ -9,8 +9,7 @@
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<AvaloniaResource Include="**/*.xaml" />
<AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="**/*.xaml" />
</ItemGroup>
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\BuildTargets.targets" />

1
src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml

@ -4,6 +4,7 @@
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

6
src/Avalonia.Themes.Simple/Accents/Base.xaml

@ -17,6 +17,7 @@
<Color x:Key="ThemeControlHighlightHighColor">#FF808080</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="HighlightColor">#FF086F9E</Color>
<Color x:Key="HighlightColor2">#FF096085</Color>
<SolidColorBrush x:Key="ThemeBackgroundBrush" Color="{StaticResource ThemeBackgroundColor}" />
<SolidColorBrush x:Key="ThemeBorderLowBrush" Color="{StaticResource ThemeBorderLowColor}" />
@ -33,6 +34,8 @@
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
<SolidColorBrush x:Key="HighlightBrush2" Color="{StaticResource HighlightColor2}" />
<SolidColorBrush x:Key="RefreshVisualizerForeground" Color="Black" />
<SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" />
</ResourceDictionary>
@ -51,6 +54,7 @@
<Color x:Key="ThemeControlHighlightHighColor">#FF505050</Color>
<Color x:Key="ThemeForegroundColor">#FFDEDEDE</Color>
<Color x:Key="HighlightColor">#FF119EDA</Color>
<Color x:Key="HighlightColor2">#FF096085</Color>
<SolidColorBrush x:Key="ThemeBackgroundBrush" Color="{StaticResource ThemeBackgroundColor}" />
<SolidColorBrush x:Key="ThemeBorderLowBrush" Color="{StaticResource ThemeBorderLowColor}" />
@ -66,11 +70,13 @@
<SolidColorBrush x:Key="ThemeControlHighlightHighBrush" Color="{StaticResource ThemeControlHighlightHighColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
<SolidColorBrush x:Key="HighlightBrush2" Color="{StaticResource HighlightColor2}" />
<SolidColorBrush x:Key="RefreshVisualizerForeground" Color="White" />
<SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Fonts.Inter/Assets#Inter, $Default</FontFamily>
<Color x:Key="ThemeAccentColor">#CC119EDA</Color>
<Color x:Key="ThemeAccentColor2">#99119EDA</Color>
<Color x:Key="ThemeAccentColor3">#66119EDA</Color>

1
src/Avalonia.Themes.Simple/Controls/ComboBox.xaml

@ -3,6 +3,7 @@
<ControlTheme x:Key="{x:Type ComboBox}"
TargetType="ComboBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />

1
src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml

@ -30,7 +30,6 @@
Background="Transparent">
<Canvas.Styles>
<Style Selector="ToolTip">
<Setter Property="Background" Value="{DynamicResource ErrorLowBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}" />
</Style>
</Canvas.Styles>

1
src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml

@ -5,6 +5,7 @@
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>

4
src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml

@ -4,9 +4,7 @@
TargetType="OverlayPopupHost">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">
<ControlTemplate>
<!-- Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test -->

4
src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml

@ -6,9 +6,7 @@
<Setter Property="TransparencyLevelHint" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
<Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">
<ControlTemplate>
<LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">

1
src/Avalonia.Themes.Simple/Controls/Window.xaml

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTheme x:Key="{x:Type Window}"
TargetType="Window">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="TransparencyBackgroundFallback" Value="{DynamicResource HighlightForegroundColor}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />

27
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<XamlAstXamlPropertyValueNode>()
.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")
{

16
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<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
private readonly ICompiledBindingPathElement[] _elements;
public CompiledBindingPath() { }
public CompiledBindingPath()
=> _elements = Array.Empty<ICompiledBindingPathElement>();
internal CompiledBindingPath(IEnumerable<ICompiledBindingPathElement> bindingPath, object rawSource)
internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource)
{
_elements = new List<ICompiledBindingPathElement>(bindingPath);
_elements = elements;
RawSource = rawSource;
}
@ -78,13 +78,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal IEnumerable<ICompiledBindingPathElement> Elements => _elements;
internal SourceMode SourceMode => _elements.OfType<IControlSourceBindingPathElement>().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<ICompiledBindingPathElement>) _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

43
src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs

@ -8,11 +8,25 @@ namespace Avalonia.Skia
/// </summary>
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; }
}
}

11
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);
}
/// <inheritdoc />

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

23
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(

43
src/Skia/Avalonia.Skia/GeometryGroupImpl.cs

@ -11,26 +11,51 @@ namespace Avalonia.Skia
{
public GeometryGroupImpl(FillRule fillRule, IReadOnlyList<Geometry> 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; }
}
}

31
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!);
/// <inheritdoc />
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; }
/// <inheritdoc />
public bool FillContains(Point point)
{
return PathContainsCore(EffectivePath, point);
return PathContainsCore(FillPath, point);
}
/// <inheritdoc />
@ -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
/// <inheritdoc />
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);
}
/// <inheritdoc />
@ -128,7 +125,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
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
/// <inheritdoc />
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;

4
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
/// </summary>
public SKTextBlob TextBlob { get; }
public Size Size { get; }
public Rect Bounds { get; }
public Point BaselineOrigin { get; }

1
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@ -13,7 +13,6 @@ namespace Avalonia.Skia.Helpers
/// </summary>
/// <param name="canvas"></param>
/// <param name="dpi"></param>
/// <param name="visualBrushRenderer"></param>
/// <returns>DrawingContext</returns>
public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi)
{

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

4
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);
}
/// <inheritdoc />

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

90
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;
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
/// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
/// <param name="stroke">An existing Skia <see cref="SKPath"/> for the stroke.</param>
/// <param name="fill">An existing Skia <see cref="SKPath"/> for the fill, can also be null or the same as the stroke</param>
/// <param name="bounds">Precomputed path bounds.</param>
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();
}
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
/// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
public StreamGeometryImpl(SKPath path) : this(path, path.TightBounds.ToAvaloniaRect())
private StreamGeometryImpl(SKPath path) : this(path, path, default(Rect))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public StreamGeometryImpl() : this(CreateEmptyPath(), default)
public StreamGeometryImpl() : this(CreateEmptyPath())
{
}
/// <inheritdoc />
public override SKPath EffectivePath => _effectivePath;
public override SKPath? StrokePath => _strokePath;
/// <inheritdoc />
public override SKPath? FillPath => _fillPath;
/// <inheritdoc />
public override Rect Bounds => _bounds;
@ -47,7 +51,9 @@ namespace Avalonia.Skia
/// <inheritdoc />
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);
}
/// <inheritdoc />
@ -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);
/// <summary>
/// Initializes a new instance of the <see cref="StreamContext"/> class.
@ -83,52 +92,79 @@ namespace Avalonia.Skia
public StreamContext(StreamGeometryImpl geometryImpl)
{
_geometryImpl = geometryImpl;
_path = _geometryImpl._effectivePath;
}
/// <inheritdoc />
/// <remarks>Will update bounds of passed geometry.</remarks>
public void Dispose()
{
_geometryImpl._bounds = _path.TightBounds.ToAvaloniaRect();
_geometryImpl._bounds = Stroke.TightBounds.ToAvaloniaRect();
_geometryImpl.InvalidateCaches();
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
@ -136,14 +172,16 @@ namespace Avalonia.Skia
{
if (isClosed)
{
_path.Close();
Stroke.Close();
if (Duplicate)
Fill.Close();
}
}
/// <inheritdoc />
public void SetFillRule(FillRule fillRule)
{
_path.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
}
}
}

4
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;

21
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);
}
}
/// <inheritdoc />
public override SKPath? EffectivePath { get; }
public override SKPath? StrokePath { get; }
/// <inheritdoc />
public override SKPath? FillPath { get; }
/// <inheritdoc />
public IGeometryImpl SourceGeometry { get; }

2
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -387,7 +387,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
{
using (var brush = CreateBrush(foreground, glyphRun.Item.Size))
using (var brush = CreateBrush(foreground, glyphRun.Item.Bounds.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save