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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}"
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}"
EndProject

6
azure-pipelines.yml

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

6
samples/BindingDemo/App.xaml

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

2
samples/BindingDemo/BindingDemo.csproj

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<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="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>

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

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

8
samples/ControlCatalog/App.xaml

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

20
samples/ControlCatalog/App.xaml.cs

@ -5,7 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia.Themes.Simple;
using Avalonia.Themes.Fluent;
using ControlCatalog.ViewModels;
@ -23,9 +23,9 @@ namespace ControlCatalog
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"))
@ -33,16 +33,16 @@ namespace ControlCatalog
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 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"))
{
@ -56,10 +56,10 @@ namespace ControlCatalog
{
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"))
{
@ -73,7 +73,7 @@ namespace ControlCatalog
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
},
Default
Simple
};
public override void Initialize()

2
samples/ControlCatalog/ControlCatalog.csproj

@ -29,7 +29,7 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.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.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="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

4
samples/ControlCatalog/MainView.xaml

@ -187,8 +187,8 @@
<ComboBox.Items>
<models:CatalogTheme>FluentLight</models:CatalogTheme>
<models:CatalogTheme>FluentDark</models:CatalogTheme>
<models:CatalogTheme>DefaultLight</models:CatalogTheme>
<models:CatalogTheme>DefaultDark</models:CatalogTheme>
<models:CatalogTheme>SimpleLight</models:CatalogTheme>
<models:CatalogTheme>SimpleDark</models:CatalogTheme>
</ComboBox.Items>
</ComboBox>
<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[2] = App.DataGridFluent;
}
else if (theme == CatalogTheme.DefaultLight)
else if (theme == CatalogTheme.SimpleLight)
{
App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light;
Application.Current.Styles[0] = App.DefaultLight;
Application.Current.Styles[1] = App.ColorPickerDefault;
Application.Current.Styles[2] = App.DataGridDefault;
App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light;
Application.Current.Styles[0] = App.SimpleLight;
Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridSimple;
}
else if (theme == CatalogTheme.DefaultDark)
else if (theme == CatalogTheme.SimpleDark)
{
App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark;
Application.Current.Styles[0] = App.DefaultDark;
Application.Current.Styles[1] = App.ColorPickerDefault;
Application.Current.Styles[2] = App.DataGridDefault;
App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark;
Application.Current.Styles[0] = App.SimpleDark;
Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridSimple;
}
}
};

10
samples/ControlCatalog/Models/CatalogTheme.cs

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

1
samples/ControlCatalog/Pages/ButtonsPage.xaml

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

9
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -10,11 +10,11 @@
<TextBlock Text="{Binding}"/>
</StackPanel>
</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.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)">
<Setter Property="FontWeight" Value="Bold" />
</Style>
@ -54,7 +54,8 @@
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" 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"
IsVisible="{Binding #ShowGDP.IsChecked}"/>
</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
#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);
#else
using var stream = await file.OpenWrite();
using var stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream);
#endif
await reader.WriteLineAsync(openedFileContent.Text);
@ -243,8 +243,8 @@ namespace ControlCatalog.Pages
async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
{
items ??= Array.Empty<IStorageItem>();
var mappedResults = items.Select(FullPathOrName).ToList();
bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark";
bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark";
var mappedResults = new List<string>();
if (items.FirstOrDefault() is IStorageItem item)
{
@ -267,9 +267,9 @@ Content:
if (file.CanOpenRead)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenRead();
await using var stream = await file.OpenReadAsync();
#else
using var stream = await file.OpenRead();
using var stream = await file.OpenReadAsync();
#endif
using var reader = new System.IO.StreamReader(stream);
@ -293,7 +293,19 @@ Content:
lastSelectedDirectory = await item.GetParentAsync();
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"
HorizontalAlignment="Center"
Spacing="16">
<Expander Header="Expand Up" ExpandDirection="Up">
<Expander Header="Expand Up" ExpandDirection="Up"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Down" ExpandDirection="Down">
<Expander Header="Expand Down" ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Left" ExpandDirection="Left">
<Expander Header="Expand Left" ExpandDirection="Left"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Expand Right" ExpandDirection="Right">
<Expander Header="Expand Right" ExpandDirection="Right"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
<CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox>
</StackPanel>
</StackPanel>
</UserControl>

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

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

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

@ -90,7 +90,6 @@ namespace ControlCatalog.Pages
private int _vertexBufferObject;
private int _indexBufferObject;
private int _vertexArrayObject;
private GlExtrasInterface _glExt;
private string GetShader(bool fragment, string shader)
{
@ -258,7 +257,6 @@ namespace ControlCatalog.Pages
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
{
CheckError(GL);
_glExt = new GlExtrasInterface(GL);
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_STATIC_DRAW);
CheckError(GL);
_vertexArrayObject = _glExt.GenVertexArray();
_glExt.BindVertexArray(_vertexArrayObject);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
CheckError(GL);
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
0, vertexSize, IntPtr.Zero);
@ -316,12 +314,13 @@ namespace ControlCatalog.Pages
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
_glExt.BindVertexArray(0);
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all resources.
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject });
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject });
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteBuffer(_indexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shaderProgram);
GL.DeleteShader(_fragmentShader);
GL.DeleteShader(_vertexShader);
@ -338,7 +337,7 @@ namespace ControlCatalog.Pages
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
_glExt.BindVertexArray(_vertexArrayObject);
GL.BindVertexArray(_vertexArrayObject);
GL.UseProgram(_shaderProgram);
CheckError(GL);
var projection =
@ -369,34 +368,5 @@ namespace ControlCatalog.Pages
if (_disco > 0.01)
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>
</Border>
<Border>
<RichTextBlock Margin="10" TextWrapping="Wrap">
<RichTextBlock SelectionBrush="LightBlue" IsTextSelectionEnabled="True" Margin="10" TextWrapping="Wrap">
This <Span FontWeight="Bold">is</Span> a
<Span Background="Silver" Foreground="Maroon">TextBlock</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.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
<SimpleTheme Mode="Light" />
</Application.Styles>
</Application>

2
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,7 +7,7 @@
<ItemGroup>
<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" />
</ItemGroup>

5
samples/Previewer/App.xaml

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

2
samples/Previewer/Previewer.csproj

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

8
samples/RenderDemo/App.xaml

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

6
samples/RenderDemo/Pages/CustomSkiaPage.cs

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

286
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -1,6 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catalog="using:ControlSamples">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catalog="using:ControlSamples">
<Design.PreviewWith>
<Border Width="400"
Height="150">
@ -20,25 +20,136 @@
</Border>
</Design.PreviewWith>
<Styles.Resources>
<x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneExpandWidth">220</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double>
<x:Double x:Key="NavigationItemHeight">36</x:Double>
<x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double>
<Thickness x:Key="HeaderMarginCollapsedPane">12,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedPane">52,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="NavigationContentShadow">0 0 1 1 #2000</BoxShadows>
</Styles.Resources>
<x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneExpandWidth">220</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double>
<x:Double x:Key="NavigationItemHeight">36</x:Double>
<x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double>
<Thickness x:Key="HeaderMarginCollapsedPane">12,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedPane">52,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="NavigationContentShadow">0 0 1 1 #2000</BoxShadows>
<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 -->
<Style Selector="catalog|HamburgerMenu">
<ControlTheme x:Key="{x:Type catalog:HamburgerMenu}" TargetType="catalog:HamburgerMenu">
<Setter Property="Padding" Value="12 8 4 0" />
<Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" />
<Setter Property="ItemContainerTheme" Value="{StaticResource HamburgerMenuTabItem}"/>
<Setter Property="TabStripPlacement" Value="Left" />
<Setter Property="Template">
<ControlTemplate>
<Panel Background="{TemplateBinding PaneBackground}">
@ -46,7 +157,8 @@
CompactPaneLength="{StaticResource PaneCompactWidth}"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="{StaticResource PaneExpandWidth}">
OpenPaneLength="{StaticResource PaneExpandWidth}"
PaneBackground="Transparent">
<SplitView.Pane>
<Grid Margin="0,0,1,5" RowDefinitions="Auto, *, Auto">
<Panel Height="{StaticResource HeaderHeight}" />
@ -58,8 +170,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
HorizontalAlignment="Stretch"
ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}">
Items="{TemplateBinding Items}">
<ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="HamburgerItemsPanel"
@ -70,7 +181,7 @@
</ScrollViewer>
<Button x:Name="SettingsButton"
Grid.Row="2"
Classes="NavigationButton"
Theme="{StaticResource NavigationButton}"
Content="Settings"
Flyout="{TemplateBinding (FlyoutBase.AttachedFlyout)}"
IsVisible="{Binding $parent[TabControl].(FlyoutBase.AttachedFlyout), Converter={x:Static ObjectConverters.IsNotNull}}" />
@ -82,6 +193,7 @@
<TextBlock x:Name="HeaderHolder"
VerticalAlignment="Center"
Classes="h1"
Margin="{StaticResource HeaderMarginExpandedPane}"
Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}">
<TextBlock.Transitions>
<Transitions>
@ -119,7 +231,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
Classes="NavigationButton"
Theme="{StaticResource NavigationButton}"
CornerRadius="4"
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}" />
@ -127,116 +239,26 @@
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginCollapsedPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay][IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedOverlayPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView">
<Setter Property="PaneBackground" Value="Transparent" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
<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>
<Style Selector="^ /template/ SplitView[IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginCollapsedPane}" />
</Style>
<Style Selector="^ /template/ SplitView[DisplayMode=Overlay][IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedOverlayPane}" />
</Style>
<Style Selector="^ /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" />
</Style>
<Style Selector="^ /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
</Style>
<Style Selector="^ /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
<Setter Property="BoxShadow" Value="{StaticResource NavigationContentShadow}" />
</Style>
<Style Selector="^ /template/ SplitView[DisplayMode=Inline][IsPaneOpen=True] Border#BackgroundBorder">
<Setter Property="CornerRadius" Value="8 0 0 0" />
</Style>
</ControlTheme>
<!-- 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>
</ResourceDictionary>

16
samples/VirtualizationDemo/App.xaml

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

2
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<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="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>

5
samples/interop/Direct3DInteropSample/App.paml

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

2
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,7 +22,7 @@
</EmbeddedResource>
</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.Win32\Avalonia.Win32.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.Platform;
using Avalonia.Rendering;
using Avalonia.Skia;
using Avalonia.Rendering.Composition;
using Avalonia.OpenGL;
namespace Avalonia
{
@ -42,6 +43,8 @@ namespace Avalonia.Android
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
internal static Compositor Compositor { get; private set; }
public static void Initialize(AndroidPlatformOptions options)
{
Options = options;
@ -62,12 +65,20 @@ namespace Avalonia.Android
{
EglPlatformOpenGlInterface.TryInitialize();
}
if (options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
}
}
}
public sealed class AndroidPlatformOptions
{
public bool UseDeferredRendering { get; set; } = true;
public bool UseDeferredRendering { get; set; } = false;
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.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root);
AndroidPlatform.Options.UseCompositor
? new CompositingRenderer(root, AndroidPlatform.Compositor) { DrawFps = true }
: AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root);
public virtual void Hide()
{

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

@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@ -35,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public bool CanBookmark => true;
public Task<string?> SaveBookmark()
public Task<string?> SaveBookmarkAsync()
{
Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.FromResult(Uri.ToString());
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.CompletedTask;
@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
{
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
@ -118,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
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"));
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"));
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.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls
{
@ -29,7 +30,11 @@ namespace Avalonia.Controls
public object? this[object key]
{
get => _inner?[key];
get
{
TryGetValue(key, out var value);
return value;
}
set
{
Inner[key] = value;
@ -119,6 +124,12 @@ namespace Avalonia.Controls
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()
{
if (_inner?.Count > 0)
@ -143,10 +154,8 @@ namespace Avalonia.Controls
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;
}
if (_mergedDictionaries != null)
{
@ -165,12 +174,28 @@ namespace Avalonia.Controls
public bool TryGetValue(object key, out object? value)
{
if (_inner is not null)
return _inner.TryGetValue(key, out value);
if (_inner is not null && _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;
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)
{
@ -198,12 +223,17 @@ namespace Avalonia.Controls
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)
{
@ -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;
_owner = _target.Owner;
if (_owner is object)
{
_owner.ResourcesChanged += ResourcesChanged;
}
}
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
{
public class TemplateResult<T>
public class TemplateResult<T> : ITemplateResult
{
public T Result { get; }
public INameScope NameScope { get; }
object? ITemplateResult.Result => Result;
public TemplateResult(T result, INameScope nameScope)
{

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

@ -32,10 +32,7 @@ namespace Avalonia.Input
DragCopy,
DragLink,
None,
[Obsolete("Use BottomSide")]
BottomSize = BottomSide
// 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
// SizeNorthWestSouthEast,

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

@ -13,9 +13,6 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; }
public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo)
@ -35,17 +32,6 @@ namespace Avalonia.Input
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)
: base(routedEvent)
{
@ -53,10 +39,6 @@ namespace Avalonia.Input
_target = target;
_targetLocation = targetLocation;
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;
namespace Avalonia.Input
@ -13,16 +12,6 @@ namespace Avalonia.Input
/// </summary>
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>
/// Gets or sets any key modifiers active at the time of focus.
/// </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.
/// </summary>
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
{
[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]
public enum KeyModifiers
{

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

@ -1,4 +1,3 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Input
@ -9,16 +8,5 @@ namespace Avalonia.Input
[NotClientImplementable]
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.Metadata;
@ -8,18 +6,6 @@ namespace Avalonia.Input
[NotClientImplementable]
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>
/// Gets a pointer for specific event args.
/// </summary>

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

@ -9,8 +9,6 @@ namespace Avalonia.Input
public Key Key { get; set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
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 }
};
[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)
{
Key = key;
@ -63,10 +56,7 @@ namespace Avalonia.Input
}
public Key Key { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
public KeyModifiers KeyModifiers { get; }
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 bool _disposed;
private PixelPoint? _position;
private MouseButton _lastMouseDownButton;
public MouseDevice(Pointer? pointer = null)
@ -29,43 +28,6 @@ namespace Avalonia.Input
_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)
{
if (!e.Handled && e is RawPointerEventArgs margs)
@ -96,7 +58,6 @@ namespace Avalonia.Input
if(mouse._disposed)
return;
_position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
var keyModifiers = e.InputModifiers.ToKeyModifiers();
switch (e.Type)
@ -145,7 +106,6 @@ namespace Avalonia.Input
private void LeaveWindow()
{
_position = null;
}
PointerPointProperties CreateProperties(RawPointerEventArgs args)
@ -324,19 +284,7 @@ namespace Avalonia.Input
_disposed = true;
_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)
{
return _pointer;

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

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -14,7 +12,6 @@ namespace Avalonia.Input
public class PenDevice : IPenDevice, IDisposable
{
private readonly Dictionary<long, Pointer> _pointers = new();
private readonly Dictionary<long, PixelPoint> _lastPositions = new();
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
@ -41,9 +38,7 @@ namespace Avalonia.Input
_pointers[e.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(),
PointerType.Pen, _pointers.Count == 0);
}
_lastPositions[e.RawPointerId] = e.Root.PointToScreen(e.Position);
var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(),
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt);
var keyModifiers = e.InputModifiers.ToKeyModifiers();
@ -69,7 +64,6 @@ namespace Avalonia.Input
{
pointer.Dispose();
_pointers.Remove(e.RawPointerId);
_lastPositions.Remove(e.RawPointerId);
}
}
@ -153,17 +147,6 @@ namespace Avalonia.Input
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)
{
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 Point _rootVisualPosition;
private readonly PointerPointProperties _properties;
private Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
@ -43,29 +43,6 @@ namespace Avalonia.Input
{
_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>
/// Gets specific pointer generated by input device.
@ -77,28 +54,6 @@ namespace Avalonia.Input
/// </summary>
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>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary>
@ -120,9 +75,6 @@ namespace Avalonia.Input
/// <returns>The pointer position in the control's coordinates.</returns>
public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
[Obsolete("Use GetCurrentPoint")]
public PointerPoint GetPointerPoint(IVisual? relativeTo) => GetCurrentPoint(relativeTo);
/// <summary>
/// Returns the PointerPoint associated with the current event
/// </summary>
@ -171,8 +123,6 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
private readonly int _clickCount;
public PointerPressedEventArgs(
IInteractive source,
IPointer pointer,
@ -184,13 +134,10 @@ namespace Avalonia.Input
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers)
{
_clickCount = clickCount;
ClickCount = clickCount;
}
public int ClickCount => _clickCount;
[Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();
public int ClickCount { get; }
}
public class PointerReleasedEventArgs : PointerEventArgs
@ -210,9 +157,6 @@ namespace Avalonia.Input
/// Gets the mouse button that triggered the corresponding PointerPressed event
/// </summary>
public MouseButton InitialPressMouseButton { get; }
[Obsolete("Use InitialPressMouseButton")]
public MouseButton MouseButton => InitialPressMouseButton;
}
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));
}
public PixelPoint? LastPosition => _lastPointer?.position;
public void OnCompleted()
{
ClearPointerOver();

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

@ -8,8 +8,6 @@ namespace Avalonia.Input.Raw
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; }
public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
@ -21,9 +19,6 @@ namespace Avalonia.Input.Raw
Data = data;
Effects = effects;
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;
}
[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 Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -20,9 +19,6 @@ namespace Avalonia.Input
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private Pointer? _lastPointer;
IInputElement? IPointerDevice.Captured => _lastPointer?.Captured;
RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown)
{
@ -32,10 +28,6 @@ namespace Avalonia.Input
return rv;
}
void IPointerDevice.Capture(IInputElement? control) => _lastPointer?.Capture(control);
Point IPointerDevice.GetPosition(IVisual relativeTo) => default;
public void ProcessRawEvent(RawInputEventArgs ev)
{
if (ev.Handled || _disposed)
@ -51,7 +43,6 @@ namespace Avalonia.Input
PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit);
}
_lastPointer = pointer;
var target = pointer.Captured ?? args.Root;
var updateKind = args.Type.ToUpdateKind();
@ -96,7 +87,6 @@ namespace Avalonia.Input
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left));
}
_lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchCancel)
@ -104,7 +94,6 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId);
using (pointer)
pointer.Capture(null);
_lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchUpdate)

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

@ -228,6 +228,8 @@ namespace Avalonia.Media
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)
{
throw new NotImplementedException();

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

@ -265,7 +265,7 @@ namespace Avalonia.Media
//RightToLeft
var glyphIndex = FindGlyphIndex(characterIndex);
if (GlyphClusters != null)
if (GlyphClusters != null && GlyphClusters.Count > 0)
{
if (characterIndex > GlyphClusters[0])
{
@ -445,7 +445,7 @@ namespace Avalonia.Media
/// </returns>
public int FindGlyphIndex(int characterIndex)
{
if (GlyphClusters == null)
if (GlyphClusters == null || GlyphClusters.Count == 0)
{
return characterIndex;
}
@ -614,17 +614,29 @@ namespace Avalonia.Media
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)
{
var firstCluster = GlyphClusters[0];
firstCluster = GlyphClusters[0];
lastCluster = GlyphClusters[GlyphClusters.Count - 1];
_offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
}
var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
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++)
{
@ -635,16 +647,16 @@ namespace Avalonia.Media
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 _);
}
}
else
{
for (var index = 0; index < glyphCount; index++)
for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
{
width -= GetGlyphAdvance(index, out _);
}
@ -654,16 +666,15 @@ namespace Avalonia.Media
height);
}
private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount)
{
glyphCount = 0;
newLineLength = 0;
if (Characters.IsEmpty)
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
{
if (isReversed)
{
return 0;
return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
}
glyphCount = 0;
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
@ -732,6 +743,78 @@ namespace Avalonia.Media
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)
{
_glyphRunImpl?.Dispose();

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

@ -209,7 +209,7 @@ namespace Avalonia.Media
var pen = new Pen(Stroke ?? defaultBrush, thickness,
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.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
@ -116,7 +117,30 @@ namespace Avalonia.Media.TextFormatting
length = text.Length;
}
length = CoerceLength(text, length);
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)
{
var lineImpl = textLine as TextLineImpl;
if(lineImpl is null)
{
return;
}
var paragraphWidth = Width;
if (double.IsInfinity(paragraphWidth))
@ -22,12 +29,12 @@ namespace Avalonia.Media.TextFormatting
return;
}
if (textLine.NewLineLength > 0)
if (lineImpl.NewLineLength > 0)
{
return;
}
var textLineBreak = textLine.TextLineBreak;
var textLineBreak = lineImpl.TextLineBreak;
if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
{
@ -39,7 +46,7 @@ namespace Avalonia.Media.TextFormatting
var breakOportunities = new Queue<int>();
foreach (var textRun in textLine.TextRuns)
foreach (var textRun in lineImpl.TextRuns)
{
var text = textRun.Text;
@ -68,10 +75,10 @@ namespace Avalonia.Media.TextFormatting
return;
}
var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace);
var remainingSpace = Math.Max(0, paragraphWidth - lineImpl.WidthIncludingTrailingWhitespace);
var spacing = remainingSpace / breakOportunities.Count;
foreach (var textRun in textLine.TextRuns)
foreach (var textRun in lineImpl.TextRuns)
{
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"/>.
/// </summary>
/// <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)
{
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="previousProperties"></param>
/// <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)
{
var defaultTypeface = defaultProperties.Typeface;
@ -76,7 +76,7 @@ namespace Avalonia.Media.TextFormatting
{
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),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -86,10 +86,10 @@ namespace Avalonia.Media.TextFormatting
return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface),
biDiLevel);
}
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),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -106,12 +106,12 @@ namespace Avalonia.Media.TextFormatting
{
continue;
}
codepoint = codepointEnumerator.Current;
break;
}
//ToDo: Fix FontFamily fallback
var matchFound =
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
@ -157,14 +157,14 @@ namespace Avalonia.Media.TextFormatting
/// <param name="script"></param>
/// <returns></returns>
protected static bool TryGetShapeableLength(
ReadOnlySlice<char> text,
Typeface typeface,
ReadOnlySlice<char> text,
Typeface typeface,
Typeface? defaultTypeface,
out int length,
out Script script)
{
length = 0;
script = Script.Unknown;
script = Script.Unknown;
if (text.Length == 0)
{
@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting
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;
}
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{
break;
}
if (currentScript != script)
{
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;
MaxLines = maxLines;
MaxLines = maxLines;
TextLines = CreateTextLines();
}
@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout(
ITextSource textSource,
TextParagraphProperties paragraphProperties,
TextParagraphProperties paragraphProperties,
TextTrimming? textTrimming = null,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting
return new Rect();
}
if (textPosition < 0 || textPosition >= _textSourceLength)
if (textPosition < 0)
{
var lastLine = TextLines[TextLines.Count - 1];
var lineX = lastLine.Width;
var lineY = Bounds.Bottom - lastLine.Height;
return new Rect(lineX, lineY, 0, lastLine.Height);
textPosition = _textSourceLength;
}
var currentY = 0.0;
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;
@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting
}
var result = new List<Rect>(TextLines.Count);
var currentY = 0d;
foreach (var textLine in TextLines)
@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length);
if(textBounds.Count > 0)
if (textBounds.Count > 0)
{
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;
}
@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting
return GetHitTestResult(currentLine, characterHit, point);
}
public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge)
{
if (charIndex < 0)
@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if (charIndex >= textLine.FirstTextSourceIndex &&
if (charIndex >= textLine.FirstTextSourceIndex &&
charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
{
return index;
@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="left">The current left.</param>
/// <param name="width">The current width.</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;
@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting
{
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 };
}
@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
_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);
@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
}
_textSourceLength += textLine.Length;
//Fulfill max height constraint
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
{
@ -485,12 +479,17 @@ namespace Avalonia.Media.TextFormatting
//Fulfill max lines constraint
if (MaxLines > 0 && textLines.Count >= MaxLines)
{
if(textLine.TextLineBreak is TextLineBreak lineBreak && lineBreak.RemainingRuns != null)
{
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
}
break;
}
}
//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);
@ -501,7 +500,7 @@ namespace Avalonia.Media.TextFormatting
Bounds = new Rect(left, 0, width, height);
if(_paragraphProperties.TextAlignment == TextAlignment.Justify)
if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
{
var whitespaceWidth = 0d;
@ -509,7 +508,7 @@ namespace Avalonia.Media.TextFormatting
{
var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
if(lineWhitespaceWidth > whitespaceWidth)
if (lineWhitespaceWidth > whitespaceWidth)
{
whitespaceWidth = lineWhitespaceWidth;
}
@ -517,7 +516,7 @@ namespace Avalonia.Media.TextFormatting
var justificationWidth = width - whitespaceWidth;
if(justificationWidth > 0)
if (justificationWidth > 0)
{
var justificationProperties = new InterWordJustification(justificationWidth);
@ -538,8 +537,13 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="width">The collapsing width.</param>
/// <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));
}
}

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

@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>
/// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed.
/// </returns>
public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList);
/// <summary>
/// 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/>
public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList)
{
if (collapsingPropertiesList.Length == 0)
{
@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
{
return this;
}
var collapsedRuns = collapsingProperties.Collapse(this);
if (collapsedRuns is null)
@ -166,58 +171,122 @@ namespace Avalonia.Media.TextFormatting
if (distance <= 0)
{
// hit happens before the line, return the first position
var firstRun = _textRuns[0];
if (firstRun is ShapedTextCharacters shapedTextCharacters)
{
return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
}
return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
}
return _resolvedFlowDirection == FlowDirection.LeftToRight ?
new CharacterHit(FirstTextSourceIndex) :
new CharacterHit(FirstTextSourceIndex + Length);
if (distance >= WidthIncludingTrailingWhitespace)
{
var lastRun = _textRuns[_textRuns.Count - 1];
return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width);
}
// process hit that happens within the line
var characterHit = new CharacterHit();
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;
}
default:
currentRun = _textRuns[j];
if(currentDistance + currentRun.Size.Width <= distance)
{
if (distance < currentRun.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength);
}
break;
currentDistance += currentRun.Size.Width;
currentPosition -= currentRun.TextSourceLength;
continue;
}
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;
currentPosition += currentRun.TextSourceLength;
characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
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;
@ -226,136 +295,168 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
var isTrailingHit = characterHit.TrailingLength > 0;
var flowDirection = _paragraphProperties.FlowDirection;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start;
var currentPosition = 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];
switch (textRun)
for (var index = 0; index < _textRuns.Count; index++)
{
case ShapedTextCharacters shapedTextCharacters:
{
var currentRun = shapedTextCharacters.GlyphRun;
var currentRun = _textRuns[index];
if (lastRun != null)
{
if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight &&
currentRun.Characters.Start == characterHit.FirstCharacterIndex &&
characterHit.TrailingLength == 0)
{
return currentDistance;
}
}
if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{
var i = index;
var rightToLeftWidth = currentRun.Size.Width;
//Look for a hit in within the current run
if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
while (i + 1 <= _textRuns.Count - 1)
{
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 (currentRun.IsLeftToRight)
if(i > index)
{
while (i >= index)
{
if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
{
if (characterIndex <= currentPosition)
{
return currentDistance;
}
}
else
{
if (characterIndex == currentPosition)
{
return currentDistance;
}
}
currentRun = _textRuns[i];
if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
{
return currentDistance + currentRun.Size.Width;
}
}
else
{
if (characterIndex == currentPosition)
rightToLeftWidth -= currentRun.Size.Width;
if (currentPosition + currentRun.TextSourceLength >= characterIndex)
{
return currentDistance + currentRun.Size.Width;
break;
}
var nextRun = index + 1 < _textRuns.Count ?
_textRuns[index + 1] as ShapedTextCharacters :
null;
currentPosition += currentRun.TextSourceLength;
if (nextRun != null)
{
if (nextRun.ShapedBuffer.IsLeftToRight)
{
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;
}
}
remainingLength -= currentRun.TextSourceLength;
i--;
}
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)
{
return currentDistance;
}
characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
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
currentDistance += textRun.Size.Width;
currentPosition += textRun.TextSourceLength;
remainingLength -= textRun.TextSourceLength;
break;
}
default:
{
if (characterIndex == currentPosition)
{
return true;
}
if (remainingLength <= 0)
{
break;
}
if (characterIndex == currentPosition + currentRun.TextSourceLength)
{
distance = currentRun.Size.Width;
return true;
}
break;
}
}
return currentDistance;
return false;
}
/// <inheritdoc/>
@ -440,121 +541,168 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var characterLength = 0;
var endX = startX;
var runWidth = 0.0;
TextRunBounds? currentRunBounds = null;
if (currentRun is ShapedTextCharacters currentShapedRun)
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var currentShapedRun = currentRun as ShapedTextCharacters;
var startIndex = currentRun.Text.Start + offset;
if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = index;
startX += currentShapedRun.Size.Width;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
{
var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
endX += endOffset;
if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
{
break;
}
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startX += nextShapedRun.Size.Width;
startX += startOffset;
rightToLeftIndex++;
}
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds))
{
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 ?
FlowDirection.LeftToRight :
FlowDirection.RightToLeft;
currentDirection = FlowDirection.RightToLeft;
}
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)
{
endX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
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)
{
(endX, startX) = (startX, endX);
}
currentPosition += currentRun.TextSourceLength;
//Lines that only contain a linebreak need to be covered here
if(characterLength == 0)
{
characterLength = NewLineLength;
}
continue;
}
var runwidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
if (currentPosition < firstTextSourceIndex)
{
startX += currentRun.Size.Width;
}
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentWidth + runwidth);
if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
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);
}
else
{
currentRect = currentRunBounds.Rectangle;
//Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
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
{
if (currentPosition <= firstTextSourceIndex)
else
{
break;
currentRect = currentRunBounds!.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
}
startX = endX;
lastDirection = currentDirection;
remainingLength -= characterLength;
currentWidth += runWidth;
if (remainingLength <= 0)
if (remainingLength <= 0 || currentPosition >= characterIndex)
{
break;
}
startX = endX;
lastDirection = currentDirection;
}
return result;
@ -571,7 +719,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength;
var startX = Start + WidthIncludingTrailingWhitespace;
var startX = WidthIncludingTrailingWhitespace;
double currentWidth = 0;
var currentRect = Rect.Empty;
@ -582,7 +730,7 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
@ -601,20 +749,31 @@ namespace Avalonia.Media.TextFormatting
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
if (currentPosition < 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(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(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 startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -652,53 +811,150 @@ namespace Avalonia.Media.TextFormatting
}
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);
}
else
{
currentRect = currentRunBounds.Rectangle;
textBounds.TextRunBounds.Add(currentRunBounds);
}
else
{
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;
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
{
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;
remainingLength -= characterLength;
if (endX < startX)
{
(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)
@ -1280,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection;
if(textAlignment == TextAlignment.Justify)
{
textAlignment = TextAlignment.Start;
}
switch (textAlignment)
{
case TextAlignment.Start:
@ -1302,8 +1563,14 @@ namespace Avalonia.Media.TextFormatting
switch (textAlignment)
{
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:
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>
/// 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.
/// </summary>
[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
{
void AddChild(object child);
}
public interface IAddChild<T> : IAddChild
public interface IAddChild<T>
{
void AddChild(T child);
}

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

@ -172,6 +172,20 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="custom">Custom draw operation</param>
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

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

@ -38,8 +38,8 @@ namespace Avalonia.Platform
/// Intersects the geometry with another geometry.
/// </summary>
/// <param name="geometry">The other geometry.</param>
/// <returns>A new <see cref="IGeometryImpl"/> representing the intersection.</returns>
IGeometryImpl Intersect(IGeometryImpl geometry);
/// <returns>A new <see cref="IGeometryImpl"/> representing the intersection or <c>null</c> when the operation failed.</returns>
IGeometryImpl? Intersect(IGeometryImpl geometry);
/// <summary>
/// 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);
}
public Task<Stream> OpenRead()
public Task<Stream> OpenReadAsync()
{
return Task.FromResult<Stream>(_fileInfo.OpenRead());
}
public Task<Stream> OpenWrite()
public Task<Stream> OpenWriteAsync()
{
return Task.FromResult<Stream>(_fileInfo.OpenWrite());
}
public virtual Task<string?> SaveBookmark()
public virtual Task<string?> SaveBookmarkAsync()
{
return Task.FromResult<string?>(_fileInfo.FullName);
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;

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

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Avalonia.Metadata;
@ -43,12 +45,22 @@ public class BclStorageFolder : IStorageBookmarkFolder
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);
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;

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

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

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

@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem
/// <summary>
/// Opens a stream for read access.
/// </summary>
Task<Stream> OpenRead();
Task<Stream> OpenReadAsync();
/// <summary>
/// Returns true, if file is writeable.
@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem
/// <summary>
/// Opens stream for writing to the file.
/// </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;
@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
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 identifier of a bookmark. Can be null if OS denied request.
/// </returns>
Task<string?> SaveBookmark();
Task<string?> SaveBookmarkAsync();
/// <summary>
/// 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.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>
/// The base class for both key-frame and expression animation instances
@ -79,4 +80,4 @@ internal abstract class AnimationInstanceBase : IAnimationInstance
_invalidated = true;
TargetObject.NotifyAnimatedValueChanged(Property);
}
}
}

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

@ -1,12 +1,11 @@
// ReSharper disable InconsistentNaming
// ReSharper disable CheckNamespace
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Expressions;
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
{
@ -14,10 +13,10 @@ namespace Avalonia.Rendering.Composition.Animations
/// This is the base class for ExpressionAnimation and KeyFrameAnimation.
/// </summary>
/// <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"/>)
/// 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.
/// See the remarks section of ExpressionAnimation for additional information.
/// </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;
// 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
{
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.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
{
/// <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
/// property changes over time. The mathematical equation can be defined using references to properties
/// 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>
public class ExpressionAnimation : CompositionAnimation
{
@ -50,4 +52,4 @@ namespace Avalonia.Rendering.Composition.Animations
=> new ExpressionAnimationInstance(ParsedExpression,
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.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
{
@ -46,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations
_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.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
{
internal interface IAnimationInstance
@ -13,4 +15,4 @@ namespace Avalonia.Rendering.Composition.Animations
void Deactivate();
void Invalidate();
}
}
}

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

@ -2,6 +2,8 @@
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
{
/// <summary>
@ -12,4 +14,4 @@ namespace Avalonia.Rendering.Composition.Animations
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 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
{
/// <summary>
@ -79,4 +81,4 @@ namespace Avalonia.Rendering.Composition.Animations
return rv;
}
}
}
}

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

@ -1,6 +1,8 @@
using System;
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
{
/// <summary>
@ -73,4 +75,4 @@ namespace Avalonia.Rendering.Composition.Animations
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.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
{
@ -22,9 +24,9 @@ namespace Avalonia.Rendering.Composition.Animations
/// The delay behavior of the key frame animation.
/// </summary>
public AnimationDelayBehavior DelayBehavior { get; set; }
/// <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>
public System.TimeSpan DelayTime { get; set; }
@ -131,4 +133,4 @@ namespace Avalonia.Rendering.Composition.Animations
/// </summary>
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.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
{
/// <summary>
@ -175,4 +177,4 @@ namespace Avalonia.Rendering.Composition.Animations
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.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
{
@ -86,4 +88,4 @@ namespace Avalonia.Rendering.Composition.Animations
{
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 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
{
/// <summary>
@ -46,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations
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.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;
/// <summary>
@ -69,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate)
return;
_queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition);
_compositor.InvokeWhenReadyForNextCommit(_update);
}
/// <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.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;
@ -72,4 +74,4 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
return true;
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.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
{
/// <summary>
@ -138,4 +140,4 @@ namespace Avalonia.Rendering.Composition
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.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
{
/// <summary>
@ -144,4 +146,4 @@ namespace Avalonia.Rendering.Composition
TypeMismatch,
NotFound
}
}
}

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

@ -4,6 +4,8 @@ using System.Numerics;
using Avalonia.Collections.Pooled;
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
{
/// <summary>
@ -127,4 +129,4 @@ namespace Avalonia.Rendering.Composition
Compositor.Server.Render();
}
}
}
}

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

@ -12,6 +12,8 @@ using Avalonia.Rendering.Composition.Transport;
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
{
/// <summary>
@ -31,6 +33,7 @@ namespace Avalonia.Rendering.Composition
internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary>
/// 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)
ScheduleCommitCallbacks(batch.Completed);
return batch.Completed;
return _lastBatchCompleted = batch.Completed;
}
async void ScheduleCommitCallbacks(Task task)
@ -137,5 +140,15 @@ namespace Avalonia.Rendering.Composition
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_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;
// 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
{
/// <summary>
@ -21,4 +23,4 @@ namespace Avalonia.Rendering.Composition
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.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;
/// <summary>
@ -99,4 +101,4 @@ internal class CompositionDrawListBuilder
if (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.Utilities;
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;
/// <summary>
@ -153,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
++_drawOperationIndex;
}
public object? GetFeature(Type t) => null;
/// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
@ -388,4 +393,4 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
}
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;
/// <summary>
@ -11,4 +13,4 @@ public static class ElementComposition
/// <param name="visual"></param>
/// <returns></returns>
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual;
}
}

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

@ -1,5 +1,7 @@
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
{
public enum CompositionBlendMode
@ -117,4 +119,4 @@ namespace Avalonia.Rendering.Composition
Fill = 1,
//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.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
{
/// <summary>
@ -234,4 +236,4 @@ namespace Avalonia.Rendering.Composition.Expressions
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 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
{
/// <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 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
{
/// <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 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
{
internal struct ExpressionEvaluationContext
@ -29,4 +31,4 @@ namespace Avalonia.Rendering.Composition.Expressions
{
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