Browse Source

Merge branch 'master' into visual-fixes

pull/8569/head
Steve 4 years ago
committed by GitHub
parent
commit
e2ffdbdc87
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Avalonia.sln
  2. 6
      azure-pipelines.yml
  3. 6
      samples/BindingDemo/App.xaml
  4. 2
      samples/BindingDemo/BindingDemo.csproj
  5. 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  6. 8
      samples/ControlCatalog/App.xaml
  7. 20
      samples/ControlCatalog/App.xaml.cs
  8. 2
      samples/ControlCatalog/ControlCatalog.csproj
  9. 4
      samples/ControlCatalog/MainView.xaml
  10. 20
      samples/ControlCatalog/MainView.xaml.cs
  11. 10
      samples/ControlCatalog/Models/CatalogTheme.cs
  12. 1
      samples/ControlCatalog/Pages/ButtonsPage.xaml
  13. 9
      samples/ControlCatalog/Pages/DataGridPage.xaml
  14. 26
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  15. 15
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  16. 2
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  17. 44
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  18. 2
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  19. 27
      samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs
  20. 3
      samples/PlatformSanityChecks/App.xaml
  21. 2
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  22. 5
      samples/Previewer/App.xaml
  23. 2
      samples/Previewer/Previewer.csproj
  24. 8
      samples/RenderDemo/App.xaml
  25. 6
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  26. 286
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  27. 16
      samples/VirtualizationDemo/App.xaml
  28. 2
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  29. 5
      samples/interop/Direct3DInteropSample/App.paml
  30. 2
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  31. 15
      src/Android/Avalonia.Android/AndroidPlatform.cs
  32. 9
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  33. 33
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  34. 56
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  35. 5
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  36. 8
      src/Avalonia.Base/Controls/Templates/ITemplateResult.cs
  37. 3
      src/Avalonia.Base/Controls/Templates/TemplateResult.cs
  38. 5
      src/Avalonia.Base/Input/Cursor.cs
  39. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  40. 11
      src/Avalonia.Base/Input/GotFocusEventArgs.cs
  41. 5
      src/Avalonia.Base/Input/IInputRoot.cs
  42. 13
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  43. 12
      src/Avalonia.Base/Input/IMouseDevice.cs
  44. 14
      src/Avalonia.Base/Input/IPointerDevice.cs
  45. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  46. 12
      src/Avalonia.Base/Input/KeyGesture.cs
  47. 54
      src/Avalonia.Base/Input/MouseDevice.cs
  48. 19
      src/Avalonia.Base/Input/PenDevice.cs
  49. 62
      src/Avalonia.Base/Input/PointerEventArgs.cs
  50. 2
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  51. 5
      src/Avalonia.Base/Input/Raw/RawDragEvent.cs
  52. 3
      src/Avalonia.Base/Input/Raw/RawTouchEventArgs.cs
  53. 11
      src/Avalonia.Base/Input/TouchDevice.cs
  54. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  55. 111
      src/Avalonia.Base/Media/GlyphRun.cs
  56. 2
      src/Avalonia.Base/Media/TextDecoration.cs
  57. 24
      src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
  58. 17
      src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
  59. 26
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  60. 58
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  61. 2
      src/Avalonia.Base/Media/TextFormatting/TextLine.cs
  62. 737
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  63. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  64. 4
      src/Avalonia.Base/Metadata/IAddChild.cs
  65. 14
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  66. 4
      src/Avalonia.Base/Platform/IGeometryImpl.cs
  67. 8
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  68. 16
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  69. 2
      src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs
  70. 4
      src/Avalonia.Base/Platform/Storage/IStorageFile.cs
  71. 11
      src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
  72. 2
      src/Avalonia.Base/Platform/Storage/IStorageItem.cs
  73. 5
      src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
  74. 11
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  75. 4
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  76. 6
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
  77. 4
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
  78. 4
      src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
  79. 4
      src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
  80. 4
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  81. 4
      src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
  82. 8
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
  83. 4
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
  84. 4
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
  85. 4
      src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
  86. 4
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  87. 4
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  88. 4
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  89. 4
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  90. 4
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  91. 17
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  92. 4
      src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
  93. 4
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs
  94. 7
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  95. 4
      src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
  96. 4
      src/Avalonia.Base/Rendering/Composition/Enums.cs
  97. 4
      src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
  98. 4
      src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
  99. 4
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  100. 4
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

2
Avalonia.sln

@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\W
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Default", "src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Simple", "src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}"
EndProject EndProject

6
azure-pipelines.yml

@ -59,7 +59,7 @@ jobs:
variables: variables:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
pool: pool:
vmImage: 'macOS-10.15' vmImage: 'macos-12'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.418' displayName: 'Use .NET Core SDK 3.1.418'
@ -91,10 +91,10 @@ jobs:
inputs: inputs:
actions: 'build' actions: 'build'
scheme: '' scheme: ''
sdk: 'macosx11.1' sdk: 'macosx12.3'
configuration: 'Release' configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '12' # Options: 8, 9, default, specifyPath xcodeVersion: '13' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2 - task: CmdLine@2

6
samples/BindingDemo/App.xaml

@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App"> x:Class="BindingDemo.App">
<Application.Styles> <Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/BindingDemo/BindingDemo.csproj

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

1
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -9,7 +9,6 @@
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat> <AndroidPackageFormat>apk</AndroidPackageFormat>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="..\..\build\Assets\Icon.png"> <AndroidResource Include="..\..\build\Assets\Icon.png">

8
samples/ControlCatalog/App.xaml

@ -5,6 +5,13 @@
x:CompileBindings="True" x:CompileBindings="True"
Name="Avalonia ControlCatalog" Name="Avalonia ControlCatalog"
x:Class="ControlCatalog.App"> x:Class="ControlCatalog.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles> <Application.Styles>
<Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3"> <Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3">
<Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextWrapping" Value="Wrap" />
@ -29,7 +36,6 @@
<Style Selector="Label.h3"> <Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="12" />
</Style> </Style>
<StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</Application.Styles> </Application.Styles>
<TrayIcon.Icons> <TrayIcon.Icons>
<TrayIcons> <TrayIcons>

20
samples/ControlCatalog/App.xaml.cs

@ -5,7 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Themes.Default; using Avalonia.Themes.Simple;
using Avalonia.Themes.Fluent; using Avalonia.Themes.Fluent;
using ControlCatalog.ViewModels; using ControlCatalog.ViewModels;
@ -23,9 +23,9 @@ namespace ControlCatalog
Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml") Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml")
}; };
public static readonly StyleInclude ColorPickerDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{ {
Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml") Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml")
}; };
public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
@ -33,16 +33,16 @@ namespace ControlCatalog
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
}; };
public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{ {
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml")
}; };
public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
public static SimpleTheme Default = new SimpleTheme(new Uri("avares://ControlCatalog/Styles")); public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles"));
public static Styles DefaultLight = new Styles public static Styles SimpleLight = new Styles
{ {
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{ {
@ -56,10 +56,10 @@ namespace ControlCatalog
{ {
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
}, },
Default Simple
}; };
public static Styles DefaultDark = new Styles public static Styles SimpleDark = new Styles
{ {
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{ {
@ -73,7 +73,7 @@ namespace ControlCatalog
{ {
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
}, },
Default Simple
}; };
public override void Initialize() public override void Initialize()

2
samples/ControlCatalog/ControlCatalog.csproj

@ -29,7 +29,7 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.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.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

4
samples/ControlCatalog/MainView.xaml

@ -187,8 +187,8 @@
<ComboBox.Items> <ComboBox.Items>
<models:CatalogTheme>FluentLight</models:CatalogTheme> <models:CatalogTheme>FluentLight</models:CatalogTheme>
<models:CatalogTheme>FluentDark</models:CatalogTheme> <models:CatalogTheme>FluentDark</models:CatalogTheme>
<models:CatalogTheme>DefaultLight</models:CatalogTheme> <models:CatalogTheme>SimpleLight</models:CatalogTheme>
<models:CatalogTheme>DefaultDark</models:CatalogTheme> <models:CatalogTheme>SimpleDark</models:CatalogTheme>
</ComboBox.Items> </ComboBox.Items>
</ComboBox> </ComboBox>
<ComboBox x:Name="TransparencyLevels" <ComboBox x:Name="TransparencyLevels"

20
samples/ControlCatalog/MainView.xaml.cs

@ -58,19 +58,19 @@ namespace ControlCatalog
Application.Current.Styles[1] = App.ColorPickerFluent; Application.Current.Styles[1] = App.ColorPickerFluent;
Application.Current.Styles[2] = App.DataGridFluent; Application.Current.Styles[2] = App.DataGridFluent;
} }
else if (theme == CatalogTheme.DefaultLight) else if (theme == CatalogTheme.SimpleLight)
{ {
App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light; App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light;
Application.Current.Styles[0] = App.DefaultLight; Application.Current.Styles[0] = App.SimpleLight;
Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridDefault; Application.Current.Styles[2] = App.DataGridSimple;
} }
else if (theme == CatalogTheme.DefaultDark) else if (theme == CatalogTheme.SimpleDark)
{ {
App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark; App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark;
Application.Current.Styles[0] = App.DefaultDark; Application.Current.Styles[0] = App.SimpleDark;
Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridDefault; Application.Current.Styles[2] = App.DataGridSimple;
} }
} }
}; };

10
samples/ControlCatalog/Models/CatalogTheme.cs

@ -1,14 +1,10 @@
using System; namespace ControlCatalog.Models
using System.Collections.Generic;
using System.Text;
namespace ControlCatalog.Models
{ {
public enum CatalogTheme public enum CatalogTheme
{ {
FluentLight, FluentLight,
FluentDark, FluentDark,
DefaultLight, SimpleLight,
DefaultDark SimpleDark
} }
} }

1
samples/ControlCatalog/Pages/ButtonsPage.xaml

@ -90,6 +90,7 @@
</Style> </Style>
</Button.Styles> </Button.Styles>
</Button> </Button>
<Button Classes="accent">Accent</Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"

9
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -10,11 +10,11 @@
<TextBlock Text="{Binding}"/> <TextBlock Text="{Binding}"/>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
<ControlTheme x:Key="GdpCell" TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" />
</ControlTheme>
</UserControl.Resources> </UserControl.Resources>
<UserControl.Styles> <UserControl.Styles>
<Style Selector="DataGridCell.gdp">
<Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" />
</Style>
<Style Selector="DataGridColumnHeader:nth-last-child(1)"> <Style Selector="DataGridColumnHeader:nth-last-child(1)">
<Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontWeight" Value="Bold" />
</Style> </Style>
@ -54,7 +54,8 @@
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" /> <DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" /> <DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" /> <DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" CellStyleClasses="gdp" <DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*"
CellTheme="{StaticResource GdpCell}"
MinWidth="200" MinWidth="200"
IsVisible="{Binding #ShowGDP.IsChecked}"/> IsVisible="{Binding #ShowGDP.IsChecked}"/>
</DataGrid.Columns> </DataGrid.Columns>

26
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -195,10 +195,10 @@ namespace ControlCatalog.Pages
{ {
// Sync disposal of StreamWriter is not supported on WASM // Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
await using var stream = await file.OpenWrite(); await using var stream = await file.OpenWriteAsync();
await using var reader = new System.IO.StreamWriter(stream); await using var reader = new System.IO.StreamWriter(stream);
#else #else
using var stream = await file.OpenWrite(); using var stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream); using var reader = new System.IO.StreamWriter(stream);
#endif #endif
await reader.WriteLineAsync(openedFileContent.Text); await reader.WriteLineAsync(openedFileContent.Text);
@ -243,8 +243,8 @@ namespace ControlCatalog.Pages
async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items) async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
{ {
items ??= Array.Empty<IStorageItem>(); items ??= Array.Empty<IStorageItem>();
var mappedResults = items.Select(FullPathOrName).ToList(); bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark";
bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark"; var mappedResults = new List<string>();
if (items.FirstOrDefault() is IStorageItem item) if (items.FirstOrDefault() is IStorageItem item)
{ {
@ -267,9 +267,9 @@ Content:
if (file.CanOpenRead) if (file.CanOpenRead)
{ {
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
await using var stream = await file.OpenRead(); await using var stream = await file.OpenReadAsync();
#else #else
using var stream = await file.OpenRead(); using var stream = await file.OpenReadAsync();
#endif #endif
using var reader = new System.IO.StreamReader(stream); using var reader = new System.IO.StreamReader(stream);
@ -293,7 +293,19 @@ Content:
lastSelectedDirectory = await item.GetParentAsync(); lastSelectedDirectory = await item.GetParentAsync();
if (lastSelectedDirectory is not null) if (lastSelectedDirectory is not null)
{ {
mappedResults.Insert(0, "Parent: " + FullPathOrName(lastSelectedDirectory)); mappedResults.Add(FullPathOrName(lastSelectedDirectory));
}
foreach (var selectedItem in items)
{
mappedResults.Add("+> " + FullPathOrName(selectedItem));
if (selectedItem is IStorageFolder folder)
{
foreach (var innerItems in await folder.GetItemsAsync())
{
mappedResults.Add("++> " + FullPathOrName(innerItems));
}
}
} }
} }

15
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -8,26 +8,31 @@
Margin="0,16,0,0" Margin="0,16,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<Expander Header="Expand Up" ExpandDirection="Up"> <Expander Header="Expand Up" ExpandDirection="Up"
CornerRadius="{Binding CornerRadius}">
<StackPanel> <StackPanel>
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander Header="Expand Down" ExpandDirection="Down"> <Expander Header="Expand Down" ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel> <StackPanel>
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander Header="Expand Left" ExpandDirection="Left"> <Expander Header="Expand Left" ExpandDirection="Left"
CornerRadius="{Binding CornerRadius}">
<StackPanel> <StackPanel>
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander Header="Expand Right" ExpandDirection="Right"> <Expander Header="Expand Right" ExpandDirection="Right"
CornerRadius="{Binding CornerRadius}">
<StackPanel> <StackPanel>
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
</StackPanel> <CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox>
</StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

2
samples/ControlCatalog/Pages/ExpanderPage.xaml.cs

@ -1,5 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -8,6 +9,7 @@ namespace ControlCatalog.Pages
public ExpanderPage() public ExpanderPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
DataContext = new ExpanderPageViewModel();
} }
private void InitializeComponent() private void InitializeComponent()

44
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -90,7 +90,6 @@ namespace ControlCatalog.Pages
private int _vertexBufferObject; private int _vertexBufferObject;
private int _indexBufferObject; private int _indexBufferObject;
private int _vertexArrayObject; private int _vertexArrayObject;
private GlExtrasInterface _glExt;
private string GetShader(bool fragment, string shader) private string GetShader(bool fragment, string shader)
{ {
@ -258,7 +257,6 @@ namespace ControlCatalog.Pages
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
{ {
CheckError(GL); CheckError(GL);
_glExt = new GlExtrasInterface(GL);
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}";
@ -298,8 +296,8 @@ namespace ControlCatalog.Pages
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata),
GL_STATIC_DRAW); GL_STATIC_DRAW);
CheckError(GL); CheckError(GL);
_vertexArrayObject = _glExt.GenVertexArray(); _vertexArrayObject = GL.GenVertexArray();
_glExt.BindVertexArray(_vertexArrayObject); GL.BindVertexArray(_vertexArrayObject);
CheckError(GL); CheckError(GL);
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
0, vertexSize, IntPtr.Zero); 0, vertexSize, IntPtr.Zero);
@ -316,12 +314,13 @@ namespace ControlCatalog.Pages
// Unbind everything // Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0); GL.BindBuffer(GL_ARRAY_BUFFER, 0);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
_glExt.BindVertexArray(0); GL.BindVertexArray(0);
GL.UseProgram(0); GL.UseProgram(0);
// Delete all resources. // Delete all resources.
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject }); GL.DeleteBuffer(_vertexBufferObject);
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject }); GL.DeleteBuffer(_indexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shaderProgram); GL.DeleteProgram(_shaderProgram);
GL.DeleteShader(_fragmentShader); GL.DeleteShader(_fragmentShader);
GL.DeleteShader(_vertexShader); GL.DeleteShader(_vertexShader);
@ -338,7 +337,7 @@ namespace ControlCatalog.Pages
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
_glExt.BindVertexArray(_vertexArrayObject); GL.BindVertexArray(_vertexArrayObject);
GL.UseProgram(_shaderProgram); GL.UseProgram(_shaderProgram);
CheckError(GL); CheckError(GL);
var projection = var projection =
@ -369,34 +368,5 @@ namespace ControlCatalog.Pages
if (_disco > 0.01) if (_disco > 0.01)
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
} }
class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo>
{
public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo)
{
}
public delegate void GlDeleteVertexArrays(int count, int[] buffers);
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)]
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
public GlDeleteVertexArrays DeleteVertexArrays { get; }
public delegate void GlBindVertexArray(int array);
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)]
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
public GlBindVertexArray BindVertexArray { get; }
public delegate void GlGenVertexArrays(int n, int[] rv);
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)]
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public GlGenVertexArrays GenVertexArrays { get; }
public int GenVertexArray()
{
var rv = new int[1];
GenVertexArrays(1, rv);
return rv[0];
}
}
} }
} }

2
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -118,7 +118,7 @@
</StackPanel> </StackPanel>
</Border> </Border>
<Border> <Border>
<RichTextBlock Margin="10" TextWrapping="Wrap"> <RichTextBlock SelectionBrush="LightBlue" IsTextSelectionEnabled="True" Margin="10" TextWrapping="Wrap">
This <Span FontWeight="Bold">is</Span> a This <Span FontWeight="Bold">is</Span> a
<Span Background="Silver" Foreground="Maroon">TextBlock</Span> <Span Background="Silver" Foreground="Maroon">TextBlock</Span>
with <Span TextDecorations="Underline">several</Span> with <Span TextDecorations="Underline">several</Span>

27
samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs

@ -0,0 +1,27 @@
using Avalonia;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class ExpanderPageViewModel : ViewModelBase
{
private object _cornerRadius = AvaloniaProperty.UnsetValue;
private bool _rounded;
public object CornerRadius
{
get => _cornerRadius;
private set => RaiseAndSetIfChanged(ref _cornerRadius, value);
}
public bool Rounded
{
get => _rounded;
set
{
if (RaiseAndSetIfChanged(ref _rounded, value))
CornerRadius = _rounded ? new CornerRadius(25) : AvaloniaProperty.UnsetValue;
}
}
}
}

3
samples/PlatformSanityChecks/App.xaml

@ -1,6 +1,5 @@
<Application xmlns="https://github.com/avaloniaui"> <Application xmlns="https://github.com/avaloniaui">
<Application.Styles> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <SimpleTheme Mode="Light" />
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,7 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup> </ItemGroup>

5
samples/Previewer/App.xaml

@ -1,6 +1,5 @@
<Application xmlns="https://github.com/avaloniaui"> <Application xmlns="https://github.com/avaloniaui">
<Application.Styles> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <SimpleTheme Mode="Light" />
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/Previewer/Previewer.csproj

@ -10,7 +10,7 @@
<EmbeddedResource Include="**\*.xaml" /> <EmbeddedResource Include="**\*.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />

8
samples/RenderDemo/App.xaml

@ -3,6 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
<StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</Application.Styles> </Application.Styles>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application> </Application>

6
samples/RenderDemo/Pages/CustomSkiaPage.cs

@ -40,14 +40,16 @@ namespace RenderDemo.Pages
static Stopwatch St = Stopwatch.StartNew(); static Stopwatch St = Stopwatch.StartNew();
public void Render(IDrawingContextImpl context) public void Render(IDrawingContextImpl context)
{ {
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; var leaseFeature = context.GetFeature<ISkiaSharpApiLeaseFeature>();
if (canvas == null) if (leaseFeature == null)
using (var c = new DrawingContext(context, false)) using (var c = new DrawingContext(context, false))
{ {
c.DrawText(_noSkia, new Point()); c.DrawText(_noSkia, new Point());
} }
else else
{ {
using var lease = leaseFeature.Lease();
var canvas = lease.SkCanvas;
canvas.Save(); canvas.Save();
// create the first shader // create the first shader
var colors = new SKColor[] { var colors = new SKColor[] {

286
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -1,6 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catalog="using:ControlSamples"> xmlns:catalog="using:ControlSamples">
<Design.PreviewWith> <Design.PreviewWith>
<Border Width="400" <Border Width="400"
Height="150"> Height="150">
@ -20,25 +20,136 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources> <x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneCompactWidth">40</x:Double> <x:Double x:Key="PaneExpandWidth">220</x:Double>
<x:Double x:Key="PaneExpandWidth">220</x:Double> <x:Double x:Key="HeaderHeight">36</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double> <x:Double x:Key="NavigationItemHeight">36</x:Double>
<x:Double x:Key="NavigationItemHeight">36</x:Double> <x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double>
<x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double> <Thickness x:Key="HeaderMarginCollapsedPane">12,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginCollapsedPane">12,0,0,0</Thickness> <Thickness x:Key="HeaderMarginExpandedPane">52,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedPane">52,0,0,0</Thickness> <Thickness x:Key="HeaderMarginExpandedOverlayPane">212,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedOverlayPane">212,0,0,0</Thickness> <BoxShadows x:Key="NavigationItemShadow">1 1 1 1 #2000, 0 0 1 1 #2fff</BoxShadows>
<BoxShadows x:Key="NavigationItemShadow">1 1 1 1 #2000, 0 0 1 1 #2fff</BoxShadows> <BoxShadows x:Key="NavigationContentShadow">0 0 1 1 #2000</BoxShadows>
<BoxShadows x:Key="NavigationContentShadow">0 0 1 1 #2000</BoxShadows>
</Styles.Resources> <ControlTheme x:Key="NavigationButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="12,0,4,0" />
<Setter Property="Margin" Value="4,0,8,0" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="HamburgerMenuTabItem" TargetType="TabItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="12,0,4,0" />
<Setter Property="Margin" Value="4,0,8,0" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="PART_LayoutRoot"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Border Name="PART_SelectedPipe"
Width="{DynamicResource TabItemPipeThickness}"
Height="{DynamicResource TabItemVerticalPipeHeight}"
Margin="6,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
IsVisible="False"
CornerRadius="{DynamicResource ControlCornerRadius}"/>
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
Margin="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
<Style Selector="^ /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
</Style>
<Style Selector="^:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Style Selector="^ /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
<Style Selector="^:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
</ControlTheme>
<!-- HamburgerMenu --> <!-- HamburgerMenu -->
<Style Selector="catalog|HamburgerMenu"> <ControlTheme x:Key="{x:Type catalog:HamburgerMenu}" TargetType="catalog:HamburgerMenu">
<Setter Property="Padding" Value="12 8 4 0" /> <Setter Property="Padding" Value="12 8 4 0" />
<Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" /> <Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" /> <Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" /> <Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" />
<Setter Property="ItemContainerTheme" Value="{StaticResource HamburgerMenuTabItem}"/>
<Setter Property="TabStripPlacement" Value="Left" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel Background="{TemplateBinding PaneBackground}"> <Panel Background="{TemplateBinding PaneBackground}">
@ -46,7 +157,8 @@
CompactPaneLength="{StaticResource PaneCompactWidth}" CompactPaneLength="{StaticResource PaneCompactWidth}"
DisplayMode="Inline" DisplayMode="Inline"
IsPaneOpen="True" IsPaneOpen="True"
OpenPaneLength="{StaticResource PaneExpandWidth}"> OpenPaneLength="{StaticResource PaneExpandWidth}"
PaneBackground="Transparent">
<SplitView.Pane> <SplitView.Pane>
<Grid Margin="0,0,1,5" RowDefinitions="Auto, *, Auto"> <Grid Margin="0,0,1,5" RowDefinitions="Auto, *, Auto">
<Panel Height="{StaticResource HeaderHeight}" /> <Panel Height="{StaticResource HeaderHeight}" />
@ -58,8 +170,7 @@
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemTemplate="{TemplateBinding ItemTemplate}" ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}">
ItemsPanel="{TemplateBinding ItemsPanel}">
<ItemsPresenter.ItemsPanel> <ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel x:Name="HamburgerItemsPanel" <StackPanel x:Name="HamburgerItemsPanel"
@ -70,7 +181,7 @@
</ScrollViewer> </ScrollViewer>
<Button x:Name="SettingsButton" <Button x:Name="SettingsButton"
Grid.Row="2" Grid.Row="2"
Classes="NavigationButton" Theme="{StaticResource NavigationButton}"
Content="Settings" Content="Settings"
Flyout="{TemplateBinding (FlyoutBase.AttachedFlyout)}" Flyout="{TemplateBinding (FlyoutBase.AttachedFlyout)}"
IsVisible="{Binding $parent[TabControl].(FlyoutBase.AttachedFlyout), Converter={x:Static ObjectConverters.IsNotNull}}" /> IsVisible="{Binding $parent[TabControl].(FlyoutBase.AttachedFlyout), Converter={x:Static ObjectConverters.IsNotNull}}" />
@ -82,6 +193,7 @@
<TextBlock x:Name="HeaderHolder" <TextBlock x:Name="HeaderHolder"
VerticalAlignment="Center" VerticalAlignment="Center"
Classes="h1" Classes="h1"
Margin="{StaticResource HeaderMarginExpandedPane}"
Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}"> Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}">
<TextBlock.Transitions> <TextBlock.Transitions>
<Transitions> <Transitions>
@ -119,7 +231,7 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
Classes="NavigationButton" Theme="{StaticResource NavigationButton}"
CornerRadius="4" CornerRadius="4"
IsChecked="{Binding #PART_NavigationPane.IsPaneOpen, Mode=TwoWay}"> IsChecked="{Binding #PART_NavigationPane.IsPaneOpen, Mode=TwoWay}">
<PathIcon Data="M3 17h18a1 1 0 0 1 .117 1.993L21 19H3a1 1 0 0 1-.117-1.993L3 17h18H3Zm0-6 18-.002a1 1 0 0 1 .117 1.993l-.117.007L3 13a1 1 0 0 1-.117-1.993L3 11l18-.002L3 11Zm0-6h18a1 1 0 0 1 .117 1.993L21 7H3a1 1 0 0 1-.117-1.993L3 5h18H3Z" Foreground="{TemplateBinding Foreground}" /> <PathIcon Data="M3 17h18a1 1 0 0 1 .117 1.993L21 19H3a1 1 0 0 1-.117-1.993L3 17h18H3Zm0-6 18-.002a1 1 0 0 1 .117 1.993l-.117.007L3 13a1 1 0 0 1-.117-1.993L3 11l18-.002L3 11Zm0-6h18a1 1 0 0 1 .117 1.993L21 7H3a1 1 0 0 1-.117-1.993L3 5h18H3Z" Foreground="{TemplateBinding Foreground}" />
@ -127,116 +239,26 @@
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView TextBlock#HeaderHolder"> <Style Selector="^ /template/ SplitView[IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedPane}" /> <Setter Property="Margin" Value="{StaticResource HeaderMarginCollapsedPane}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[IsPaneOpen=True] TextBlock#HeaderHolder"> <Style Selector="^ /template/ SplitView[DisplayMode=Overlay][IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginCollapsedPane}" /> <Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedOverlayPane}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay][IsPaneOpen=True] TextBlock#HeaderHolder"> <Style Selector="^ /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedOverlayPane}" /> <Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView"> <Style Selector="^ /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="PaneBackground" Value="Transparent" /> <Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]"> <Style Selector="^ /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder">
<Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" /> <Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
</Style> <Setter Property="BoxShadow" Value="{StaticResource NavigationContentShadow}" />
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]"> </Style>
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" /> <Style Selector="^ /template/ SplitView[DisplayMode=Inline][IsPaneOpen=True] Border#BackgroundBorder">
</Style> <Setter Property="CornerRadius" Value="8 0 0 0" />
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder"> </Style>
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" /> </ControlTheme>
<Setter Property="BoxShadow" Value="{StaticResource NavigationContentShadow}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Inline][IsPaneOpen=True] Border#BackgroundBorder">
<Setter Property="CornerRadius" Value="8 0 0 0" />
</Style>
</ResourceDictionary>
<!-- HamburgerMenu TabItem -->
<Style Selector="catalog|HamburgerMenu > TabItem, :is(Button).NavigationButton">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="12,0,4,0" />
<Setter Property="Margin" Value="4,0,8,0" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="ClipToBounds" Value="False" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem">
<Setter Property="Template">
<ControlTemplate>
<Border Name="PART_LayoutRoot"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Border Name="PART_SelectedPipe"
Width="{DynamicResource TabItemPipeThickness}"
Height="{DynamicResource TabItemVerticalPipeHeight}"
Margin="6,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector=":is(Button).NavigationButton">
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</ControlTemplate>
</Setter>
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton /template/ ContentPresenter">
<Setter Property="Border.Background" Value="Transparent" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pointerover /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pressed /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<Style Selector=":is(Button).NavigationButton:pressed">
<Setter Property="RenderTransform" Value="none" />
</Style>
</Styles>

16
samples/VirtualizationDemo/App.xaml

@ -1,9 +1,7 @@
<Application <Application xmlns="https://github.com/avaloniaui"
xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="VirtualizationDemo.App">
x:Class="VirtualizationDemo.App"> <Application.Styles>
<Application.Styles> <SimpleTheme />
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/> </Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/> </Application>
</Application.Styles>
</Application>

2
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>

5
samples/interop/Direct3DInteropSample/App.paml

@ -1,6 +1,5 @@
<Application xmlns="https://github.com/avaloniaui"> <Application xmlns="https://github.com/avaloniaui">
<Application.Styles> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <SimpleTheme Mode="Light" />
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,7 +22,7 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" />

15
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -8,7 +8,8 @@ using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Skia; using Avalonia.Rendering.Composition;
using Avalonia.OpenGL;
namespace Avalonia namespace Avalonia
{ {
@ -42,6 +43,8 @@ namespace Avalonia.Android
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
internal static Compositor Compositor { get; private set; }
public static void Initialize(AndroidPlatformOptions options) public static void Initialize(AndroidPlatformOptions options)
{ {
Options = options; Options = options;
@ -62,12 +65,20 @@ namespace Avalonia.Android
{ {
EglPlatformOpenGlInterface.TryInitialize(); EglPlatformOpenGlInterface.TryInitialize();
} }
if (options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
}
} }
} }
public sealed class AndroidPlatformOptions public sealed class AndroidPlatformOptions
{ {
public bool UseDeferredRendering { get; set; } = true; public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true; public bool UseGpu { get; set; } = true;
public bool UseCompositor { get; set; } = true;
} }
} }

9
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -19,6 +19,7 @@ using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle }; public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) => public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseDeferredRendering AndroidPlatform.Options.UseCompositor
? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()) { RenderOnlyOnRenderThread = true } ? new CompositingRenderer(root, AndroidPlatform.Compositor) { DrawFps = true }
: new ImmediateRenderer(root); : AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root);
public virtual void Hide() public virtual void Hide()
{ {

33
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -35,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public bool CanBookmark => true; public bool CanBookmark => true;
public Task<string?> SaveBookmark() public Task<string?> SaveBookmarkAsync()
{ {
Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.FromResult(Uri.ToString()); return Task.FromResult(Uri.ToString());
} }
public Task ReleaseBookmark() public Task ReleaseBookmarkAsync()
{ {
Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.CompletedTask; return Task.CompletedTask;
@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
{ {
return Task.FromResult(new StorageItemProperties()); return Task.FromResult(new StorageItemProperties());
} }
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
{
using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused.
var files = await javaFile.ListFilesAsync().ConfigureAwait(false);
if (files is null)
{
return Array.Empty<IStorageItem>();
}
return files
.Select(f => (file: f, uri: AndroidUri.FromFile(f)))
.Where(t => t.uri is not null)
.Select(t => t.file switch
{
{ IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!),
{ IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!),
_ => null
})
.Where(i => i is not null)
.ToArray()!;
}
} }
internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile
@ -118,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public bool CanOpenWrite => true; public bool CanOpenWrite => true;
public Task<Stream> OpenRead() => Task.FromResult(OpenContentStream(Context, Uri, false) public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream")); ?? throw new InvalidOperationException("Failed to open content stream"));
public Task<Stream> OpenWrite() => Task.FromResult(OpenContentStream(Context, Uri, true) public Task<Stream> OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true)
?? throw new InvalidOperationException("Failed to open content stream")); ?? throw new InvalidOperationException("Failed to open content stream"));
private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput) private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput)

56
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -29,7 +30,11 @@ namespace Avalonia.Controls
public object? this[object key] public object? this[object key]
{ {
get => _inner?[key]; get
{
TryGetValue(key, out var value);
return value;
}
set set
{ {
Inner[key] = value; Inner[key] = value;
@ -119,6 +124,12 @@ namespace Avalonia.Controls
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
} }
public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
{
Inner.Add(key, new DeferredItem(factory));
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
public void Clear() public void Clear()
{ {
if (_inner?.Count > 0) if (_inner?.Count > 0)
@ -143,10 +154,8 @@ namespace Avalonia.Controls
public bool TryGetResource(object key, out object? value) public bool TryGetResource(object key, out object? value)
{ {
if (_inner is not null && _inner.TryGetValue(key, out value)) if (TryGetValue(key, out value))
{
return true; return true;
}
if (_mergedDictionaries != null) if (_mergedDictionaries != null)
{ {
@ -165,12 +174,28 @@ namespace Avalonia.Controls
public bool TryGetValue(object key, out object? value) public bool TryGetValue(object key, out object? value)
{ {
if (_inner is not null) if (_inner is not null && _inner.TryGetValue(key, out value))
return _inner.TryGetValue(key, out value); {
if (value is DeferredItem deffered)
{
_inner[key] = value = deffered.Factory(null) switch
{
ITemplateResult t => t.Result,
object v => v,
_ => null,
};
}
return true;
}
value = null; value = null;
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();
}
void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item) void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
{ {
@ -198,12 +223,17 @@ namespace Avalonia.Controls
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool ContainsDeferredKey(object key)
{ {
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator(); if (_inner is not null && _inner.TryGetValue(key, out var result))
} {
return result is DeferredItem;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return false;
}
void IResourceProvider.AddOwner(IResourceHost owner) void IResourceProvider.AddOwner(IResourceHost owner)
{ {
@ -258,5 +288,11 @@ namespace Avalonia.Controls
} }
} }
} }
private class DeferredItem
{
public DeferredItem(Func<IServiceProvider?, object?> factory) => Factory = factory;
public Func<IServiceProvider?, object?> Factory { get; }
}
} }
} }

5
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -132,6 +132,11 @@ namespace Avalonia.Controls
{ {
_target.OwnerChanged += OwnerChanged; _target.OwnerChanged += OwnerChanged;
_owner = _target.Owner; _owner = _target.Owner;
if (_owner is object)
{
_owner.ResourcesChanged += ResourcesChanged;
}
} }
protected override void Deinitialize() protected override void Deinitialize()

8
src/Avalonia.Base/Controls/Templates/ITemplateResult.cs

@ -0,0 +1,8 @@
namespace Avalonia.Controls.Templates
{
public interface ITemplateResult
{
public object? Result { get; }
public INameScope NameScope { get; }
}
}

3
src/Avalonia.Controls/Templates/TemplateResult.cs → src/Avalonia.Base/Controls/Templates/TemplateResult.cs

@ -1,9 +1,10 @@
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
public class TemplateResult<T> public class TemplateResult<T> : ITemplateResult
{ {
public T Result { get; } public T Result { get; }
public INameScope NameScope { get; } public INameScope NameScope { get; }
object? ITemplateResult.Result => Result;
public TemplateResult(T result, INameScope nameScope) public TemplateResult(T result, INameScope nameScope)
{ {

5
src/Avalonia.Base/Input/Cursor.cs

@ -32,10 +32,7 @@ namespace Avalonia.Input
DragCopy, DragCopy,
DragLink, DragLink,
None, None,
[Obsolete("Use BottomSide")]
BottomSize = BottomSide
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax directly from theme with fallback image // We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast, // SizeNorthWestSouthEast,

18
src/Avalonia.Base/Input/DragEventArgs.cs

@ -13,9 +13,6 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; } public IDataObject Data { get; private set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; }
public KeyModifiers KeyModifiers { get; private set; } public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual relativeTo)
@ -35,17 +32,6 @@ namespace Avalonia.Input
return point; return point;
} }
[Obsolete("Use constructor taking KeyModifiers")]
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers)
: base(routedEvent)
{
Data = data;
_target = target;
_targetLocation = targetLocation;
Modifiers = modifiers;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xF);
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent) : base(routedEvent)
{ {
@ -53,10 +39,6 @@ namespace Avalonia.Input
_target = target; _target = target;
_targetLocation = targetLocation; _targetLocation = targetLocation;
KeyModifiers = keyModifiers; KeyModifiers = keyModifiers;
#pragma warning disable CS0618 // Type or member is obsolete
Modifiers = (InputModifiers)keyModifiers;
#pragma warning restore CS0618 // Type or member is obsolete
} }
} }
} }

11
src/Avalonia.Base/Input/GotFocusEventArgs.cs

@ -1,4 +1,3 @@
using System;
using Avalonia.Interactivity; using Avalonia.Interactivity;
namespace Avalonia.Input namespace Avalonia.Input
@ -13,16 +12,6 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public NavigationMethod NavigationMethod { get; set; } public NavigationMethod NavigationMethod { get; set; }
/// <summary>
/// Gets or sets any input modifiers active at the time of focus.
/// </summary>
[Obsolete("Use KeyModifiers")]
public InputModifiers InputModifiers
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)((int)value & 0xF);
}
/// <summary> /// <summary>
/// Gets or sets any key modifiers active at the time of focus. /// Gets or sets any key modifiers active at the time of focus.
/// </summary> /// </summary>

5
src/Avalonia.Base/Input/IInputRoot.cs

@ -27,10 +27,5 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>
bool ShowAccessKeys { get; set; } bool ShowAccessKeys { get; set; }
/// <summary>
/// Gets associated mouse device
/// </summary>
IMouseDevice? MouseDevice { get; }
} }
} }

13
src/Avalonia.Base/Input/IKeyboardDevice.cs

@ -4,19 +4,6 @@ using Avalonia.Metadata;
namespace Avalonia.Input namespace Avalonia.Input
{ {
[Flags, Obsolete("Use KeyModifiers and PointerPointProperties")]
public enum InputModifiers
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
LeftMouseButton = 16,
RightMouseButton = 32,
MiddleMouseButton = 64
}
[Flags] [Flags]
public enum KeyModifiers public enum KeyModifiers
{ {

12
src/Avalonia.Base/Input/IMouseDevice.cs

@ -1,4 +1,3 @@
using System;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Input namespace Avalonia.Input
@ -9,16 +8,5 @@ namespace Avalonia.Input
[NotClientImplementable] [NotClientImplementable]
public interface IMouseDevice : IPointerDevice public interface IMouseDevice : IPointerDevice
{ {
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
[Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
[Obsolete]
void TopLevelClosed(IInputRoot root);
[Obsolete]
void SceneInvalidated(IInputRoot root, Rect rect);
} }
} }

14
src/Avalonia.Base/Input/IPointerDevice.cs

@ -1,5 +1,3 @@
using System;
using Avalonia.VisualTree;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -8,18 +6,6 @@ namespace Avalonia.Input
[NotClientImplementable] [NotClientImplementable]
public interface IPointerDevice : IInputDevice public interface IPointerDevice : IInputDevice
{ {
/// <inheritdoc cref="IPointer.Captured" />
[Obsolete("Use IPointer")]
IInputElement? Captured { get; }
/// <inheritdoc cref="IPointer.Capture(IInputElement?)" />
[Obsolete("Use IPointer")]
void Capture(IInputElement? control);
/// <inheritdoc cref="PointerEventArgs.GetPosition(IVisual?)" />
[Obsolete("Use PointerEventArgs.GetPosition")]
Point GetPosition(IVisual relativeTo);
/// <summary> /// <summary>
/// Gets a pointer for specific event args. /// Gets a pointer for specific event args.
/// </summary> /// </summary>

2
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -9,8 +9,6 @@ namespace Avalonia.Input
public Key Key { get; set; } public Key Key { get; set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
public KeyModifiers KeyModifiers { get; set; } public KeyModifiers KeyModifiers { get; set; }
} }
} }

12
src/Avalonia.Base/Input/KeyGesture.cs

@ -15,13 +15,6 @@ namespace Avalonia.Input
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma } { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma }
}; };
[Obsolete("Use constructor taking KeyModifiers")]
public KeyGesture(Key key, InputModifiers modifiers)
{
Key = key;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xf);
}
public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None) public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None)
{ {
Key = key; Key = key;
@ -63,10 +56,7 @@ namespace Avalonia.Input
} }
public Key Key { get; } public Key Key { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
public KeyModifiers KeyModifiers { get; } public KeyModifiers KeyModifiers { get; }
public static KeyGesture Parse(string gesture) public static KeyGesture Parse(string gesture)

54
src/Avalonia.Base/Input/MouseDevice.cs

@ -21,7 +21,6 @@ namespace Avalonia.Input
private readonly Pointer _pointer; private readonly Pointer _pointer;
private bool _disposed; private bool _disposed;
private PixelPoint? _position;
private MouseButton _lastMouseDownButton; private MouseButton _lastMouseDownButton;
public MouseDevice(Pointer? pointer = null) public MouseDevice(Pointer? pointer = null)
@ -29,43 +28,6 @@ namespace Avalonia.Input
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
} }
[Obsolete("Use IPointer instead")]
public IInputElement? Captured => _pointer.Captured;
[Obsolete("Use events instead")]
public PixelPoint Position
{
get => _position ?? new PixelPoint(-1, -1);
protected set => _position = value;
}
[Obsolete("Use IPointer instead")]
public void Capture(IInputElement? control)
{
_pointer.Capture(control);
}
/// <summary>
/// Gets the mouse position relative to a control.
/// </summary>
/// <param name="relativeTo">The control.</param>
/// <returns>The mouse position in the control's coordinates.</returns>
public Point GetPosition(IVisual relativeTo)
{
relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
if (relativeTo.VisualRoot == null)
{
throw new InvalidOperationException("Control is not attached to visual tree.");
}
#pragma warning disable CS0618 // Type or member is obsolete
var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
#pragma warning restore CS0618 // Type or member is obsolete
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform!.Value;
}
public void ProcessRawEvent(RawInputEventArgs e) public void ProcessRawEvent(RawInputEventArgs e)
{ {
if (!e.Handled && e is RawPointerEventArgs margs) if (!e.Handled && e is RawPointerEventArgs margs)
@ -96,7 +58,6 @@ namespace Avalonia.Input
if(mouse._disposed) if(mouse._disposed)
return; return;
_position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e); var props = CreateProperties(e);
var keyModifiers = e.InputModifiers.ToKeyModifiers(); var keyModifiers = e.InputModifiers.ToKeyModifiers();
switch (e.Type) switch (e.Type)
@ -145,7 +106,6 @@ namespace Avalonia.Input
private void LeaveWindow() private void LeaveWindow()
{ {
_position = null;
} }
PointerPointProperties CreateProperties(RawPointerEventArgs args) PointerPointProperties CreateProperties(RawPointerEventArgs args)
@ -324,19 +284,7 @@ namespace Avalonia.Input
_disposed = true; _disposed = true;
_pointer?.Dispose(); _pointer?.Dispose();
} }
[Obsolete]
public void TopLevelClosed(IInputRoot root)
{
// no-op
}
[Obsolete]
public void SceneInvalidated(IInputRoot root, Rect rect)
{
// no-op
}
public IPointer? TryGetPointer(RawPointerEventArgs ev) public IPointer? TryGetPointer(RawPointerEventArgs ev)
{ {
return _pointer; return _pointer;

19
src/Avalonia.Base/Input/PenDevice.cs

@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Linq;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -14,7 +12,6 @@ namespace Avalonia.Input
public class PenDevice : IPenDevice, IDisposable public class PenDevice : IPenDevice, IDisposable
{ {
private readonly Dictionary<long, Pointer> _pointers = new(); private readonly Dictionary<long, Pointer> _pointers = new();
private readonly Dictionary<long, PixelPoint> _lastPositions = new();
private int _clickCount; private int _clickCount;
private Rect _lastClickRect; private Rect _lastClickRect;
private ulong _lastClickTime; private ulong _lastClickTime;
@ -41,9 +38,7 @@ namespace Avalonia.Input
_pointers[e.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(), _pointers[e.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(),
PointerType.Pen, _pointers.Count == 0); PointerType.Pen, _pointers.Count == 0);
} }
_lastPositions[e.RawPointerId] = e.Root.PointToScreen(e.Position);
var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(), var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(),
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt); e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt);
var keyModifiers = e.InputModifiers.ToKeyModifiers(); var keyModifiers = e.InputModifiers.ToKeyModifiers();
@ -69,7 +64,6 @@ namespace Avalonia.Input
{ {
pointer.Dispose(); pointer.Dispose();
_pointers.Remove(e.RawPointerId); _pointers.Remove(e.RawPointerId);
_lastPositions.Remove(e.RawPointerId);
} }
} }
@ -153,17 +147,6 @@ namespace Avalonia.Input
p.Dispose(); p.Dispose();
} }
[Obsolete]
IInputElement? IPointerDevice.Captured => _pointers.Values
.FirstOrDefault(p => p.IsPrimary)?.Captured;
[Obsolete]
void IPointerDevice.Capture(IInputElement? control) => _pointers.Values
.FirstOrDefault(p => p.IsPrimary)?.Capture(control);
[Obsolete]
Point IPointerDevice.GetPosition(IVisual relativeTo) => new Point(-1, -1);
public IPointer? TryGetPointer(RawPointerEventArgs ev) public IPointer? TryGetPointer(RawPointerEventArgs ev)
{ {
return _pointers.TryGetValue(ev.RawPointerId, out var pointer) return _pointers.TryGetValue(ev.RawPointerId, out var pointer)

62
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -11,7 +11,7 @@ namespace Avalonia.Input
private readonly IVisual? _rootVisual; private readonly IVisual? _rootVisual;
private readonly Point _rootVisualPosition; private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties; private readonly PointerPointProperties _properties;
private Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints; private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
public PointerEventArgs(RoutedEvent routedEvent, public PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source, IInteractive? source,
@ -43,29 +43,6 @@ namespace Avalonia.Input
{ {
_previousPoints = previousPoints; _previousPoints = previousPoints;
} }
class EmulatedDevice : IPointerDevice
{
private readonly PointerEventArgs _ev;
public EmulatedDevice(PointerEventArgs ev)
{
_ev = ev;
}
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement? Captured => _ev.Pointer.Captured;
public void Capture(IInputElement? control)
{
_ev.Pointer.Capture(control);
}
public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
public IPointer? TryGetPointer(RawPointerEventArgs ev) => _ev.Pointer;
}
/// <summary> /// <summary>
/// Gets specific pointer generated by input device. /// Gets specific pointer generated by input device.
@ -77,28 +54,6 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public ulong Timestamp { get; } public ulong Timestamp { get; }
private IPointerDevice? _device;
[Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
[Obsolete("Use KeyModifiers and PointerPointProperties")]
public InputModifiers InputModifiers
{
get
{
var mods = (InputModifiers)KeyModifiers;
if (_properties.IsLeftButtonPressed)
mods |= InputModifiers.LeftMouseButton;
if (_properties.IsMiddleButtonPressed)
mods |= InputModifiers.MiddleMouseButton;
if (_properties.IsRightButtonPressed)
mods |= InputModifiers.RightMouseButton;
return mods;
}
}
/// <summary> /// <summary>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated. /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary> /// </summary>
@ -120,9 +75,6 @@ namespace Avalonia.Input
/// <returns>The pointer position in the control's coordinates.</returns> /// <returns>The pointer position in the control's coordinates.</returns>
public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
[Obsolete("Use GetCurrentPoint")]
public PointerPoint GetPointerPoint(IVisual? relativeTo) => GetCurrentPoint(relativeTo);
/// <summary> /// <summary>
/// Returns the PointerPoint associated with the current event /// Returns the PointerPoint associated with the current event
/// </summary> /// </summary>
@ -171,8 +123,6 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs public class PointerPressedEventArgs : PointerEventArgs
{ {
private readonly int _clickCount;
public PointerPressedEventArgs( public PointerPressedEventArgs(
IInteractive source, IInteractive source,
IPointer pointer, IPointer pointer,
@ -184,13 +134,10 @@ namespace Avalonia.Input
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers) timestamp, properties, modifiers)
{ {
_clickCount = clickCount; ClickCount = clickCount;
} }
public int ClickCount => _clickCount; public int ClickCount { get; }
[Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();
} }
public class PointerReleasedEventArgs : PointerEventArgs public class PointerReleasedEventArgs : PointerEventArgs
@ -210,9 +157,6 @@ namespace Avalonia.Input
/// Gets the mouse button that triggered the corresponding PointerPressed event /// Gets the mouse button that triggered the corresponding PointerPressed event
/// </summary> /// </summary>
public MouseButton InitialPressMouseButton { get; } public MouseButton InitialPressMouseButton { get; }
[Obsolete("Use InitialPressMouseButton")]
public MouseButton MouseButton => InitialPressMouseButton;
} }
public class PointerCaptureLostEventArgs : RoutedEventArgs public class PointerCaptureLostEventArgs : RoutedEventArgs

2
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@ -15,6 +15,8 @@ namespace Avalonia.Input
_inputRoot = inputRoot ?? throw new ArgumentNullException(nameof(inputRoot)); _inputRoot = inputRoot ?? throw new ArgumentNullException(nameof(inputRoot));
} }
public PixelPoint? LastPosition => _lastPointer?.position;
public void OnCompleted() public void OnCompleted()
{ {
ClearPointerOver(); ClearPointerOver();

5
src/Avalonia.Base/Input/Raw/RawDragEvent.cs

@ -8,8 +8,6 @@ namespace Avalonia.Input.Raw
public IDataObject Data { get; } public IDataObject Data { get; }
public DragDropEffects Effects { get; set; } public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; } public RawDragEventType Type { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; }
public KeyModifiers KeyModifiers { get; } public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
@ -21,9 +19,6 @@ namespace Avalonia.Input.Raw
Data = data; Data = data;
Effects = effects; Effects = effects;
KeyModifiers = modifiers.ToKeyModifiers(); KeyModifiers = modifiers.ToKeyModifiers();
#pragma warning disable CS0618 // Type or member is obsolete
Modifiers = (InputModifiers)modifiers;
#pragma warning restore CS0618 // Type or member is obsolete
} }
} }
} }

3
src/Avalonia.Base/Input/Raw/RawTouchEventArgs.cs

@ -19,8 +19,5 @@ namespace Avalonia.Input.Raw
{ {
RawPointerId = rawPointerId; RawPointerId = rawPointerId;
} }
[Obsolete("Use RawPointerId")]
public long TouchPointId { get => RawPointerId; set => RawPointerId = value; }
} }
} }

11
src/Avalonia.Base/Input/TouchDevice.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -20,9 +19,6 @@ namespace Avalonia.Input
private int _clickCount; private int _clickCount;
private Rect _lastClickRect; private Rect _lastClickRect;
private ulong _lastClickTime; private ulong _lastClickTime;
private Pointer? _lastPointer;
IInputElement? IPointerDevice.Captured => _lastPointer?.Captured;
RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown) RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown)
{ {
@ -32,10 +28,6 @@ namespace Avalonia.Input
return rv; return rv;
} }
void IPointerDevice.Capture(IInputElement? control) => _lastPointer?.Capture(control);
Point IPointerDevice.GetPosition(IVisual relativeTo) => default;
public void ProcessRawEvent(RawInputEventArgs ev) public void ProcessRawEvent(RawInputEventArgs ev)
{ {
if (ev.Handled || _disposed) if (ev.Handled || _disposed)
@ -51,7 +43,6 @@ namespace Avalonia.Input
PointerType.Touch, _pointers.Count == 0); PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit); pointer.Capture(hit);
} }
_lastPointer = pointer;
var target = pointer.Captured ?? args.Root; var target = pointer.Captured ?? args.Root;
var updateKind = args.Type.ToUpdateKind(); var updateKind = args.Type.ToUpdateKind();
@ -96,7 +87,6 @@ namespace Avalonia.Input
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left)); keyModifier, MouseButton.Left));
} }
_lastPointer = null;
} }
if (args.Type == RawPointerEventType.TouchCancel) if (args.Type == RawPointerEventType.TouchCancel)
@ -104,7 +94,6 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId); _pointers.Remove(args.RawPointerId);
using (pointer) using (pointer)
pointer.Capture(null); pointer.Capture(null);
_lastPointer = null;
} }
if (args.Type == RawPointerEventType.TouchUpdate) if (args.Type == RawPointerEventType.TouchUpdate)

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

@ -228,6 +228,8 @@ namespace Avalonia.Media
throw new NotImplementedException(); throw new NotImplementedException();
} }
public object? GetFeature(Type t) => null;
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

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

@ -265,7 +265,7 @@ namespace Avalonia.Media
//RightToLeft //RightToLeft
var glyphIndex = FindGlyphIndex(characterIndex); var glyphIndex = FindGlyphIndex(characterIndex);
if (GlyphClusters != null) if (GlyphClusters != null && GlyphClusters.Count > 0)
{ {
if (characterIndex > GlyphClusters[0]) if (characterIndex > GlyphClusters[0])
{ {
@ -445,7 +445,7 @@ namespace Avalonia.Media
/// </returns> /// </returns>
public int FindGlyphIndex(int characterIndex) public int FindGlyphIndex(int characterIndex)
{ {
if (GlyphClusters == null) if (GlyphClusters == null || GlyphClusters.Count == 0)
{ {
return characterIndex; return characterIndex;
} }
@ -614,17 +614,29 @@ namespace Avalonia.Media
private GlyphRunMetrics CreateGlyphRunMetrics() private GlyphRunMetrics CreateGlyphRunMetrics()
{ {
var firstCluster = 0;
var lastCluster = Characters.Length - 1;
if (!IsLeftToRight)
{
var cluster = firstCluster;
firstCluster = lastCluster;
lastCluster = cluster;
}
if (GlyphClusters != null && GlyphClusters.Count > 0) if (GlyphClusters != null && GlyphClusters.Count > 0)
{ {
var firstCluster = GlyphClusters[0]; firstCluster = GlyphClusters[0];
lastCluster = GlyphClusters[GlyphClusters.Count - 1];
_offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster); _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
} }
var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
var widthIncludingTrailingWhitespace = 0d; var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount); var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
for (var index = 0; index < GlyphIndices.Count; index++) for (var index = 0; index < GlyphIndices.Count; index++)
{ {
@ -635,16 +647,16 @@ namespace Avalonia.Media
var width = widthIncludingTrailingWhitespace; var width = widthIncludingTrailingWhitespace;
if (IsLeftToRight) if (isReversed)
{ {
for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++) for (var index = 0; index < glyphCount; index++)
{ {
width -= GetGlyphAdvance(index, out _); width -= GetGlyphAdvance(index, out _);
} }
} }
else else
{ {
for (var index = 0; index < glyphCount; index++) for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
{ {
width -= GetGlyphAdvance(index, out _); width -= GetGlyphAdvance(index, out _);
} }
@ -654,16 +666,15 @@ namespace Avalonia.Media
height); height);
} }
private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount) private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
{ {
glyphCount = 0; if (isReversed)
newLineLength = 0;
if (Characters.IsEmpty)
{ {
return 0; return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
} }
glyphCount = 0;
newLineLength = 0;
var trailingWhitespaceLength = 0; var trailingWhitespaceLength = 0;
if (GlyphClusters == null) if (GlyphClusters == null)
@ -732,6 +743,78 @@ namespace Avalonia.Media
return trailingWhitespaceLength; return trailingWhitespaceLength;
} }
private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount)
{
glyphCount = 0;
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
{
for (var i = 0; i < Characters.Length;)
{
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (!codepoint.IsWhiteSpace)
{
break;
}
if (codepoint.IsBreakChar)
{
newLineLength++;
}
trailingWhitespaceLength++;
i += count;
glyphCount++;
}
}
else
{
for (var i = 0; i < GlyphClusters.Count; i++)
{
var currentCluster = GlyphClusters[i];
var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
if (!codepoint.IsWhiteSpace)
{
break;
}
var clusterLength = 1;
while (i - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
if (currentCluster == nextCluster)
{
clusterLength++;
i--;
continue;
}
break;
}
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
trailingWhitespaceLength += clusterLength;
glyphCount++;
}
}
return trailingWhitespaceLength;
}
private void Set<T>(ref T field, T value) private void Set<T>(ref T field, T value)
{ {
_glyphRunImpl?.Dispose(); _glyphRunImpl?.Dispose();

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

@ -209,7 +209,7 @@ namespace Avalonia.Media
var pen = new Pen(Stroke ?? defaultBrush, thickness, var pen = new Pen(Stroke ?? defaultBrush, thickness,
new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Size.Width, 0)); drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0));
} }
} }
} }

24
src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
@ -116,7 +117,30 @@ namespace Avalonia.Media.TextFormatting
length = text.Length; length = text.Length;
} }
length = CoerceLength(text, length);
return new ValueSpan<TextRunProperties>(firstTextSourceIndex, length, currentProperties); return new ValueSpan<TextRunProperties>(firstTextSourceIndex, length, currentProperties);
} }
private static int CoerceLength(ReadOnlySlice<char> text, int length)
{
var finalLength = 0;
var graphemeEnumerator = new GraphemeEnumerator(text);
while (graphemeEnumerator.MoveNext())
{
var grapheme = graphemeEnumerator.Current;
finalLength += grapheme.Text.Length;
if (finalLength >= length)
{
return finalLength;
}
}
return length;
}
} }
} }

17
src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs

@ -15,6 +15,13 @@ namespace Avalonia.Media.TextFormatting
public override void Justify(TextLine textLine) public override void Justify(TextLine textLine)
{ {
var lineImpl = textLine as TextLineImpl;
if(lineImpl is null)
{
return;
}
var paragraphWidth = Width; var paragraphWidth = Width;
if (double.IsInfinity(paragraphWidth)) if (double.IsInfinity(paragraphWidth))
@ -22,12 +29,12 @@ namespace Avalonia.Media.TextFormatting
return; return;
} }
if (textLine.NewLineLength > 0) if (lineImpl.NewLineLength > 0)
{ {
return; return;
} }
var textLineBreak = textLine.TextLineBreak; var textLineBreak = lineImpl.TextLineBreak;
if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null) if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
{ {
@ -39,7 +46,7 @@ namespace Avalonia.Media.TextFormatting
var breakOportunities = new Queue<int>(); var breakOportunities = new Queue<int>();
foreach (var textRun in textLine.TextRuns) foreach (var textRun in lineImpl.TextRuns)
{ {
var text = textRun.Text; var text = textRun.Text;
@ -68,10 +75,10 @@ namespace Avalonia.Media.TextFormatting
return; return;
} }
var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace); var remainingSpace = Math.Max(0, paragraphWidth - lineImpl.WidthIncludingTrailingWhitespace);
var spacing = remainingSpace / breakOportunities.Count; var spacing = remainingSpace / breakOportunities.Count;
foreach (var textRun in textLine.TextRuns) foreach (var textRun in lineImpl.TextRuns)
{ {
var text = textRun.Text; var text = textRun.Text;

26
src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs

@ -38,7 +38,7 @@ namespace Avalonia.Media.TextFormatting
/// Gets a list of <see cref="ShapeableTextCharacters"/>. /// Gets a list of <see cref="ShapeableTextCharacters"/>.
/// </summary> /// </summary>
/// <returns>The shapeable text characters.</returns> /// <returns>The shapeable text characters.</returns>
internal IReadOnlyList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel, internal IReadOnlyList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel,
ref TextRunProperties? previousProperties) ref TextRunProperties? previousProperties)
{ {
var shapeableCharacters = new List<ShapeableTextCharacters>(2); var shapeableCharacters = new List<ShapeableTextCharacters>(2);
@ -65,7 +65,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="biDiLevel">The bidi level of the run.</param> /// <param name="biDiLevel">The bidi level of the run.</param>
/// <param name="previousProperties"></param> /// <param name="previousProperties"></param>
/// <returns>A list of shapeable text runs.</returns> /// <returns>A list of shapeable text runs.</returns>
private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice<char> text, private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice<char> text,
TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties)
{ {
var defaultTypeface = defaultProperties.Typeface; var defaultTypeface = defaultProperties.Typeface;
@ -76,7 +76,7 @@ namespace Avalonia.Media.TextFormatting
{ {
if (script == Script.Common && previousTypeface is not null) if (script == Script.Common && previousTypeface is not null)
{ {
if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _)) if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _))
{ {
return new ShapeableTextCharacters(text.Take(fallbackCount), return new ShapeableTextCharacters(text.Take(fallbackCount),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -86,10 +86,10 @@ namespace Avalonia.Media.TextFormatting
return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface), return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface),
biDiLevel); biDiLevel);
} }
if (previousTypeface is not null) if (previousTypeface is not null)
{ {
if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _)) if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _))
{ {
return new ShapeableTextCharacters(text.Take(count), return new ShapeableTextCharacters(text.Take(count),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -106,12 +106,12 @@ namespace Avalonia.Media.TextFormatting
{ {
continue; continue;
} }
codepoint = codepointEnumerator.Current; codepoint = codepointEnumerator.Current;
break; break;
} }
//ToDo: Fix FontFamily fallback //ToDo: Fix FontFamily fallback
var matchFound = var matchFound =
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
@ -157,14 +157,14 @@ namespace Avalonia.Media.TextFormatting
/// <param name="script"></param> /// <param name="script"></param>
/// <returns></returns> /// <returns></returns>
protected static bool TryGetShapeableLength( protected static bool TryGetShapeableLength(
ReadOnlySlice<char> text, ReadOnlySlice<char> text,
Typeface typeface, Typeface typeface,
Typeface? defaultTypeface, Typeface? defaultTypeface,
out int length, out int length,
out Script script) out Script script)
{ {
length = 0; length = 0;
script = Script.Unknown; script = Script.Unknown;
if (text.Length == 0) if (text.Length == 0)
{ {
@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting
var currentScript = currentGrapheme.FirstCodepoint.Script; var currentScript = currentGrapheme.FirstCodepoint.Script;
if (currentScript != Script.Common && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{ {
break; break;
} }
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{ {
break; break;
} }
if (currentScript != script) if (currentScript != script)
{ {
if (script is Script.Unknown || currentScript != Script.Common && if (script is Script.Unknown || currentScript != Script.Common &&

58
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -63,7 +63,7 @@ namespace Avalonia.Media.TextFormatting
MaxHeight = maxHeight; MaxHeight = maxHeight;
MaxLines = maxLines; MaxLines = maxLines;
TextLines = CreateTextLines(); TextLines = CreateTextLines();
} }
@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxLines">The maximum number of text lines.</param> /// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout( public TextLayout(
ITextSource textSource, ITextSource textSource,
TextParagraphProperties paragraphProperties, TextParagraphProperties paragraphProperties,
TextTrimming? textTrimming = null, TextTrimming? textTrimming = null,
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting
return new Rect(); return new Rect();
} }
if (textPosition < 0 || textPosition >= _textSourceLength) if (textPosition < 0)
{ {
var lastLine = TextLines[TextLines.Count - 1]; textPosition = _textSourceLength;
var lineX = lastLine.Width;
var lineY = Bounds.Bottom - lastLine.Height;
return new Rect(lineX, lineY, 0, lastLine.Height);
} }
var currentY = 0.0; var currentY = 0.0;
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
{ {
var end = textLine.FirstTextSourceIndex + textLine.Length - 1; var end = textLine.FirstTextSourceIndex + textLine.Length;
if (end < textPosition) if (end <= textPosition && end < _textSourceLength)
{ {
currentY += textLine.Height; currentY += textLine.Height;
@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting
} }
var result = new List<Rect>(TextLines.Count); var result = new List<Rect>(TextLines.Count);
var currentY = 0d; var currentY = 0d;
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length); var textBounds = textLine.GetTextBounds(start, length);
if(textBounds.Count > 0) if (textBounds.Count > 0)
{ {
foreach (var bounds in textBounds) foreach (var bounds in textBounds)
{ {
@ -262,7 +256,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length) if (textLine.FirstTextSourceIndex + textLine.Length >= start + length)
{ {
break; break;
} }
@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting
return GetHitTestResult(currentLine, characterHit, point); return GetHitTestResult(currentLine, characterHit, point);
} }
public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge) public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge)
{ {
if (charIndex < 0) if (charIndex < 0)
@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
if (charIndex >= textLine.FirstTextSourceIndex && if (charIndex >= textLine.FirstTextSourceIndex &&
charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1)) charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
{ {
return index; return index;
@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="left">The current left.</param> /// <param name="left">The current left.</param>
/// <param name="width">The current width.</param> /// <param name="width">The current width.</param>
/// <param name="height">The current height.</param> /// <param name="height">The current height.</param>
private static void UpdateBounds(TextLine textLine,ref double left, ref double width, ref double height) private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height)
{ {
var lineWidth = textLine.WidthIncludingTrailingWhitespace; var lineWidth = textLine.WidthIncludingTrailingWhitespace;
@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
Bounds = new Rect(0,0,0, textLine.Height); Bounds = new Rect(0, 0, 0, textLine.Height);
return new List<TextLine> { textLine }; return new List<TextLine> { textLine };
} }
@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
_paragraphProperties, previousLine?.TextLineBreak); _paragraphProperties, previousLine?.TextLineBreak);
if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
{ {
if(previousLine != null && previousLine.NewLineLength > 0) if (previousLine != null && previousLine.NewLineLength > 0)
{ {
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties); var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
} }
_textSourceLength += textLine.Length; _textSourceLength += textLine.Length;
//Fulfill max height constraint //Fulfill max height constraint
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight) if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
{ {
@ -485,12 +479,17 @@ namespace Avalonia.Media.TextFormatting
//Fulfill max lines constraint //Fulfill max lines constraint
if (MaxLines > 0 && textLines.Count >= MaxLines) if (MaxLines > 0 && textLines.Count >= MaxLines)
{ {
if(textLine.TextLineBreak is TextLineBreak lineBreak && lineBreak.RemainingRuns != null)
{
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
}
break; break;
} }
} }
//Make sure the TextLayout always contains at least on empty line //Make sure the TextLayout always contains at least on empty line
if(textLines.Count == 0) if (textLines.Count == 0)
{ {
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties); var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
@ -501,7 +500,7 @@ namespace Avalonia.Media.TextFormatting
Bounds = new Rect(left, 0, width, height); Bounds = new Rect(left, 0, width, height);
if(_paragraphProperties.TextAlignment == TextAlignment.Justify) if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
{ {
var whitespaceWidth = 0d; var whitespaceWidth = 0d;
@ -509,7 +508,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
if(lineWhitespaceWidth > whitespaceWidth) if (lineWhitespaceWidth > whitespaceWidth)
{ {
whitespaceWidth = lineWhitespaceWidth; whitespaceWidth = lineWhitespaceWidth;
} }
@ -517,7 +516,7 @@ namespace Avalonia.Media.TextFormatting
var justificationWidth = width - whitespaceWidth; var justificationWidth = width - whitespaceWidth;
if(justificationWidth > 0) if (justificationWidth > 0)
{ {
var justificationProperties = new InterWordJustification(justificationWidth); var justificationProperties = new InterWordJustification(justificationWidth);
@ -538,8 +537,13 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
/// <param name="width">The collapsing width.</param> /// <param name="width">The collapsing width.</param>
/// <returns>The <see cref="TextCollapsingProperties"/>.</returns> /// <returns>The <see cref="TextCollapsingProperties"/>.</returns>
private TextCollapsingProperties GetCollapsingProperties(double width) private TextCollapsingProperties? GetCollapsingProperties(double width)
{ {
if(_textTrimming == TextTrimming.None)
{
return null;
}
return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
} }
} }

2
src/Avalonia.Base/Media/TextFormatting/TextLine.cs

@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns> /// <returns>
/// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed. /// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed.
/// </returns> /// </returns>
public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList);
/// <summary> /// <summary>
/// Create a justified line based on justification text properties. /// Create a justified line based on justification text properties.

737
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
} }
/// <inheritdoc/> /// <inheritdoc/>
public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList)
{ {
if (collapsingPropertiesList.Length == 0) if (collapsingPropertiesList.Length == 0)
{ {
@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0]; var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
{
return this;
}
var collapsedRuns = collapsingProperties.Collapse(this); var collapsedRuns = collapsingProperties.Collapse(this);
if (collapsedRuns is null) if (collapsedRuns is null)
@ -166,58 +171,122 @@ namespace Avalonia.Media.TextFormatting
if (distance <= 0) if (distance <= 0)
{ {
// hit happens before the line, return the first position
var firstRun = _textRuns[0]; var firstRun = _textRuns[0];
if (firstRun is ShapedTextCharacters shapedTextCharacters) return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
{ }
return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
}
return _resolvedFlowDirection == FlowDirection.LeftToRight ? if (distance >= WidthIncludingTrailingWhitespace)
new CharacterHit(FirstTextSourceIndex) : {
new CharacterHit(FirstTextSourceIndex + Length); var lastRun = _textRuns[_textRuns.Count - 1];
return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width);
} }
// process hit that happens within the line // process hit that happens within the line
var characterHit = new CharacterHit(); var characterHit = new CharacterHit();
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var currentDistance = 0.0;
foreach (var currentRun in _textRuns) for (var i = 0; i < _textRuns.Count; i++)
{ {
switch (currentRun) var currentRun = _textRuns[i];
if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{ {
case ShapedTextCharacters shapedRun: var rightToLeftIndex = i;
currentPosition += currentRun.TextSourceLength;
while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
{
var nextShaped = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
if (nextShaped == null || nextShaped.ShapedBuffer.IsLeftToRight)
{ {
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); break;
}
var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); currentPosition += nextShaped.TextSourceLength;
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); rightToLeftIndex++;
}
for (var j = i; i <= rightToLeftIndex; j++)
{
if(j > _textRuns.Count - 1)
{
break; break;
} }
default:
currentRun = _textRuns[j];
if(currentDistance + currentRun.Size.Width <= distance)
{ {
if (distance < currentRun.Size.Width / 2) currentDistance += currentRun.Size.Width;
{ currentPosition -= currentRun.TextSourceLength;
characterHit = new CharacterHit(currentPosition);
} continue;
else
{
characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength);
}
break;
} }
characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
break;
}
} }
if (distance <= currentRun.Size.Width) if (currentDistance + currentRun.Size.Width < distance)
{ {
break; currentDistance += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
} }
distance -= currentRun.Size.Width; characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
currentPosition += currentRun.TextSourceLength;
break;
}
return characterHit;
}
private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance)
{
CharacterHit characterHit;
switch (run)
{
case ShapedTextCharacters shapedRun:
{
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
var offset = 0;
if (shapedRun.GlyphRun.IsLeftToRight)
{
offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
}
//else
//{
// offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
//}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
break;
}
default:
{
if (distance < run.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, run.TextSourceLength);
}
break;
}
} }
return characterHit; return characterHit;
@ -226,136 +295,168 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{ {
var isTrailingHit = characterHit.TrailingLength > 0; var flowDirection = _paragraphProperties.FlowDirection;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start;
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var remainingLength = characterIndex - FirstTextSourceIndex; var remainingLength = characterIndex - FirstTextSourceIndex;
GlyphRun? lastRun = null; var currentDistance = Start;
for (var index = 0; index < _textRuns.Count; index++) if (flowDirection == FlowDirection.LeftToRight)
{ {
var textRun = _textRuns[index]; for (var index = 0; index < _textRuns.Count; index++)
switch (textRun)
{ {
case ShapedTextCharacters shapedTextCharacters: var currentRun = _textRuns[index];
{
var currentRun = shapedTextCharacters.GlyphRun;
if (lastRun != null) if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{ {
if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight && var i = index;
currentRun.Characters.Start == characterHit.FirstCharacterIndex &&
characterHit.TrailingLength == 0) var rightToLeftWidth = currentRun.Size.Width;
{
return currentDistance;
}
}
//Look for a hit in within the current run while (i + 1 <= _textRuns.Count - 1)
if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length) {
var nextRun = _textRuns[i + 1];
if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight)
{ {
characterHit = new CharacterHit(textRun.Text.Start + remainingLength); i++;
var distance = currentRun.GetDistanceFromCharacterHit(characterHit); rightToLeftWidth += nextRun.Size.Width;
return currentDistance + distance; continue;
} }
break;
}
//Look at the left and right edge of the current run if(i > index)
if (currentRun.IsLeftToRight) {
while (i >= index)
{ {
if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) currentRun = _textRuns[i];
{
if (characterIndex <= currentPosition)
{
return currentDistance;
}
}
else
{
if (characterIndex == currentPosition)
{
return currentDistance;
}
}
if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit) rightToLeftWidth -= currentRun.Size.Width;
{
return currentDistance + currentRun.Size.Width; if (currentPosition + currentRun.TextSourceLength >= characterIndex)
}
}
else
{
if (characterIndex == currentPosition)
{ {
return currentDistance + currentRun.Size.Width; break;
} }
var nextRun = index + 1 < _textRuns.Count ? currentPosition += currentRun.TextSourceLength;
_textRuns[index + 1] as ShapedTextCharacters :
null;
if (nextRun != null) remainingLength -= currentRun.TextSourceLength;
{
if (nextRun.ShapedBuffer.IsLeftToRight) i--;
{
if (characterIndex == currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
else
{
if (currentPosition + nextRun.Text.Length == characterIndex)
{
return currentDistance;
}
}
}
else
{
if (characterIndex > currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
} }
lastRun = currentRun; currentDistance += rightToLeftWidth;
}
}
if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
{
return Math.Max(0, currentDistance + distance);
}
break; //No hit hit found so we add the full width
currentDistance += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
else
{
currentDistance += WidthIncludingTrailingWhitespace;
for (var index = _textRuns.Count - 1; index >= 0; index--)
{
var currentRun = _textRuns[index];
if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
flowDirection, out var distance, out var currentGlyphRun))
{
if (currentGlyphRun != null)
{
distance = currentGlyphRun.Size.Width - distance;
} }
default:
return Math.Max(0, currentDistance - distance);
}
//No hit hit found so we add the full width
currentDistance -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
return Math.Max(0, currentDistance);
}
private static bool TryGetDistanceFromCharacterHit(
DrawableTextRun currentRun,
CharacterHit characterHit,
int currentPosition,
int remainingLength,
FlowDirection flowDirection,
out double distance,
out GlyphRun? currentGlyphRun)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var isTrailingHit = characterHit.TrailingLength > 0;
distance = 0;
currentGlyphRun = null;
switch (currentRun)
{
case ShapedTextCharacters shapedTextCharacters:
{
currentGlyphRun = shapedTextCharacters.GlyphRun;
if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length)
{ {
if (characterIndex == currentPosition) characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
{
return currentDistance; distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit);
}
return true;
}
if (characterIndex == currentPosition + textRun.TextSourceLength) if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit)
{
if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
{ {
return currentDistance + textRun.Size.Width; distance = currentGlyphRun.Size.Width;
} }
break; return true;
} }
}
//No hit hit found so we add the full width break;
currentDistance += textRun.Size.Width; }
currentPosition += textRun.TextSourceLength; default:
remainingLength -= textRun.TextSourceLength; {
if (characterIndex == currentPosition)
{
return true;
}
if (remainingLength <= 0) if (characterIndex == currentPosition + currentRun.TextSourceLength)
{ {
break; distance = currentRun.Size.Width;
}
return true;
}
break;
}
} }
return currentDistance; return false;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -440,121 +541,168 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var characterLength = 0; var characterLength = 0;
var endX = startX; var endX = startX;
var runWidth = 0.0;
TextRunBounds? currentRunBounds = null;
if (currentRun is ShapedTextCharacters currentShapedRun) var currentShapedRun = currentRun as ShapedTextCharacters;
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset; if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = index;
startX += currentShapedRun.Size.Width;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
currentShapedRun.ShapedBuffer.IsLeftToRight ? {
new CharacterHit(startIndex + remainingLength) : var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
new CharacterHit(startIndex));
endX += endOffset; if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
{
break;
}
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( startX += nextShapedRun.Size.Width;
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startX += startOffset; rightToLeftIndex++;
}
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds))
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); {
startX = currentRunBounds!.Rectangle.Left;
endX = currentRunBounds.Rectangle.Right;
characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength); runWidth = currentRunBounds.Rectangle.Width;
}
currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ? currentDirection = FlowDirection.RightToLeft;
FlowDirection.LeftToRight :
FlowDirection.RightToLeft;
} }
else else
{ {
if (currentPosition < firstTextSourceIndex) if (currentShapedRun != null)
{ {
startX += currentRun.Size.Width; if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
} {
startX += currentRun.Size.Width;
if (currentPosition + currentRun.TextSourceLength <= characterIndex) currentPosition += currentRun.TextSourceLength;
{
endX += currentRun.Size.Width;
characterLength = currentRun.TextSourceLength; continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
if (currentPosition < startIndex)
{
startOffset = endOffset;
}
else
{
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
}
startX += startOffset;
endX += endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
currentDirection = FlowDirection.LeftToRight;
} }
} else
{
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
if (endX < startX) currentPosition += currentRun.TextSourceLength;
{
(endX, startX) = (startX, endX);
}
//Lines that only contain a linebreak need to be covered here continue;
if(characterLength == 0) }
{
characterLength = NewLineLength;
}
var runwidth = endX - startX; if (currentPosition < firstTextSourceIndex)
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun); {
startX += currentRun.Size.Width;
}
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{ {
currentRect = currentRect.WithWidth(currentWidth + runwidth); endX += currentRun.Size.Width;
var textBounds = result[result.Count - 1]; characterLength = currentRun.TextSourceLength;
}
}
textBounds.Rectangle = currentRect; if (endX < startX)
{
(endX, startX) = (startX, endX);
}
textBounds.TextRunBounds.Add(currentRunBounds); //Lines that only contain a linebreak need to be covered here
} if (characterLength == 0)
else {
{ characterLength = NewLineLength;
currentRect = currentRunBounds.Rectangle; }
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); runWidth = endX - startX;
} currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
currentWidth += runwidth; currentPosition += characterLength;
currentPosition += characterLength;
remainingLength -= characterLength;
}
if (currentDirection == FlowDirection.LeftToRight) if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
if (currentPosition > characterIndex) if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{ {
break; currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds!);
} }
} else
else
{
if (currentPosition <= firstTextSourceIndex)
{ {
break; currentRect = currentRunBounds!.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
} }
} }
startX = endX; currentWidth += runWidth;
lastDirection = currentDirection;
remainingLength -= characterLength;
if (remainingLength <= 0) if (remainingLength <= 0 || currentPosition >= characterIndex)
{ {
break; break;
} }
startX = endX;
lastDirection = currentDirection;
} }
return result; return result;
@ -571,7 +719,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength; var remainingLength = textLength;
var startX = Start + WidthIncludingTrailingWhitespace; var startX = WidthIncludingTrailingWhitespace;
double currentWidth = 0; double currentWidth = 0;
var currentRect = Rect.Empty; var currentRect = Rect.Empty;
@ -582,7 +730,7 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex)
{ {
startX -= currentRun.Size.Width; startX -= currentRun.Size.Width;
@ -601,20 +749,31 @@ namespace Avalonia.Media.TextFormatting
currentPosition += offset; currentPosition += offset;
var startIndex = currentRun.Text.Start + offset; var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( if (currentShapedRun.ShapedBuffer.IsLeftToRight)
currentShapedRun.ShapedBuffer.IsLeftToRight ? {
new CharacterHit(startIndex + remainingLength) : if (currentPosition < startIndex)
new CharacterHit(startIndex)); {
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
endX += endOffset - currentShapedRun.Size.Width; startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
currentShapedRun.ShapedBuffer.IsLeftToRight ? }
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startX += startOffset - currentShapedRun.Size.Width; startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -652,53 +811,150 @@ namespace Avalonia.Media.TextFormatting
} }
var runWidth = endX - startX; var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runWidth); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
} }
currentWidth += runWidth; currentWidth += runWidth;
currentPosition += characterLength; currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight) if (currentPosition > characterIndex)
{
break;
}
lastDirection = currentDirection;
remainingLength -= characterLength;
if (remainingLength <= 0)
{
break;
}
}
result.Reverse();
return result;
}
private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds)
{
textRunBounds = null;
for (var index = runIndex; index >= 0; index--)
{
if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
}
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{ {
if (currentPosition > characterIndex) startX -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var characterLength = 0;
var endX = startX;
if (currentRun is ShapedTextCharacters currentShapedRun)
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{ {
break; if (currentPosition < startIndex)
{
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
} }
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
} }
else else
{ {
if (currentPosition <= firstTextSourceIndex) if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{ {
break; endX -= currentRun.Size.Width;
}
if (currentPosition < firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
characterLength = currentRun.TextSourceLength;
} }
} }
lastDirection = currentDirection; if (endX < startX)
remainingLength -= characterLength; {
(endX, startX) = (startX, endX);
}
if (remainingLength <= 0) //Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{ {
break; characterLength = NewLineLength;
} }
var runWidth = endX - startX;
remainingLength -= characterLength;
currentPosition += characterLength;
textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
return true;
} }
return result; return false;
} }
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength) public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
@ -1280,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment; var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection; var paragraphFlowDirection = _paragraphProperties.FlowDirection;
if(textAlignment == TextAlignment.Justify)
{
textAlignment = TextAlignment.Start;
}
switch (textAlignment) switch (textAlignment)
{ {
case TextAlignment.Start: case TextAlignment.Start:
@ -1302,8 +1563,14 @@ namespace Avalonia.Media.TextFormatting
switch (textAlignment) switch (textAlignment)
{ {
case TextAlignment.Center: case TextAlignment.Center:
return Math.Max(0, (_paragraphWidth - width) / 2); var start = (_paragraphWidth - width) / 2;
if (paragraphFlowDirection == FlowDirection.RightToLeft)
{
start -= (widthIncludingTrailingWhitespace - width);
}
return Math.Max(0, start);
case TextAlignment.Right: case TextAlignment.Right:
return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);

2
src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs

@ -224,7 +224,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
} }
/// <summary> /// <summary>
/// Returns <see langword="true"/> if <paramref name="value"/> is between /// Returns <see langword="true"/> if <paramref name="cp"/> is between
/// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive. /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

4
src/Markup/Avalonia.Markup.Xaml/IAddChild.cs → src/Avalonia.Base/Metadata/IAddChild.cs

@ -1,11 +1,11 @@
namespace Avalonia.Markup.Xaml namespace Avalonia.Metadata
{ {
public interface IAddChild public interface IAddChild
{ {
void AddChild(object child); void AddChild(object child);
} }
public interface IAddChild<T> : IAddChild public interface IAddChild<T>
{ {
void AddChild(T child); void AddChild(T child);
} }

14
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -172,6 +172,20 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="custom">Custom draw operation</param> /// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom); void Custom(ICustomDrawOperation custom);
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>
object? GetFeature(Type t);
}
public static class DrawingContextImplExtensions
{
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>
public static T? GetFeature<T>(this IDrawingContextImpl context) where T : class =>
(T?)context.GetFeature(typeof(T));
} }
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl

4
src/Avalonia.Base/Platform/IGeometryImpl.cs

@ -38,8 +38,8 @@ namespace Avalonia.Platform
/// Intersects the geometry with another geometry. /// Intersects the geometry with another geometry.
/// </summary> /// </summary>
/// <param name="geometry">The other geometry.</param> /// <param name="geometry">The other geometry.</param>
/// <returns>A new <see cref="IGeometryImpl"/> representing the intersection.</returns> /// <returns>A new <see cref="IGeometryImpl"/> representing the intersection or <c>null</c> when the operation failed.</returns>
IGeometryImpl Intersect(IGeometryImpl geometry); IGeometryImpl? Intersect(IGeometryImpl geometry);
/// <summary> /// <summary>
/// Indicates whether the geometry's stroke contains the specified point. /// Indicates whether the geometry's stroke contains the specified point.

8
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -47,22 +47,22 @@ public class BclStorageFile : IStorageBookmarkFile
return Task.FromResult<IStorageFolder?>(null); return Task.FromResult<IStorageFolder?>(null);
} }
public Task<Stream> OpenRead() public Task<Stream> OpenReadAsync()
{ {
return Task.FromResult<Stream>(_fileInfo.OpenRead()); return Task.FromResult<Stream>(_fileInfo.OpenRead());
} }
public Task<Stream> OpenWrite() public Task<Stream> OpenWriteAsync()
{ {
return Task.FromResult<Stream>(_fileInfo.OpenWrite()); return Task.FromResult<Stream>(_fileInfo.OpenWrite());
} }
public virtual Task<string?> SaveBookmark() public virtual Task<string?> SaveBookmarkAsync()
{ {
return Task.FromResult<string?>(_fileInfo.FullName); return Task.FromResult<string?>(_fileInfo.FullName);
} }
public Task ReleaseBookmark() public Task ReleaseBookmarkAsync()
{ {
// No-op // No-op
return Task.CompletedTask; return Task.CompletedTask;

16
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using System.Security; using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -43,12 +45,22 @@ public class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult<IStorageFolder?>(null); return Task.FromResult<IStorageFolder?>(null);
} }
public virtual Task<string?> SaveBookmark() public Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
{
var items = _directoryInfo.GetDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
.Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
.ToArray();
return Task.FromResult<IReadOnlyList<IStorageItem>>(items);
}
public virtual Task<string?> SaveBookmarkAsync()
{ {
return Task.FromResult<string?>(_directoryInfo.FullName); return Task.FromResult<string?>(_directoryInfo.FullName);
} }
public Task ReleaseBookmark() public Task ReleaseBookmarkAsync()
{ {
// No-op // No-op
return Task.CompletedTask; return Task.CompletedTask;

2
src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs

@ -6,7 +6,7 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable] [NotClientImplementable]
public interface IStorageBookmarkItem : IStorageItem public interface IStorageBookmarkItem : IStorageItem
{ {
Task ReleaseBookmark(); Task ReleaseBookmarkAsync();
} }
[NotClientImplementable] [NotClientImplementable]

4
src/Avalonia.Base/Platform/Storage/IStorageFile.cs

@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem
/// <summary> /// <summary>
/// Opens a stream for read access. /// Opens a stream for read access.
/// </summary> /// </summary>
Task<Stream> OpenRead(); Task<Stream> OpenReadAsync();
/// <summary> /// <summary>
/// Returns true, if file is writeable. /// Returns true, if file is writeable.
@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem
/// <summary> /// <summary>
/// Opens stream for writing to the file. /// Opens stream for writing to the file.
/// </summary> /// </summary>
Task<Stream> OpenWrite(); Task<Stream> OpenWriteAsync();
} }

11
src/Avalonia.Base/Platform/Storage/IStorageFolder.cs

@ -1,4 +1,6 @@
using Avalonia.Metadata; using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Platform.Storage; namespace Avalonia.Platform.Storage;
@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable] [NotClientImplementable]
public interface IStorageFolder : IStorageItem public interface IStorageFolder : IStorageItem
{ {
/// <summary>
/// Gets the files and subfolders in the current folder.
/// </summary>
/// <returns>
/// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an <see cref="IStorageItem"/> implementation object.
/// </returns>
Task<IReadOnlyList<IStorageItem>> GetItemsAsync();
} }

2
src/Avalonia.Base/Platform/Storage/IStorageItem.cs

@ -44,7 +44,7 @@ public interface IStorageItem : IDisposable
/// <returns> /// <returns>
/// Returns identifier of a bookmark. Can be null if OS denied request. /// Returns identifier of a bookmark. Can be null if OS denied request.
/// </returns> /// </returns>
Task<string?> SaveBookmark(); Task<string?> SaveBookmarkAsync();
/// <summary> /// <summary>
/// Gets the parent folder of the current storage item. /// Gets the parent folder of the current storage item.

5
src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs

@ -3,8 +3,9 @@ using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations; // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations;
/// <summary> /// <summary>
/// The base class for both key-frame and expression animation instances /// The base class for both key-frame and expression animation instances
@ -79,4 +80,4 @@ internal abstract class AnimationInstanceBase : IAnimationInstance
_invalidated = true; _invalidated = true;
TargetObject.NotifyAnimatedValueChanged(Property); TargetObject.NotifyAnimatedValueChanged(Property);
} }
} }

11
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@ -1,12 +1,11 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable CheckNamespace // ReSharper disable CheckNamespace
using System;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
@ -14,10 +13,10 @@ namespace Avalonia.Rendering.Composition.Animations
/// This is the base class for ExpressionAnimation and KeyFrameAnimation. /// This is the base class for ExpressionAnimation and KeyFrameAnimation.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Use the <see cref="CompositionObject.StartAnimation"/> method to start the animation. /// Use the <see cref="CompositionObject.StartAnimation(string , CompositionAnimation)"/> method to start the animation.
/// Value parameters (as opposed to reference parameters which are set using <see cref="SetReferenceParameter"/>) /// Value parameters (as opposed to reference parameters which are set using <see cref="SetReferenceParameter"/>)
/// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called. /// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called.
/// Changing the value of the variable after <see cref="CompositionObject.StartAnimation"/> is called will not affect /// Changing the value of the variable after <see cref="CompositionObject.StartAnimation(string , CompositionAnimation)"/> is called will not affect
/// the value of the ExpressionAnimation. /// the value of the ExpressionAnimation.
/// See the remarks section of ExpressionAnimation for additional information. /// See the remarks section of ExpressionAnimation for additional information.
/// </remarks> /// </remarks>
@ -72,4 +71,4 @@ namespace Avalonia.Rendering.Composition.Animations
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase
@ -21,4 +23,4 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
} }
} }
} }

6
src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs

@ -3,6 +3,8 @@ using System;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -14,7 +16,7 @@ namespace Avalonia.Rendering.Composition.Animations
/// This contrasts <see cref="KeyFrameAnimation"/>s, which use an interpolator to define how the animating /// This contrasts <see cref="KeyFrameAnimation"/>s, which use an interpolator to define how the animating
/// property changes over time. The mathematical equation can be defined using references to properties /// property changes over time. The mathematical equation can be defined using references to properties
/// of Composition objects, mathematical functions and operators and Input. /// of Composition objects, mathematical functions and operators and Input.
/// Use the <see cref="CompositionObject.StartAnimation"/> method to start the animation. /// Use the <see cref="CompositionObject.StartAnimation(string , CompositionAnimation)"/> method to start the animation.
/// </remarks> /// </remarks>
public class ExpressionAnimation : CompositionAnimation public class ExpressionAnimation : CompositionAnimation
{ {
@ -50,4 +52,4 @@ namespace Avalonia.Rendering.Composition.Animations
=> new ExpressionAnimationInstance(ParsedExpression, => new ExpressionAnimationInstance(ParsedExpression,
targetObject, finalValue, CreateSnapshot()); targetObject, finalValue, CreateSnapshot());
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
@ -46,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations
_finalValue = finalValue; _finalValue = finalValue;
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs

@ -2,6 +2,8 @@ using System;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
internal interface IAnimationInstance internal interface IAnimationInstance
@ -13,4 +15,4 @@ namespace Avalonia.Rendering.Composition.Animations
void Deactivate(); void Deactivate();
void Invalidate(); void Invalidate();
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs

@ -2,6 +2,8 @@
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -12,4 +14,4 @@ namespace Avalonia.Rendering.Composition.Animations
internal void InternalOnly(); internal void InternalOnly();
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -79,4 +81,4 @@ namespace Avalonia.Rendering.Composition.Animations
return rv; return rv;
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Numerics; using System.Numerics;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -73,4 +75,4 @@ namespace Avalonia.Rendering.Composition.Animations
public static BooleanInterpolator Instance { get; } = new BooleanInterpolator(); public static BooleanInterpolator Instance { get; } = new BooleanInterpolator();
} }
} }

8
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs

@ -2,6 +2,8 @@ using System;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
@ -22,9 +24,9 @@ namespace Avalonia.Rendering.Composition.Animations
/// The delay behavior of the key frame animation. /// The delay behavior of the key frame animation.
/// </summary> /// </summary>
public AnimationDelayBehavior DelayBehavior { get; set; } public AnimationDelayBehavior DelayBehavior { get; set; }
/// <summary> /// <summary>
/// Delay before the animation starts after <see cref="CompositionObject.StartAnimation"/> is called. /// Delay before the animation starts after <see cref="CompositionObject.StartAnimation(string , CompositionAnimation)"/> is called.
/// </summary> /// </summary>
public System.TimeSpan DelayTime { get; set; } public System.TimeSpan DelayTime { get; set; }
@ -131,4 +133,4 @@ namespace Avalonia.Rendering.Composition.Animations
/// </summary> /// </summary>
SetToFinalValue SetToFinalValue
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs

@ -4,6 +4,8 @@ using Avalonia.Animation;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -175,4 +177,4 @@ namespace Avalonia.Rendering.Composition.Animations
base.Deactivate(); base.Deactivate();
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
@ -86,4 +88,4 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction); public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction);
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs

@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary> /// <summary>
@ -46,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations
public ExpressionVariant GetProperty(string name) => GetParameter(name); public ExpressionVariant GetProperty(string name) => GetParameter(name);
} }
} }

4
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -11,6 +11,8 @@ using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
/// <summary> /// <summary>
@ -69,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); _compositor.InvokeWhenReadyForNextCommit(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>

4
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
using Avalonia.VisualTree; using Avalonia.VisualTree;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
@ -72,4 +74,4 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
return true; return true;
return false; return false;
} }
} }

4
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities; using Avalonia.Utilities;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary> /// <summary>
@ -138,4 +140,4 @@ namespace Avalonia.Rendering.Composition
writer.Write((byte)(IsDisposed ? 1 : 0)); writer.Write((byte)(IsDisposed ? 1 : 0));
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary> /// <summary>
@ -144,4 +146,4 @@ namespace Avalonia.Rendering.Composition
TypeMismatch, TypeMismatch,
NotFound NotFound
} }
} }

4
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -4,6 +4,8 @@ using System.Numerics;
using Avalonia.Collections.Pooled; using Avalonia.Collections.Pooled;
using Avalonia.VisualTree; using Avalonia.VisualTree;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary> /// <summary>
@ -127,4 +129,4 @@ namespace Avalonia.Rendering.Composition
Compositor.Server.Render(); Compositor.Server.Render();
} }
} }
} }

17
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -12,6 +12,8 @@ using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading; using Avalonia.Threading;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary> /// <summary>
@ -31,6 +33,7 @@ namespace Avalonia.Rendering.Composition
internal IEasing DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit; private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new(); private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary> /// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU /// Creates a new compositor on a specified render loop that would use a particular GPU
@ -84,7 +87,7 @@ namespace Avalonia.Rendering.Composition
if (_invokeOnNextCommit != null) if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed); ScheduleCommitCallbacks(batch.Completed);
return batch.Completed; return _lastBatchCompleted = batch.Completed;
} }
async void ScheduleCommitCallbacks(Task task) async void ScheduleCommitCallbacks(Task task)
@ -137,5 +140,15 @@ namespace Avalonia.Rendering.Composition
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action); _invokeOnNextCommit.Add(action);
} }
public void InvokeWhenReadyForNextCommit(Action action)
{
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted)
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition);
else
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
}
} }
} }

4
src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs

@ -1,5 +1,7 @@
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary> /// <summary>
@ -21,4 +23,4 @@ namespace Avalonia.Rendering.Composition
base.OnRootChangedCore(); base.OnRootChangedCore();
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs

@ -4,6 +4,8 @@ using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities; using Avalonia.Utilities;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Drawing; namespace Avalonia.Rendering.Composition.Drawing;
/// <summary> /// <summary>
@ -99,4 +101,4 @@ internal class CompositionDrawListBuilder
if (count < Count) if (count < Count)
_operations!.RemoveRange(count, _operations.Count - count); _operations!.RemoveRange(count, _operations.Count - count);
} }
} }

7
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -8,6 +8,9 @@ using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
/// <summary> /// <summary>
@ -153,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
++_drawOperationIndex; ++_drawOperationIndex;
} }
public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {
@ -388,4 +393,4 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
} }
return null; return null;
} }
} }

4
src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs

@ -1,3 +1,5 @@
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
/// <summary> /// <summary>
@ -11,4 +13,4 @@ public static class ElementComposition
/// <param name="visual"></param> /// <param name="visual"></param>
/// <returns></returns> /// <returns></returns>
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual;
} }

4
src/Avalonia.Base/Rendering/Composition/Enums.cs

@ -1,5 +1,7 @@
using System; using System;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
public enum CompositionBlendMode public enum CompositionBlendMode
@ -117,4 +119,4 @@ namespace Avalonia.Rendering.Composition
Fill = 1, Fill = 1,
//TODO: Uniform, UniformToFill //TODO: Uniform, UniformToFill
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs

@ -4,6 +4,8 @@ using System.Numerics;
using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Animations;
using Avalonia.Utilities; using Avalonia.Utilities;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary> /// <summary>
@ -234,4 +236,4 @@ namespace Avalonia.Rendering.Composition.Expressions
public static BuiltInExpressionFfi Instance { get; } = new BuiltInExpressionFfi(); public static BuiltInExpressionFfi Instance { get; } = new BuiltInExpressionFfi();
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs

@ -5,6 +5,8 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Avalonia.Media; using Avalonia.Media;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary> /// <summary>
@ -181,4 +183,4 @@ namespace Avalonia.Rendering.Composition.Expressions
); );
} }
} }
} }

4
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@ -4,6 +4,8 @@ using System.Globalization;
using System.Reflection; using System.Reflection;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary> /// <summary>
@ -374,4 +376,4 @@ namespace Avalonia.Rendering.Composition.Expressions
} }

4
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
internal struct ExpressionEvaluationContext internal struct ExpressionEvaluationContext
@ -29,4 +31,4 @@ namespace Avalonia.Rendering.Composition.Expressions
{ {
bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result); bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result);
} }
} }

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

Loading…
Cancel
Save