Browse Source

Merge branch 'master' into fixes/core/tools/AvaloniaPropertyAnalyzer/AVP1031

pull/10468/head
workgroupengineering 4 years ago
committed by GitHub
parent
commit
4afa0ad0c7
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. 1
      samples/ControlCatalog/ControlCatalog.csproj
  5. 8
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  6. 8
      samples/ControlCatalog/Pages/PointerCanvas.cs
  7. 1
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  8. 1
      samples/IntegrationTestApp/MainWindow.axaml
  9. 12
      samples/IntegrationTestApp/MainWindow.axaml.cs
  10. 22
      samples/IntegrationTestApp/Program.cs
  11. 5
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  12. 1
      src/Avalonia.Base/Media/DrawingContext.cs
  13. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  14. 4
      src/Avalonia.Base/Media/GeometryDrawing.cs
  15. 3
      src/Avalonia.Base/Media/PolyLineSegment.cs
  16. 6
      src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
  17. 98
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  18. 6
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  19. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  20. 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  21. 21
      src/Avalonia.Base/StyledElement.cs
  22. 13
      src/Avalonia.Base/Styling/OrSelector.cs
  23. 15
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  24. 10
      src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
  25. 17
      src/Avalonia.Base/Visual.cs
  26. 2
      src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
  27. 2
      src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs
  28. 9
      src/Avalonia.Controls/MaskedTextBox.cs
  29. 34
      src/Avalonia.Controls/MenuItem.cs
  30. 2
      src/Avalonia.Controls/NativeMenuItem.cs
  31. 4
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  32. 3
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  33. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  34. 17
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  35. 6
      src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
  36. 2
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  37. 2
      src/Avalonia.Controls/TrayIcon.cs
  38. 2
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs
  39. 12
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  40. 2
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  41. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  42. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf
  43. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf
  44. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf
  45. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf
  46. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf
  47. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf
  48. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf
  49. 13
      src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj
  50. 3
      src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs
  51. 2
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  52. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
  53. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
  54. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
  55. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
  56. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
  57. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
  58. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
  59. 3
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  60. 1
      src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
  61. 6
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  62. 1
      src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
  63. 1
      src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
  64. 1
      src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
  65. 4
      src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
  66. 4
      src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
  67. 1
      src/Avalonia.Themes.Simple/Controls/Window.xaml
  68. 16
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  69. 43
      src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
  70. 9
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  71. 5
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  72. 23
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  73. 43
      src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
  74. 31
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  75. 1
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  76. 5
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  77. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  78. 5
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  79. 90
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  80. 4
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  81. 21
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  82. 4
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  83. 1
      src/tools/DevAnalyzers/DevAnalyzers.csproj
  84. 1
      src/tools/DevGenerators/DevGenerators.csproj
  85. 43
      tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
  86. 25
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  87. 25
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  88. 2
      tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs
  89. 2
      tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
  90. 2
      tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs
  91. 14
      tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs
  92. 17
      tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs
  93. 38
      tests/Avalonia.IntegrationTests.Appium/DefaultAppFixture.cs
  94. 9
      tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs
  95. 2
      tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
  96. 2
      tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs
  97. 16
      tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
  98. 2
      tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs
  99. 19
      tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs
  100. 10
      tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.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

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"/>.

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

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

6
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@ -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/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/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)

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

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

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")]

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}" />

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

9
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);
}
}
}
@ -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;

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

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

1
src/tools/DevAnalyzers/DevAnalyzers.csproj

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>

1
src/tools/DevGenerators/DevGenerators.csproj

@ -4,6 +4,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>

43
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<ThemedControl>().Class("foo"))
{
Setters = { new Setter(StyledElement.ThemeProperty, theme) }
});
root.LayoutManager.ExecuteLayoutPass();
var border = Assert.IsType<Border>(target.VisualChild);
var inner = Assert.IsType<Border>(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<ThemedControl>((o, n) => new Border());
var template = new FuncControlTemplate<ThemedControl>(
(o, n) => new Border() { Child = new Border() });
return new ControlTheme
{

25
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<IMenu>();
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<IMenu>();
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<IMenu>();
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<IMenu>();
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<IMenu>();
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<IMenu>();
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<IMenu>();
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);

25
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:

2
tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs

@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public AutomationTests(TestAppFixture fixture)
public AutomationTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

2
tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs

@ -9,7 +9,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public ButtonTests(TestAppFixture fixture)
public ButtonTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

2
tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs

@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public CheckBoxTests(TestAppFixture fixture)
public CheckBoxTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

14
tests/Avalonia.IntegrationTests.Appium/CollectionDefinitions.cs

@ -0,0 +1,14 @@
using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
[CollectionDefinition("Default")]
public class DefaultCollection : ICollectionFixture<DefaultAppFixture>
{
}
[CollectionDefinition("OverlayPopups")]
public class OverlayPopupsCollection : ICollectionFixture<OverlayPopupsAppFixture>
{
}
}

17
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<AppiumWebElement> _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) { }
}
}
}

38
tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs → 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<AppiumWebElement>(
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<AppiumWebElement>(
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<AppiumWebElement> Session { get; }
public void Dispose()

9
tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs

@ -1,9 +0,0 @@
using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
[CollectionDefinition("Default")]
public class DefaultCollection : ICollectionFixture<TestAppFixture>
{
}
}

2
tests/Avalonia.IntegrationTests.Appium/GestureTests.cs

@ -11,7 +11,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public GestureTests(TestAppFixture fixture)
public GestureTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

2
tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs

@ -11,7 +11,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public ListBoxTests(TestAppFixture fixture)
public ListBoxTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

16
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<AppiumWebElement> _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) { }
}
}
}

2
tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs

@ -8,7 +8,7 @@ namespace Avalonia.IntegrationTests.Appium
{
private readonly AppiumDriver<AppiumWebElement> _session;
public NativeMenuTests(TestAppFixture fixture)
public NativeMenuTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

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

10
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<AppiumWebElement> _session;
public RadioButtonTests(TestAppFixture fixture)
public RadioButtonTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

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

Loading…
Cancel
Save