Browse Source

Merge branch 'master' into fixes/win32-consistent-minimize-restore-window-size

pull/8574/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
63eb5f285d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/BindingDemo/App.xaml
  2. 2
      samples/BindingDemo/BindingDemo.csproj
  3. 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  4. 8
      samples/ControlCatalog/App.xaml
  5. 1
      samples/ControlCatalog/Pages/ButtonsPage.xaml
  6. 15
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  7. 2
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  8. 44
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  9. 2
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  10. 27
      samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs
  11. 8
      samples/RenderDemo/App.xaml
  12. 286
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  13. 5
      src/Avalonia.Base/Input/Cursor.cs
  14. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  15. 11
      src/Avalonia.Base/Input/GotFocusEventArgs.cs
  16. 5
      src/Avalonia.Base/Input/IInputRoot.cs
  17. 13
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  18. 12
      src/Avalonia.Base/Input/IMouseDevice.cs
  19. 14
      src/Avalonia.Base/Input/IPointerDevice.cs
  20. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  21. 12
      src/Avalonia.Base/Input/KeyGesture.cs
  22. 54
      src/Avalonia.Base/Input/MouseDevice.cs
  23. 19
      src/Avalonia.Base/Input/PenDevice.cs
  24. 62
      src/Avalonia.Base/Input/PointerEventArgs.cs
  25. 2
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  26. 5
      src/Avalonia.Base/Input/Raw/RawDragEvent.cs
  27. 3
      src/Avalonia.Base/Input/Raw/RawTouchEventArgs.cs
  28. 11
      src/Avalonia.Base/Input/TouchDevice.cs
  29. 109
      src/Avalonia.Base/Media/GlyphRun.cs
  30. 2
      src/Avalonia.Base/Media/TextDecoration.cs
  31. 51
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  32. 433
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  33. 4
      src/Avalonia.Base/Metadata/IAddChild.cs
  34. 85
      src/Avalonia.Base/StyledElement.cs
  35. 2
      src/Avalonia.Base/Styling/ChildSelector.cs
  36. 67
      src/Avalonia.Base/Styling/ControlTheme.cs
  37. 2
      src/Avalonia.Base/Styling/DescendentSelector.cs
  38. 2
      src/Avalonia.Base/Styling/IStyle.cs
  39. 7
      src/Avalonia.Base/Styling/IStyleable.cs
  40. 14
      src/Avalonia.Base/Styling/NestingSelector.cs
  41. 2
      src/Avalonia.Base/Styling/NotSelector.cs
  42. 2
      src/Avalonia.Base/Styling/NthChildSelector.cs
  43. 12
      src/Avalonia.Base/Styling/OrSelector.cs
  44. 2
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  45. 31
      src/Avalonia.Base/Styling/Selector.cs
  46. 166
      src/Avalonia.Base/Styling/Style.cs
  47. 124
      src/Avalonia.Base/Styling/StyleBase.cs
  48. 2
      src/Avalonia.Base/Styling/StyleCache.cs
  49. 10
      src/Avalonia.Base/Styling/StyleChildren.cs
  50. 19
      src/Avalonia.Base/Styling/Styler.cs
  51. 7
      src/Avalonia.Base/Styling/Styles.cs
  52. 2
      src/Avalonia.Base/Styling/TemplateSelector.cs
  53. 2
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  54. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  55. 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  56. 4
      src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml
  57. 25
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  58. 23
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
  59. 346
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  60. 100
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
  61. 165
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  62. 76
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  63. 27
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  64. 8
      src/Avalonia.Controls/Chrome/TitleBar.cs
  65. 2
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  66. 22
      src/Avalonia.Controls/Design.cs
  67. 34
      src/Avalonia.Controls/Documents/Inline.cs
  68. 78
      src/Avalonia.Controls/Documents/InlineCollection.cs
  69. 9
      src/Avalonia.Controls/Documents/LineBreak.cs
  70. 35
      src/Avalonia.Controls/Documents/Span.cs
  71. 12
      src/Avalonia.Controls/Documents/TextElement.cs
  72. 6
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  73. 16
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  74. 21
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  75. 5
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  76. 21
      src/Avalonia.Controls/ItemsControl.cs
  77. 9
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  78. 13
      src/Avalonia.Controls/Platform/PlatformManager.cs
  79. 11
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  80. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  81. 11
      src/Avalonia.Controls/ProgressBar.cs
  82. 1
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  83. 160
      src/Avalonia.Controls/RichTextBlock.cs
  84. 22
      src/Avalonia.Controls/TextBlock.cs
  85. 285
      src/Avalonia.Controls/TextBox.cs
  86. 5
      src/Avalonia.Controls/TopLevel.cs
  87. 5
      src/Avalonia.Controls/TreeViewItem.cs
  88. 22
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  89. 11
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  90. 11
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  91. 4
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  92. 1
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  93. 14
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  94. 376
      src/Avalonia.OpenGL/Egl/EglConsts.cs
  95. 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  96. 4
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  97. 225
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  98. 44
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  99. 10783
      src/Avalonia.OpenGL/GlConsts.cs
  100. 103
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs

4
samples/BindingDemo/App.xaml

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

2
samples/BindingDemo/BindingDemo.csproj

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

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

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

8
samples/ControlCatalog/App.xaml

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

1
samples/ControlCatalog/Pages/ButtonsPage.xaml

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

15
samples/ControlCatalog/Pages/ExpanderPage.xaml

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

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

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

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

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

2
samples/ControlCatalog/Pages/TextBlockPage.xaml

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

27
samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs

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

8
samples/RenderDemo/App.xaml

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

286
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -166,58 +166,74 @@ namespace Avalonia.Media.TextFormatting
if (distance <= 0) if (distance <= 0)
{ {
// hit happens before the line, return the first position
var firstRun = _textRuns[0]; var firstRun = _textRuns[0];
if (firstRun is ShapedTextCharacters shapedTextCharacters) return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
{ }
return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
}
return _resolvedFlowDirection == FlowDirection.LeftToRight ? if (distance > WidthIncludingTrailingWhitespace)
new CharacterHit(FirstTextSourceIndex) : {
new CharacterHit(FirstTextSourceIndex + Length); var lastRun = _textRuns[_textRuns.Count - 1];
return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width);
} }
// process hit that happens within the line // process hit that happens within the line
var characterHit = new CharacterHit(); var characterHit = new CharacterHit();
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var currentDistance = 0.0;
foreach (var currentRun in _textRuns) foreach (var currentRun in _textRuns)
{ {
switch (currentRun) if (currentDistance + currentRun.Size.Width < distance)
{ {
case ShapedTextCharacters shapedRun: currentDistance += currentRun.Size.Width;
{ currentPosition += currentRun.TextSourceLength;
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); continue;
}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
break; break;
} }
default:
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 = Math.Max(0, currentPosition - shapedRun.Text.Start);
if (!shapedRun.GlyphRun.IsLeftToRight)
{ {
if (distance < currentRun.Size.Width / 2) offset = Math.Max(0, offset - shapedRun.Text.End);
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength);
}
break;
} }
}
if (distance <= currentRun.Size.Width) characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
{
break;
}
distance -= currentRun.Size.Width; break;
currentPosition += currentRun.TextSourceLength; }
default:
{
if (distance < run.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, run.TextSourceLength);
}
break;
}
} }
return characterHit; return characterHit;
@ -226,136 +242,122 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit) public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{ {
var isTrailingHit = characterHit.TrailingLength > 0; var flowDirection = _paragraphProperties.FlowDirection;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start;
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var remainingLength = characterIndex - FirstTextSourceIndex; var remainingLength = characterIndex - FirstTextSourceIndex;
GlyphRun? lastRun = null; var currentDistance = Start;
for (var index = 0; index < _textRuns.Count; index++) if (flowDirection == FlowDirection.LeftToRight)
{
for (var index = 0; index < _textRuns.Count; index++)
{
var currentRun = _textRuns[index];
if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
flowDirection, out var distance, out _))
{
return currentDistance + distance;
}
//No hit hit found so we add the full width
currentDistance += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
else
{ {
var textRun = _textRuns[index]; currentDistance += WidthIncludingTrailingWhitespace;
switch (textRun) for (var index = _textRuns.Count - 1; index >= 0; index--)
{ {
case ShapedTextCharacters shapedTextCharacters: var currentRun = _textRuns[index];
{
var currentRun = shapedTextCharacters.GlyphRun;
if (lastRun != null) if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
{ flowDirection, out var distance, out var currentGlyphRun))
if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight && {
currentRun.Characters.Start == characterHit.FirstCharacterIndex && if (currentGlyphRun != null)
characterHit.TrailingLength == 0) {
{ distance = currentGlyphRun.Size.Width - distance;
return currentDistance; }
}
}
//Look for a hit in within the current run return currentDistance - distance;
if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length) }
{
characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
var distance = currentRun.GetDistanceFromCharacterHit(characterHit); //No hit hit found so we add the full width
currentDistance -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
return currentDistance + distance; return currentDistance;
} }
//Look at the left and right edge of the current run private static bool TryGetDistanceFromCharacterHit(
if (currentRun.IsLeftToRight) DrawableTextRun currentRun,
{ CharacterHit characterHit,
if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) int currentPosition,
{ int remainingLength,
if (characterIndex <= currentPosition) FlowDirection flowDirection,
{ out double distance,
return currentDistance; out GlyphRun? currentGlyphRun)
} {
} var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
else var isTrailingHit = characterHit.TrailingLength > 0;
{
if (characterIndex == currentPosition)
{
return currentDistance;
}
}
if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit) distance = 0;
{ currentGlyphRun = null;
return currentDistance + currentRun.Size.Width;
}
}
else
{
if (characterIndex == currentPosition)
{
return currentDistance + currentRun.Size.Width;
}
var nextRun = index + 1 < _textRuns.Count ? switch (currentRun)
_textRuns[index + 1] as ShapedTextCharacters : {
null; case ShapedTextCharacters shapedTextCharacters:
{
currentGlyphRun = shapedTextCharacters.GlyphRun;
if (nextRun != null) if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length)
{ {
if (nextRun.ShapedBuffer.IsLeftToRight) characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
{
if (characterIndex == currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
else
{
if (currentPosition + nextRun.Text.Length == characterIndex)
{
return currentDistance;
}
}
}
else
{
if (characterIndex > currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
}
lastRun = currentRun; distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit);
break; return true;
} }
default:
if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit)
{ {
if (characterIndex == currentPosition) if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
{ {
return currentDistance; distance = currentGlyphRun.Size.Width;
} }
if (characterIndex == currentPosition + textRun.TextSourceLength) return true;
{ }
return currentDistance + textRun.Size.Width;
}
break; break;
}
default:
{
if (characterIndex == currentPosition)
{
return true;
} }
}
//No hit hit found so we add the full width if (characterIndex == currentPosition + currentRun.TextSourceLength)
currentDistance += textRun.Size.Width; {
currentPosition += textRun.TextSourceLength; distance = currentRun.Size.Width;
remainingLength -= textRun.TextSourceLength;
if (remainingLength <= 0) return true;
{
break; }
}
break;
}
} }
return currentDistance; return false;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -460,20 +462,33 @@ namespace Avalonia.Media.TextFormatting
var startIndex = currentRun.Text.Start + offset; var startIndex = currentRun.Text.Start + offset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( double startOffset;
currentShapedRun.ShapedBuffer.IsLeftToRight ? double endOffset;
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
endX += 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));
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( if (currentPosition < startIndex)
currentShapedRun.ShapedBuffer.IsLeftToRight ? {
new CharacterHit(startIndex) : startOffset = endOffset;
new CharacterHit(startIndex + remainingLength)); }
else
{
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
}
startX += startOffset; startX += startOffset;
endX += endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -504,47 +519,40 @@ namespace Avalonia.Media.TextFormatting
} }
//Lines that only contain a linebreak need to be covered here //Lines that only contain a linebreak need to be covered here
if(characterLength == 0) if (characterLength == 0)
{ {
characterLength = NewLineLength; characterLength = NewLineLength;
} }
var runwidth = endX - startX; var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun); var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runwidth); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
} }
currentWidth += runwidth; currentWidth += runWidth;
currentPosition += characterLength; currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight) if (currentPosition > characterIndex)
{
if (currentPosition > characterIndex)
{
break;
}
}
else
{ {
if (currentPosition <= firstTextSourceIndex) break;
{
break;
}
} }
startX = endX; startX = endX;
@ -571,7 +579,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex; var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength; var remainingLength = textLength;
var startX = Start + WidthIncludingTrailingWhitespace; var startX = WidthIncludingTrailingWhitespace;
double currentWidth = 0; double currentWidth = 0;
var currentRect = Rect.Empty; var currentRect = Rect.Empty;
@ -582,7 +590,7 @@ namespace Avalonia.Media.TextFormatting
continue; continue;
} }
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex)
{ {
startX -= currentRun.Size.Width; startX -= currentRun.Size.Width;
@ -601,20 +609,31 @@ namespace Avalonia.Media.TextFormatting
currentPosition += offset; currentPosition += offset;
var startIndex = currentRun.Text.Start + offset; var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( if (currentShapedRun.ShapedBuffer.IsLeftToRight)
currentShapedRun.ShapedBuffer.IsLeftToRight ? {
new CharacterHit(startIndex + remainingLength) : if (currentPosition < startIndex)
new CharacterHit(startIndex)); {
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
endX += endOffset - currentShapedRun.Size.Width; startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
currentShapedRun.ShapedBuffer.IsLeftToRight ? }
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startX += startOffset - currentShapedRun.Size.Width; startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -652,41 +671,35 @@ namespace Avalonia.Media.TextFormatting
} }
var runWidth = endX - startX; var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{ {
currentRect = currentRect.WithWidth(currentWidth + runWidth); if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
var textBounds = result[result.Count - 1]; var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect; textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds); textBounds.TextRunBounds.Add(currentRunBounds);
} }
else else
{ {
currentRect = currentRunBounds.Rectangle; currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds })); result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
} }
}
currentWidth += runWidth; currentWidth += runWidth;
currentPosition += characterLength; currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight) if (currentPosition > characterIndex)
{
if (currentPosition > characterIndex)
{
break;
}
}
else
{ {
if (currentPosition <= firstTextSourceIndex) break;
{
break;
}
} }
lastDirection = currentDirection; lastDirection = currentDirection;
@ -698,6 +711,8 @@ namespace Avalonia.Media.TextFormatting
} }
} }
result.Reverse();
return result; return result;
} }
@ -1302,8 +1317,14 @@ namespace Avalonia.Media.TextFormatting
switch (textAlignment) switch (textAlignment)
{ {
case TextAlignment.Center: case TextAlignment.Center:
return Math.Max(0, (_paragraphWidth - width) / 2); var start = (_paragraphWidth - width) / 2;
if(paragraphFlowDirection == FlowDirection.RightToLeft)
{
start -= (widthIncludingTrailingWhitespace - width);
}
return Math.Max(0, start);
case TextAlignment.Right: case TextAlignment.Right:
return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);

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

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

85
src/Avalonia.Base/StyledElement.cs

@ -12,8 +12,6 @@ using Avalonia.Logging;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
#nullable enable
namespace Avalonia namespace Avalonia
{ {
/// <summary> /// <summary>
@ -55,7 +53,14 @@ namespace Avalonia
nameof(TemplatedParent), nameof(TemplatedParent),
o => o.TemplatedParent, o => o.TemplatedParent,
(o ,v) => o.TemplatedParent = v); (o ,v) => o.TemplatedParent = v);
/// <summary>
/// Defines the <see cref="Theme"/> property.
/// </summary>
public static readonly StyledProperty<ControlTheme?> ThemeProperty =
AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme));
private static readonly ControlTheme s_invalidTheme = new ControlTheme();
private int _initCount; private int _initCount;
private string? _name; private string? _name;
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
@ -67,6 +72,8 @@ namespace Avalonia
private List<IStyleInstance>? _appliedStyles; private List<IStyleInstance>? _appliedStyles;
private ITemplatedControl? _templatedParent; private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating; private bool _dataContextUpdating;
private bool _hasPromotedTheme;
private ControlTheme? _implicitTheme;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="StyledElement"/> class. /// Initializes static members of the <see cref="StyledElement"/> class.
@ -230,6 +237,15 @@ namespace Avalonia
internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
} }
/// <summary>
/// Gets or sets the theme to be applied to the element.
/// </summary>
public ControlTheme? Theme
{
get => GetValue(ThemeProperty);
set => SetValue(ThemeProperty, value);
}
/// <summary> /// <summary>
/// Gets the styled element's logical children. /// Gets the styled element's logical children.
/// </summary> /// </summary>
@ -302,6 +318,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent; IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent;
/// <inheritdoc/> /// <inheritdoc/>
public virtual void BeginInit() public virtual void BeginInit()
{ {
@ -341,10 +358,15 @@ namespace Avalonia
} }
finally finally
{ {
_styled = true;
EndBatchUpdate(); EndBatchUpdate();
} }
_styled = true; if (_hasPromotedTheme)
{
_hasPromotedTheme = false;
ClearValue(ThemeProperty);
}
} }
return _styled; return _styled;
@ -475,6 +497,31 @@ namespace Avalonia
}; };
} }
ControlTheme? IStyleable.GetEffectiveTheme()
{
var theme = Theme;
// Explitly set Theme property takes precedence.
if (theme is not null)
return theme;
// If the Theme property is not set, try to find a ControlTheme resource with our StyleKey.
if (_implicitTheme is null)
{
var key = ((IStyleable)this).StyleKey;
if (this.TryFindResource(key, out var value) && value is ControlTheme t)
_implicitTheme = t;
else
_implicitTheme = s_invalidTheme;
}
if (_implicitTheme != s_invalidTheme)
return _implicitTheme;
return null;
}
void IStyleable.StyleApplied(IStyleInstance instance) void IStyleable.StyleApplied(IStyleInstance instance)
{ {
instance = instance ?? throw new ArgumentNullException(nameof(instance)); instance = instance ?? throw new ArgumentNullException(nameof(instance));
@ -590,6 +637,30 @@ namespace Avalonia
{ {
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ThemeProperty)
{
// Changing the theme detaches all styles, meaning that if the theme property was
// set via a style, it will get cleared! To work around this, if the value was
// applied at less than local value priority then promote the value to local value
// priority until styling is re-applied.
if (change.Priority > BindingPriority.LocalValue)
{
Theme = change.GetNewValue<ControlTheme?>();
_hasPromotedTheme = true;
}
else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
{
_hasPromotedTheme = false;
}
InvalidateStyles();
}
}
private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
{ {
if (o is StyledElement element) if (o is StyledElement element)
@ -696,6 +767,7 @@ namespace Avalonia
if (_logicalRoot != null) if (_logicalRoot != null)
{ {
_logicalRoot = null; _logicalRoot = null;
_implicitTheme = null;
DetachStyles(); DetachStyles();
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e); DetachedFromLogicalTree?.Invoke(this, e);
@ -760,7 +832,7 @@ namespace Avalonia
private void DetachStyles() private void DetachStyles()
{ {
if (_appliedStyles is object) if (_appliedStyles?.Count > 0)
{ {
BeginBatchUpdate(); BeginBatchUpdate();
@ -801,9 +873,12 @@ namespace Avalonia
if (applied.Source == styles[i]) if (applied.Source == styles[i])
{ {
applied.Dispose();
_appliedStyles.RemoveAt(j); _appliedStyles.RemoveAt(j);
applied.Dispose();
} }
if (j > _appliedStyles.Count)
j = _appliedStyles.Count;
} }
} }
} }

2
src/Avalonia.Base/Styling/ChildSelector.cs

@ -65,6 +65,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

67
src/Avalonia.Base/Styling/ControlTheme.cs

@ -0,0 +1,67 @@
using System;
namespace Avalonia.Styling
{
/// <summary>
/// Defines a switchable theme for a control.
/// </summary>
public class ControlTheme : StyleBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ControlTheme"/> class.
/// </summary>
public ControlTheme() { }
/// <summary>
/// Initializes a new instance of the <see cref="ControlTheme"/> class.
/// </summary>
/// <param name="targetType">The value for <see cref="TargetType"/>.</param>
public ControlTheme(Type targetType) => TargetType = targetType;
/// <summary>
/// Gets or sets the type for which this control theme is intended.
/// </summary>
public Type? TargetType { get; set; }
/// <summary>
/// Gets or sets a control theme that is the basis of the current theme.
/// </summary>
public ControlTheme? BasedOn { get; set; }
public override SelectorMatchResult TryAttach(IStyleable target, object? host)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType;
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey))
{
Attach(target, null);
result = SelectorMatchResult.AlwaysThisType;
}
var childResult = TryAttachChildren(target, host);
if (childResult > result)
result = childResult;
return result;
}
public override string ToString()
{
if (TargetType is not null)
return "ControlTheme: " + TargetType.Name;
else
return "ControlTheme";
}
internal override void SetParent(StyleBase? parent)
{
throw new InvalidOperationException("ControlThemes cannot be added as a nested style.");
}
}
}

2
src/Avalonia.Base/Styling/DescendentSelector.cs

@ -70,6 +70,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

2
src/Avalonia.Base/Styling/IStyle.cs

@ -23,6 +23,6 @@ namespace Avalonia.Styling
/// <returns> /// <returns>
/// A <see cref="SelectorMatchResult"/> describing how the style matches the control. /// A <see cref="SelectorMatchResult"/> describing how the style matches the control.
/// </returns> /// </returns>
SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host); SelectorMatchResult TryAttach(IStyleable target, object? host);
} }
} }

7
src/Avalonia.Base/Styling/IStyleable.cs

@ -3,8 +3,6 @@ using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
/// <summary> /// <summary>
@ -28,6 +26,11 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
ITemplatedControl? TemplatedParent { get; } ITemplatedControl? TemplatedParent { get; }
/// <summary>
/// Gets the effective theme for the control as used by the syling system.
/// </summary>
ControlTheme? GetEffectiveTheme();
/// <summary> /// <summary>
/// Notifies the element that a style has been applied. /// Notifies the element that a style has been applied.
/// </summary> /// </summary>

14
src/Avalonia.Base/Styling/NestingSelector.cs

@ -15,9 +15,17 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{ {
if (parent is Style s && s.Selector is Selector selector) if (parent is Style s && s.Selector is not null)
{ {
return selector.Match(control, (parent as Style)?.Parent, subscribe); return s.Selector.Match(control, s.Parent, subscribe);
}
else if (parent is ControlTheme theme)
{
if (theme.TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
return theme.TargetType.IsAssignableFrom(control.StyleKey) ?
SelectorMatch.AlwaysThisType :
SelectorMatch.NeverThisType;
} }
throw new InvalidOperationException( throw new InvalidOperationException(
@ -25,6 +33,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => true; protected override Selector? MovePreviousOrParent() => null;
} }
} }

2
src/Avalonia.Base/Styling/NotSelector.cs

@ -67,6 +67,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _argument.HasValidNestingSelector(); protected override Selector? MovePreviousOrParent() => _previous;
} }
} }

2
src/Avalonia.Base/Styling/NthChildSelector.cs

@ -105,7 +105,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; protected override Selector? MovePreviousOrParent() => _previous;
public override string ToString() public override string ToString()
{ {

12
src/Avalonia.Base/Styling/OrSelector.cs

@ -103,18 +103,12 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null;
internal override bool HasValidNestingSelector() internal override void ValidateNestingSelector(bool inControlTheme)
{ {
foreach (var selector in _selectors) foreach (var selector in _selectors)
{ selector.ValidateNestingSelector(inControlTheme);
if (!selector.HasValidNestingSelector())
{
return false;
}
}
return true;
} }
private Type? EvaluateTargetType() private Type? EvaluateTargetType()

2
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -90,7 +90,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; protected override Selector? MovePreviousOrParent() => _previous;
internal static bool Compare(Type propertyType, object? propertyValue, object? value) internal static bool Compare(Type propertyType, object? propertyValue, object? value)
{ {

31
src/Avalonia.Base/Styling/Selector.cs

@ -86,7 +86,36 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
protected abstract Selector? MovePrevious(); protected abstract Selector? MovePrevious();
internal abstract bool HasValidNestingSelector(); /// <summary>
/// Moves to the previous selector or the parent selector.
/// </summary>
protected abstract Selector? MovePreviousOrParent();
internal virtual void ValidateNestingSelector(bool inControlTheme)
{
var s = this;
var templateCount = 0;
do
{
if (inControlTheme)
{
if (!s.InTemplate && s.IsCombinator)
throw new InvalidOperationException(
"ControlTheme style may not directly contain a child or descendent selector.");
if (s is TemplateSelector && templateCount++ > 0)
throw new InvalidOperationException(
"ControlTemplate styles cannot contain multiple template selectors.");
}
var previous = s.MovePreviousOrParent();
if (previous is null && s is not NestingSelector)
throw new InvalidOperationException("Child styles must have a nesting selector.");
s = previous;
} while (s is not null);
}
private static SelectorMatch MatchUntilCombinator( private static SelectorMatch MatchUntilCombinator(
IStyleable control, IStyleable control,

166
src/Avalonia.Base/Styling/Style.cs

@ -1,22 +1,13 @@
using System; using System;
using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Metadata;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
/// <summary> /// <summary>
/// Defines a style. /// Defines a style.
/// </summary> /// </summary>
public class Style : AvaloniaObject, IStyle, IResourceProvider public class Style : StyleBase
{ {
private IResourceHost? _owner; private Selector? _selector;
private StyleChildren? _children;
private IResourceDictionary? _resources;
private List<ISetter>? _setters;
private List<IAnimation>? _animations;
private StyleCache? _childCache;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Style"/> class. /// Initializes a new instance of the <see cref="Style"/> class.
@ -35,113 +26,41 @@ namespace Avalonia.Styling
} }
/// <summary> /// <summary>
/// Gets the children of the style. /// Gets or sets the style's selector.
/// </summary>
public IList<IStyle> Children => _children ??= new(this);
/// <summary>
/// Gets the <see cref="StyledElement"/> or Application that hosts the style.
/// </summary> /// </summary>
public IResourceHost? Owner public Selector? Selector
{ {
get => _owner; get => _selector;
private set set => _selector = ValidateSelector(value);
{
if (_owner != value)
{
_owner = value;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
}
} }
/// <summary> public override SelectorMatchResult TryAttach(IStyleable target, object? host)
/// Gets the parent style if this style is hosted in a <see cref="Style.Children"/> collection.
/// </summary>
public Style? Parent { get; private set; }
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
public IResourceDictionary Resources
{ {
get => _resources ?? (Resources = new ResourceDictionary()); _ = target ?? throw new ArgumentNullException(nameof(target));
set
{
value = value ?? throw new ArgumentNullException(nameof(value));
var hadResources = _resources?.HasResources ?? false;
_resources = value;
if (Owner is object)
{
_resources.AddOwner(Owner);
if (hadResources || _resources.HasResources)
{
Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
}
}
/// <summary>
/// Gets or sets the style's selector.
/// </summary>
public Selector? Selector { get; set; }
/// <summary>
/// Gets the style's setters.
/// </summary>
public IList<ISetter> Setters => _setters ??= new List<ISetter>();
/// <summary>
/// Gets the style's animations.
/// </summary>
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => (IReadOnlyList<IStyle>?)_children ?? Array.Empty<IStyle>();
public event EventHandler? OwnerChanged; var result = SelectorMatchResult.NeverThisType;
public void Add(ISetter setter) => Setters.Add(setter); if (HasSettersOrAnimations)
public void Add(IStyle style) => Children.Add(style); {
var match = Selector?.Match(target, Parent, true) ??
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) (target == host ?
{ SelectorMatch.AlwaysThisInstance :
target = target ?? throw new ArgumentNullException(nameof(target)); SelectorMatch.NeverThisInstance);
var match = Selector is object ? Selector.Match(target, Parent) : if (match.IsMatch)
target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; Attach(target, match.Activator);
if (match.IsMatch && (_setters is object || _animations is object)) result = match.Result;
{
var instance = new StyleInstance(this, target, _setters, _animations, match.Activator);
target.StyleApplied(instance);
instance.Start();
} }
var result = match.Result; var childResult = TryAttachChildren(target, host);
if (_children is not null) if (childResult > result)
{ result = childResult;
_childCache ??= new StyleCache();
var childResult = _childCache.TryAttach(_children, target, host);
if (childResult > result)
result = childResult;
}
return result; return result;
} }
public bool TryGetResource(object key, out object? result)
{
result = null;
return _resources?.TryGetResource(key, out result) ?? false;
}
/// <summary> /// <summary>
/// Returns a string representation of the style. /// Returns a string representation of the style.
/// </summary> /// </summary>
@ -158,41 +77,30 @@ namespace Avalonia.Styling
} }
} }
void IResourceProvider.AddOwner(IResourceHost owner) internal override void SetParent(StyleBase? parent)
{ {
owner = owner ?? throw new ArgumentNullException(nameof(owner)); if (parent is Style parentStyle && parentStyle.Selector is not null)
if (Owner != null)
{ {
throw new InvalidOperationException("The Style already has a parent."); if (Selector is null)
} throw new InvalidOperationException("Child styles must have a selector.");
Selector.ValidateNestingSelector(false);
Owner = owner;
_resources?.AddOwner(owner);
}
void IResourceProvider.RemoveOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner == owner)
{
Owner = null;
_resources?.RemoveOwner(owner);
} }
} else if (parent is ControlTheme)
internal void SetParent(Style? parent)
{
if (parent?.Selector is not null)
{ {
if (Selector is null) if (Selector is null)
throw new InvalidOperationException("Child styles must have a selector."); throw new InvalidOperationException("Child styles must have a selector.");
if (!Selector.HasValidNestingSelector()) Selector.ValidateNestingSelector(true);
throw new InvalidOperationException("Child styles must have a nesting selector.");
} }
Parent = parent; base.SetParent(parent);
}
private static Selector? ValidateSelector(Selector? selector)
{
if (selector is TemplateSelector)
throw new InvalidOperationException(
"Invalid selector: Template selector must be followed by control selector.");
return selector;
} }
} }
} }

124
src/Avalonia.Base/Styling/StyleBase.cs

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
/// <summary>
/// Base class for <see cref="Style"/> and <see cref="ControlTheme"/>.
/// </summary>
public abstract class StyleBase : AvaloniaObject, IStyle, IResourceProvider
{
private IResourceHost? _owner;
private StyleChildren? _children;
private IResourceDictionary? _resources;
private List<ISetter>? _setters;
private List<IAnimation>? _animations;
private StyleCache? _childCache;
public IList<IStyle> Children => _children ??= new(this);
public IResourceHost? Owner
{
get => _owner;
private set
{
if (_owner != value)
{
_owner = value;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public IStyle? Parent { get; private set; }
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
value = value ?? throw new ArgumentNullException(nameof(value));
var hadResources = _resources?.HasResources ?? false;
_resources = value;
if (Owner is object)
{
_resources.AddOwner(Owner);
if (hadResources || _resources.HasResources)
{
Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
}
}
public IList<ISetter> Setters => _setters ??= new List<ISetter>();
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => (IReadOnlyList<IStyle>?)_children ?? Array.Empty<IStyle>();
internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
public void Add(ISetter setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged;
public abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
public bool TryGetResource(object key, out object? result)
{
result = null;
return _resources?.TryGetResource(key, out result) ?? false;
}
internal void Attach(IStyleable target, IStyleActivator? activator)
{
var instance = new StyleInstance(this, target, _setters, _animations, activator);
target.StyleApplied(instance);
instance.Start();
}
internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host)
{
if (_children is null || _children.Count == 0)
return SelectorMatchResult.NeverThisType;
_childCache ??= new StyleCache();
return _childCache.TryAttach(_children, target, host);
}
internal virtual void SetParent(StyleBase? parent) => Parent = parent;
void IResourceProvider.AddOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner != null)
{
throw new InvalidOperationException("The Style already has a parent.");
}
Owner = owner;
_resources?.AddOwner(owner);
}
void IResourceProvider.RemoveOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner == owner)
{
Owner = null;
_resources?.RemoveOwner(owner);
}
}
}
}

2
src/Avalonia.Base/Styling/StyleCache.cs

@ -12,7 +12,7 @@ namespace Avalonia.Styling
/// </remarks> /// </remarks>
internal class StyleCache : Dictionary<Type, List<IStyle>?> internal class StyleCache : Dictionary<Type, List<IStyle>?>
{ {
public SelectorMatchResult TryAttach(IList<IStyle> styles, IStyleable target, IStyleHost? host) public SelectorMatchResult TryAttach(IList<IStyle> styles, IStyleable target, object? host)
{ {
if (TryGetValue(target.StyleKey, out var cached)) if (TryGetValue(target.StyleKey, out var cached))
{ {

10
src/Avalonia.Base/Styling/StyleChildren.cs

@ -5,20 +5,20 @@ namespace Avalonia.Styling
{ {
internal class StyleChildren : Collection<IStyle> internal class StyleChildren : Collection<IStyle>
{ {
private readonly Style _owner; private readonly StyleBase _owner;
public StyleChildren(Style owner) => _owner = owner; public StyleChildren(StyleBase owner) => _owner = owner;
protected override void InsertItem(int index, IStyle item) protected override void InsertItem(int index, IStyle item)
{ {
(item as Style)?.SetParent(_owner); (item as StyleBase)?.SetParent(_owner);
base.InsertItem(index, item); base.InsertItem(index, item);
} }
protected override void RemoveItem(int index) protected override void RemoveItem(int index)
{ {
var item = Items[index]; var item = Items[index];
(item as Style)?.SetParent(null); (item as StyleBase)?.SetParent(null);
if (_owner.Owner is IResourceHost host) if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.RemoveOwner(host); (item as IResourceProvider)?.RemoveOwner(host);
base.RemoveItem(index); base.RemoveItem(index);
@ -26,7 +26,7 @@ namespace Avalonia.Styling
protected override void SetItem(int index, IStyle item) protected override void SetItem(int index, IStyle item)
{ {
(item as Style)?.SetParent(_owner); (item as StyleBase)?.SetParent(_owner);
base.SetItem(index, item); base.SetItem(index, item);
if (_owner.Owner is IResourceHost host) if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.AddOwner(host); (item as IResourceProvider)?.AddOwner(host);

19
src/Avalonia.Base/Styling/Styler.cs

@ -1,19 +1,24 @@
using System; using System;
#nullable enable
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
public class Styler : IStyler public class Styler : IStyler
{ {
public void ApplyStyles(IStyleable target) public void ApplyStyles(IStyleable target)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); _ = target ?? throw new ArgumentNullException(nameof(target));
// Apply the control theme.
target.GetEffectiveTheme()?.TryAttach(target, target);
// If the control has a themed templated parent then apply the styles from the
// templated parent theme.
if (target.TemplatedParent is IStyleable styleableParent)
styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent);
// Apply styles from the rest of the tree.
if (target is IStyleHost styleHost) if (target is IStyleHost styleHost)
{
ApplyStyles(target, styleHost); ApplyStyles(target, styleHost);
}
} }
private void ApplyStyles(IStyleable target, IStyleHost host) private void ApplyStyles(IStyleable target, IStyleHost host)
@ -21,14 +26,10 @@ namespace Avalonia.Styling
var parent = host.StylingParent; var parent = host.StylingParent;
if (parent != null) if (parent != null)
{
ApplyStyles(target, parent); ApplyStyles(target, parent);
}
if (host.IsStylesInitialized) if (host.IsStylesInitialized)
{
host.Styles.TryAttach(target, host); host.Styles.TryAttach(target, host);
}
} }
} }
} }

7
src/Avalonia.Base/Styling/Styles.cs

@ -26,6 +26,11 @@ namespace Avalonia.Styling
{ {
_styles.ResetBehavior = ResetBehavior.Remove; _styles.ResetBehavior = ResetBehavior.Remove;
_styles.CollectionChanged += OnCollectionChanged; _styles.CollectionChanged += OnCollectionChanged;
_styles.Validate = i =>
{
if (i is ControlTheme)
throw new InvalidOperationException("ControlThemes cannot be added to a Styles collection.");
};
} }
public Styles(IResourceHost owner) public Styles(IResourceHost owner)
@ -111,7 +116,7 @@ namespace Avalonia.Styling
set => _styles[index] = value; set => _styles[index] = value;
} }
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) public SelectorMatchResult TryAttach(IStyleable target, object? host)
{ {
_cache ??= new StyleCache(); _cache ??= new StyleCache();
return _cache.TryAttach(this, target, host); return _cache.TryAttach(this, target, host);

2
src/Avalonia.Base/Styling/TemplateSelector.cs

@ -49,6 +49,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false; protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

2
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -141,7 +141,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; protected override Selector? MovePreviousOrParent() => _previous;
private string BuildSelectorString() private string BuildSelectorString()
{ {

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -95,6 +95,9 @@
<Compile Include="../Avalonia.Controls/GridLength.cs"> <Compile Include="../Avalonia.Controls/GridLength.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="../Avalonia.Base/Data/BindingPriority.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/RelativePoint.cs"> <Compile Include="../Avalonia.Base/RelativePoint.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>

3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -251,7 +251,8 @@ namespace Avalonia.Build.Tasks
var populateBuilder = classTypeDefinition == null ? var populateBuilder = classTypeDefinition == null ?
builder : builder :
typeSystem.CreateTypeBuilder(classTypeDefinition); typeSystem.CreateTypeBuilder(classTypeDefinition);
compiler.Compile(parsed, contextClass, compiler.Compile(parsed,
contextClass,
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
classTypeDefinition == null), classTypeDefinition == null),
buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true), buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true),

4
src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml

@ -37,4 +37,8 @@
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml" /> <StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml" /> <StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml" />
<!-- Controls -->
<!-- Note the ColorPicker and ColorView are unsupported in the default theme -->
<!-- These controls depend on fluent styles for TabControl, Button, TextBox, etc. -->
</Styles> </Styles>

25
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -1,14 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls" xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources> <!-- This must follow OverlayCornerRadius -->
<!-- This must follow OverlayCornerRadius --> <CornerRadius x:Key="TopOverlayCornerRadius">5,5,0,0</CornerRadius>
<CornerRadius x:Key="TopOverlayCornerRadius">5,5,0,0</CornerRadius>
</Styles.Resources>
<Style Selector="ColorPicker"> <ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
<Setter Property="Width" Value="64" /> <Setter Property="Width" Value="64" />
@ -80,12 +79,12 @@
</DropDownButton> </DropDownButton>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </ControlTheme>
<!-- Adjust Background within Flyout --> <!-- Adjust Background within Flyout -->
<!-- Note: This is implemented but there seems to be an issue and the selector can't match across the Flyout --> <!-- Note: This is implemented but there seems to be an issue and the selector can't match across the Flyout -->
<Style Selector="ColorPicker /template/ ColorView#FlyoutColorView /template/ Border#TabBackgroundBorder"> <!--<Style Selector="ColorPicker /template/ ColorView#FlyoutColorView /template/ Border#TabBackgroundBorder">
<Setter Property="CornerRadius" Value="{DynamicResource TopOverlayCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource TopOverlayCornerRadius}" />
</Style> </Style>-->
</Styles> </ResourceDictionary>

23
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -1,15 +1,14 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters" xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources> <pc:AccentColorConverter x:Key="AccentColorConverter" />
<pc:AccentColorConverter x:Key="AccentColorConverter" /> <x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double>
<x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double> <x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
<x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
</Styles.Resources>
<Style Selector="ColorPreviewer"> <ControlTheme x:Key="{x:Type ColorPreviewer}"
TargetType="ColorPreviewer">
<Setter Property="Height" Value="70" /> <Setter Property="Height" Value="70" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template"> <Setter Property="Template">
@ -97,6 +96,6 @@
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </ControlTheme>
</Styles> </ResourceDictionary>

346
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@ -1,188 +1,190 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True"> x:CompileBindings="True">
<Style Selector="Thumb.ColorSliderThumbStyle"> <ControlTheme x:Key="ColorSliderThumbTheme"
<Setter Property="BorderThickness" Value="0" /> TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10" /> CornerRadius="{TemplateBinding CornerRadius}" />
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </ControlTheme>
<Style Selector="ColorSlider:horizontal"> <ControlTheme x:Key="{x:Type ColorSlider}"
<Setter Property="BorderThickness" Value="0" /> TargetType="ColorSlider">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Horizontal">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Classes="ColorSliderThumbStyle"
Name="ColorSliderThumb"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Height}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ColorSlider:vertical"> <Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" /> <Setter Property="Height" Value="20" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}"> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}" <Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}"> <Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}" Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}" Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track" <Track Name="PART_Track"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}" Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}" Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}" Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}" IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Vertical"> Orientation="Horizontal">
<Track.DecreaseButton> <Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton" <RepeatButton Name="PART_DecreaseButton"
Background="Transparent" Background="Transparent"
Focusable="False" Focusable="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<RepeatButton.Template> <RepeatButton.Template>
<ControlTemplate> <ControlTemplate>
<Border Name="FocusTarget" <Border Name="FocusTarget"
Background="Transparent" Background="Transparent"
Margin="0,-10" /> Margin="0,-10" />
</ControlTemplate> </ControlTemplate>
</RepeatButton.Template> </RepeatButton.Template>
</RepeatButton> </RepeatButton>
</Track.DecreaseButton> </Track.DecreaseButton>
<Track.IncreaseButton> <Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton" <RepeatButton Name="PART_IncreaseButton"
Background="Transparent" Background="Transparent"
Focusable="False" Focusable="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<RepeatButton.Template> <RepeatButton.Template>
<ControlTemplate> <ControlTemplate>
<Border Name="FocusTarget" <Border Name="FocusTarget"
Background="Transparent" Background="Transparent"
Margin="0,-10" /> Margin="0,-10" />
</ControlTemplate> </ControlTemplate>
</RepeatButton.Template> </RepeatButton.Template>
</RepeatButton> </RepeatButton>
</Track.IncreaseButton> </Track.IncreaseButton>
<Thumb Classes="ColorSliderThumbStyle" <Thumb Name="ColorSliderThumb"
Name="ColorSliderThumb" Theme="{StaticResource ColorSliderThumbTheme}"
Margin="0" Margin="0"
Padding="0" Padding="0"
DataContext="{TemplateBinding Value}" DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Width}" Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}" /> Width="{TemplateBinding Height}" />
</Track> </Track>
</Grid> </Grid>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<!-- Normal State --> <Style Selector="^:vertical">
<Style Selector="ColorSlider /template/ Thumb.ColorSliderThumbStyle"> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" /> <Setter Property="Width" Value="20" />
<Setter Property="BorderThickness" Value="3" /> <Setter Property="Template">
</Style> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Vertical">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Width}"
Width="{TemplateBinding Width}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Selector/Thumb Color -->
<Style Selector="^:pointerover /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.75" />
</Style>
<Style Selector="^:pointerover:dark-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="^:pointerover:light-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.8" />
</Style>
<!-- Selector/Thumb Color --> <Style Selector="^:dark-selector /template/ Thumb#ColorSliderThumb">
<Style Selector="ColorSlider:pointerover /template/ Thumb.ColorSliderThumbStyle"> <Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
<Setter Property="Opacity" Value="0.75" /> </Style>
</Style> <Style Selector="^:light-selector /template/ Thumb#ColorSliderThumb">
<Style Selector="ColorSlider:pointerover:dark-selector /template/ Thumb.ColorSliderThumbStyle"> <Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
<Setter Property="Opacity" Value="0.7" /> </Style>
</Style>
<Style Selector="ColorSlider:pointerover:light-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="Opacity" Value="0.8" />
</Style>
<Style Selector="ColorSlider:dark-selector /template/ Thumb.ColorSliderThumbStyle"> </ControlTheme>
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="ColorSlider:light-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
</Styles> </ResourceDictionary>

100
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml

@ -1,9 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls" xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True"> x:CompileBindings="True">
<Style Selector="ColorSpectrum"> <ControlTheme x:Key="{x:Type ColorSpectrum}"
TargetType="ColorSpectrum">
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type ColorSpectrum}"> <ControlTemplate TargetType="{x:Type ColorSpectrum}">
@ -79,53 +80,54 @@
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style>
<!-- Normal --> <!-- Normal -->
<!-- Separating this allows easier customization in applications --> <!-- Separating this allows easier customization in applications -->
<Style Selector="ColorSpectrum /template/ Ellipse#BorderEllipse, <Style Selector="^ /template/ Ellipse#BorderEllipse,
ColorSpectrum /template/ Rectangle#BorderRectangle"> ^ /template/ Rectangle#BorderRectangle">
<Setter Property="Stroke" Value="{DynamicResource SystemControlForegroundListLowBrush}" /> <Setter Property="Stroke" Value="{DynamicResource SystemControlForegroundListLowBrush}" />
<Setter Property="StrokeThickness" Value="1" /> <Setter Property="StrokeThickness" Value="1" />
</Style> </Style>
<!-- Focus --> <!-- Focus -->
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> <Style Selector="^ /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="False" /> <Setter Property="IsVisible" Value="False" />
</Style> </Style>
<Style Selector="ColorSpectrum:focus-visible /template/ Ellipse#FocusEllipse"> <Style Selector="^:focus-visible /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="True" /> <Setter Property="IsVisible" Value="True" />
</Style> </Style>
<!-- Selector Color --> <!-- Selector Color -->
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> <Style Selector="^ /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" /> <Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style> </Style>
<Style Selector="ColorSpectrum /template/ Ellipse#SelectionEllipse"> <Style Selector="^ /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" /> <Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style> </Style>
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#FocusEllipse"> <Style Selector="^:light-selector /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" /> <Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style> </Style>
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#SelectionEllipse"> <Style Selector="^:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" /> <Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style> </Style>
<Style Selector="ColorSpectrum:pointerover /template/ Ellipse#SelectionEllipse"> <Style Selector="^:pointerover /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.7" /> <Setter Property="Opacity" Value="0.7" />
</Style> </Style>
<Style Selector="ColorSpectrum:pointerover:light-selector /template/ Ellipse#SelectionEllipse"> <Style Selector="^:pointerover:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.8" /> <Setter Property="Opacity" Value="0.8" />
</Style> </Style>
<!-- Selector Size --> <!-- Selector Size -->
<Style Selector="ColorSpectrum /template/ Panel#PART_SelectionEllipsePanel"> <Style Selector="^ /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="16" /> <Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" /> <Setter Property="Height" Value="16" />
</Style> </Style>
<Style Selector="ColorSpectrum:large-selector /template/ Panel#PART_SelectionEllipsePanel"> <Style Selector="^:large-selector /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="48" /> <Setter Property="Width" Value="48" />
<Setter Property="Height" Value="48" /> <Setter Property="Height" Value="48" />
</Style> </Style>
</Styles> </ControlTheme>
</ResourceDictionary>

165
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -1,86 +1,85 @@
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls" xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters" xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives" xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker" xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib" xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources> <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" /> <pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
<pc:ThirdComponentConverter x:Key="ThirdComponentConverter" /> <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" /> <converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" /> <globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" /> <x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double> <x:Double x:Key="ColorViewComponentLabelWidth">30</x:Double>
<x:Double x:Key="ColorViewComponentLabelWidth">30</x:Double> <x:Double x:Key="ColorViewComponentTextInputWidth">80</x:Double>
<x:Double x:Key="ColorViewComponentTextInputWidth">80</x:Double> <!-- Fluent UI System Icons : ic_fluent_inking_tool_20_regular.svg -->
<!-- Fluent UI System Icons : ic_fluent_inking_tool_20_regular.svg --> <PathGeometry x:Key="ColorViewSpectrumIconGeometry">
<PathGeometry x:Key="ColorViewSpectrumIconGeometry"> M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614
M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614 16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843
16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843 16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695
16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695 14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268
14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268 17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222
17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222 16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431
16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431 7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386
7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386 2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691
2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691 7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549
7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549 9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17
9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17 10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5
10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5 15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z
15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z </PathGeometry>
</PathGeometry> <!-- Fluent UI System Icons : ic_fluent_color_20_regular.svg -->
<!-- Fluent UI System Icons : ic_fluent_color_20_regular.svg --> <PathGeometry x:Key="ColorViewPaletteIconGeometry">
<PathGeometry x:Key="ColorViewPaletteIconGeometry"> M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582
M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582 5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642
5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642 7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12
7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12 7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75
7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75 13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579
13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579 15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5
15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5 11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25
11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25 14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75
14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75 13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972
13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972 2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925
2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925 2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983
2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983 11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404
11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404 5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332
5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332 11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579
11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579 11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774
11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774 8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379
8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379 13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744
13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744 8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093
8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093 18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842
18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842 13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426
13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426 10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624
10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624 14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305
14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305 16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556
16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556 9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642
9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642 13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541
13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541 9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648
9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648 10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179
10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179 11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39
11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39 4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473
4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473 10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272
10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272 5.28471 4.63649 6.29249 4.01905Z
5.28471 4.63649 6.29249 4.01905Z </PathGeometry>
</PathGeometry> <!-- Fluent UI System Icons : ic_fluent_options_20_regular.svg -->
<!-- Fluent UI System Icons : ic_fluent_options_20_regular.svg --> <PathGeometry x:Key="ColorViewComponentsIconGeometry">
<PathGeometry x:Key="ColorViewComponentsIconGeometry"> M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386
M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386 5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297
5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297 8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18
8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18 5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11
5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11 4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999
4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999 14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386
14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5
14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5 17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239
17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5
17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z
13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z </PathGeometry>
</PathGeometry>
</Styles.Resources>
<Style Selector="ColorView"> <ControlTheme x:Key="{x:Type ColorView}"
TargetType="ColorView">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Palette"> <Setter Property="Palette">
<controls:FluentColorPalette /> <controls:FluentColorPalette />
@ -645,6 +644,6 @@
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </ControlTheme>
</Styles> </ResourceDictionary>

76
src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml

@ -3,42 +3,48 @@
xmlns:converters="using:Avalonia.Controls.Converters"> xmlns:converters="using:Avalonia.Controls.Converters">
<Styles.Resources> <Styles.Resources>
<!-- Shared Resources --> <ResourceDictionary>
<VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
TileMode="Tile" <!-- Shared Resources -->
Stretch="Uniform" <VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
DestinationRect="0,0,8,8"> TileMode="Tile"
<VisualBrush.Visual> Stretch="Uniform"
<DrawingPresenter Width="8" DestinationRect="0,0,8,8">
Height="8"> <VisualBrush.Visual>
<DrawingGroup> <DrawingPresenter Width="8"
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z" Height="8">
Brush="Transparent" /> <DrawingGroup>
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z" <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
Brush="#19808080" /> Brush="Transparent" />
</DrawingGroup> <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
</DrawingPresenter> Brush="#19808080" />
</VisualBrush.Visual> </DrawingGroup>
</VisualBrush> </DrawingPresenter>
</VisualBrush.Visual>
<!-- Shared Converters --> </VisualBrush>
<converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:ToBrushConverter x:Key="ToBrushConverter" /> <!-- Shared Converters -->
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/> <converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/> <converters:ToBrushConverter x:Key="ToBrushConverter" />
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/> <converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/> <converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" /> <converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" /> <converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
</Styles.Resources> <converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" />
<!-- Primitives --> <ResourceDictionary.MergedDictionaries>
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
<!-- Controls --> <!-- Primitives -->
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml" /> <ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" /> <ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" />
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
<!-- Controls -->
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml" />
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>
</Styles> </Styles>

27
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -8,10 +8,10 @@ namespace Avalonia.Controls.Chrome
/// <summary> /// <summary>
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled. /// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
/// </summary> /// </summary>
[TemplatePart("PART_CloseButton", typeof(Panel))] [TemplatePart("PART_CloseButton", typeof(Button))]
[TemplatePart("PART_RestoreButton", typeof(Panel))] [TemplatePart("PART_RestoreButton", typeof(Button))]
[TemplatePart("PART_MinimiseButton", typeof(Panel))] [TemplatePart("PART_MinimiseButton", typeof(Button))]
[TemplatePart("PART_FullScreenButton", typeof(Panel))] [TemplatePart("PART_FullScreenButton", typeof(Button))]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class CaptionButtons : TemplatedControl public class CaptionButtons : TemplatedControl
{ {
@ -88,18 +88,15 @@ namespace Avalonia.Controls.Chrome
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton"); var closeButton = e.NameScope.Get<Button>("PART_CloseButton");
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton"); var restoreButton = e.NameScope.Get<Button>("PART_RestoreButton");
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); var minimiseButton = e.NameScope.Get<Button>("PART_MinimiseButton");
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); var fullScreenButton = e.NameScope.Get<Button>("PART_FullScreenButton");
closeButton.PointerReleased += (sender, e) => OnClose(); closeButton.Click += (sender, e) => OnClose();
restoreButton.Click += (sender, e) => OnRestore();
restoreButton.PointerReleased += (sender, e) => OnRestore(); minimiseButton.Click += (sender, e) => OnMinimize();
fullScreenButton.Click += (sender, e) => OnToggleFullScreen();
minimiseButton.PointerReleased += (sender, e) => OnMinimize();
fullScreenButton.PointerReleased += (sender, e) => OnToggleFullScreen();
} }
} }
} }

8
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -44,13 +44,13 @@ namespace Avalonia.Controls.Chrome
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_captionButtons?.Detach(); _captionButtons?.Detach();
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); _captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
if (VisualRoot is Window window) if (VisualRoot is Window window)
{ {
_captionButtons?.Attach(window); _captionButtons?.Attach(window);
UpdateSize(window); UpdateSize(window);
} }
} }
@ -90,7 +90,7 @@ namespace Avalonia.Controls.Chrome
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
_disposables?.Dispose(); _disposables?.Dispose();
_captionButtons?.Detach(); _captionButtons?.Detach();
_captionButtons = null; _captionButtons = null;
} }

2
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -454,7 +454,7 @@ namespace Avalonia.Controls.Primitives
children.Add(new ListBoxItem children.Add(new ListBoxItem
{ {
Height = ItemHeight, Height = ItemHeight,
Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"), Classes = new Classes($"{PanelType}Item"),
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center,
Focusable = false Focusable = false
}); });

22
src/Avalonia.Controls/Design.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Styling; using Avalonia.Styling;
@ -6,6 +7,8 @@ namespace Avalonia.Controls
{ {
public static class Design public static class Design
{ {
private static Dictionary<object, Control?>? _previewWith;
public static bool IsDesignMode { get; internal set; } public static bool IsDesignMode { get; internal set; }
public static readonly AttachedProperty<double> HeightProperty = AvaloniaProperty public static readonly AttachedProperty<double> HeightProperty = AvaloniaProperty
@ -47,19 +50,30 @@ namespace Avalonia.Controls
return control.GetValue(DataContextProperty); return control.GetValue(DataContextProperty);
} }
public static readonly AttachedProperty<Control> PreviewWithProperty = AvaloniaProperty public static readonly AttachedProperty<Control?> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control>("PreviewWith", typeof (Design)); .RegisterAttached<AvaloniaObject, Control?>("PreviewWith", typeof (Design));
public static void SetPreviewWith(AvaloniaObject target, Control control) public static void SetPreviewWith(AvaloniaObject target, Control? control)
{ {
target.SetValue(PreviewWithProperty, control); target.SetValue(PreviewWithProperty, control);
} }
public static Control GetPreviewWith(AvaloniaObject target) public static void SetPreviewWith(ResourceDictionary target, Control? control)
{
_previewWith ??= new();
_previewWith[target] = control;
}
public static Control? GetPreviewWith(AvaloniaObject target)
{ {
return target.GetValue(PreviewWithProperty); return target.GetValue(PreviewWithProperty);
} }
public static Control? GetPreviewWith(ResourceDictionary target)
{
return _previewWith?[target];
}
public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty
.RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design)); .RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design));

34
src/Avalonia.Controls/Documents/Inline.cs

@ -50,8 +50,38 @@ namespace Avalonia.Controls.Documents
protected TextRunProperties CreateTextRunProperties() protected TextRunProperties CreateTextRunProperties()
{ {
return new GenericTextRunProperties(new Typeface(FontFamily, FontStyle, FontWeight), FontSize, var textDecorations = TextDecorations;
TextDecorations, Foreground, Background, BaselineAlignment); var background = Background;
if(Parent is Inline inline)
{
if(textDecorations == null)
{
textDecorations = inline.TextDecorations;
}
if(background == null)
{
background = inline.Background;
}
}
var fontStyle = FontStyle;
if(Parent is Italic)
{
fontStyle = FontStyle.Italic;
}
var fontWeight = FontWeight;
if(Parent is Bold)
{
fontWeight = FontWeight.Bold;
}
return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize,
textDecorations, Foreground, background, BaselineAlignment);
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)

78
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -14,7 +14,6 @@ namespace Avalonia.Controls.Documents
{ {
private ILogical? _parent; private ILogical? _parent;
private IInlineHost? _inlineHost; private IInlineHost? _inlineHost;
private string? _text = string.Empty;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="InlineCollection"/> class. /// Initializes a new instance of the <see cref="InlineCollection"/> class.
@ -22,7 +21,7 @@ namespace Avalonia.Controls.Documents
public InlineCollection() public InlineCollection()
{ {
ResetBehavior = ResetBehavior.Remove; ResetBehavior = ResetBehavior.Remove;
this.ForEachItem( this.ForEachItem(
x => x =>
{ {
@ -36,16 +35,16 @@ namespace Avalonia.Controls.Documents
x.InlineHost = InlineHost; x.InlineHost = InlineHost;
Invalidate(); Invalidate();
}, },
() => throw new NotSupportedException()); () => throw new NotSupportedException());
} }
internal ILogical? Parent internal ILogical? Parent
{ {
get => _parent; get => _parent;
set set
{ {
_parent = value; _parent = value;
OnParentChanged(value); OnParentChanged(value);
} }
} }
@ -61,8 +60,6 @@ namespace Avalonia.Controls.Documents
} }
} }
public bool HasComplexContent => Count > 0;
/// <summary> /// <summary>
/// Gets or adds the text held by the inlines collection. /// Gets or adds the text held by the inlines collection.
/// <remarks> /// <remarks>
@ -73,11 +70,6 @@ namespace Avalonia.Controls.Documents
{ {
get get
{ {
if (!HasComplexContent)
{
return _text;
}
var builder = new StringBuilder(); var builder = new StringBuilder();
foreach (var inline in this) foreach (var inline in this)
@ -87,17 +79,7 @@ namespace Avalonia.Controls.Documents
return builder.ToString(); return builder.ToString();
} }
set
{
if (HasComplexContent)
{
Add(new Run(value));
}
else
{
_text = value;
}
}
} }
/// <summary> /// <summary>
@ -110,36 +92,46 @@ namespace Avalonia.Controls.Documents
/// <param name="text"></param> /// <param name="text"></param>
public void Add(string text) public void Add(string text)
{ {
if (HasComplexContent) AddText(text);
{ }
Add(new Run(text));
} public override void Add(Inline inline)
else {
{ OnAdd();
_text = text;
} base.Add(inline);
} }
public void Add(IControl child) public void Add(IControl child)
{ {
var implicitRun = new InlineUIContainer(child); OnAdd();
base.Add(new InlineUIContainer(child));
}
Add(implicitRun); private void AddText(string text)
{
if(Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
}
else
{
base.Add(new Run(text));
}
} }
public override void Add(Inline item) private void OnAdd()
{ {
if (!HasComplexContent) if (Parent is RichTextBlock textBlock)
{ {
if (!string.IsNullOrEmpty(_text)) if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text))
{ {
base.Add(new Run(_text)); base.Add(new Run(textBlock._text));
textBlock._text = null;
} }
_text = null;
} }
base.Add(item);
} }
/// <summary> /// <summary>
@ -152,7 +144,7 @@ namespace Avalonia.Controls.Documents
/// </summary> /// </summary>
protected void Invalidate() protected void Invalidate()
{ {
if(InlineHost != null) if (InlineHost != null)
{ {
InlineHost.Invalidate(); InlineHost.Invalidate();
} }
@ -162,7 +154,7 @@ namespace Avalonia.Controls.Documents
private void OnParentChanged(ILogical? parent) private void OnParentChanged(ILogical? parent)
{ {
foreach(var child in this) foreach (var child in this)
{ {
((ISetLogicalParent)child).SetParent(parent); ((ISetLogicalParent)child).SetParent(parent);
} }

9
src/Avalonia.Controls/Documents/LineBreak.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Avalonia.LogicalTree;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -22,7 +21,13 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList<TextRun> textRuns) internal override void BuildTextRun(IList<TextRun> textRuns)
{ {
textRuns.Add(new TextEndOfLine()); var text = Environment.NewLine.AsMemory();
var textRunProperties = CreateTextRunProperties();
var textCharacters = new TextCharacters(text, textRunProperties);
textRuns.Add(textCharacters);
} }
internal override void AppendText(StringBuilder stringBuilder) internal override void AppendText(StringBuilder stringBuilder)

35
src/Avalonia.Controls/Documents/Span.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
@ -38,41 +37,17 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList<TextRun> textRuns) internal override void BuildTextRun(IList<TextRun> textRuns)
{ {
if (Inlines.HasComplexContent) foreach (var inline in Inlines)
{ {
foreach (var inline in Inlines) inline.BuildTextRun(textRuns);
{
inline.BuildTextRun(textRuns);
}
}
else
{
if (Inlines.Text is string text)
{
var textRunProperties = CreateTextRunProperties();
var textCharacters = new TextCharacters(text.AsMemory(), textRunProperties);
textRuns.Add(textCharacters);
}
} }
} }
internal override void AppendText(StringBuilder stringBuilder) internal override void AppendText(StringBuilder stringBuilder)
{ {
if (Inlines.HasComplexContent) foreach (var inline in Inlines)
{
foreach (var inline in Inlines)
{
inline.AppendText(stringBuilder);
}
}
else
{ {
if (Inlines.Text is string text) inline.AppendText(stringBuilder);
{
stringBuilder.Append(text);
}
} }
} }
@ -93,7 +68,7 @@ namespace Avalonia.Controls.Documents
{ {
base.OnInlineHostChanged(oldValue, newValue); base.OnInlineHostChanged(oldValue, newValue);
if(Inlines is not null) if (Inlines is not null)
{ {
Inlines.InlineHost = newValue; Inlines.InlineHost = newValue;
} }

12
src/Avalonia.Controls/Documents/TextElement.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontFamily>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, FontFamily>(
nameof(FontFamily), nameof(FontFamily),
defaultValue: FontFamily.Default, defaultValue: FontFamily.Default,
inherits: true); inherits: true);
@ -27,7 +27,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly AttachedProperty<double> FontSizeProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, double>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, double>(
nameof(FontSize), nameof(FontSize),
defaultValue: 12, defaultValue: 12,
inherits: true); inherits: true);
@ -36,7 +36,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly AttachedProperty<FontStyle> FontStyleProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStyle>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, FontStyle>(
nameof(FontStyle), nameof(FontStyle),
inherits: true); inherits: true);
@ -44,7 +44,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly AttachedProperty<FontWeight> FontWeightProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontWeight>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, FontWeight>(
nameof(FontWeight), nameof(FontWeight),
inherits: true, inherits: true,
defaultValue: FontWeight.Normal); defaultValue: FontWeight.Normal);
@ -53,7 +53,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="FontStretch"/> property. /// Defines the <see cref="FontStretch"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty = public static readonly AttachedProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStretch>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, FontStretch>(
nameof(FontStretch), nameof(FontStretch),
inherits: true, inherits: true,
defaultValue: FontStretch.Normal); defaultValue: FontStretch.Normal);
@ -62,7 +62,7 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty = public static readonly AttachedProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, IBrush?>( AvaloniaProperty.RegisterAttached<TextElement, TextElement, IBrush?>(
nameof(Foreground), nameof(Foreground),
Brushes.Black, Brushes.Black,
inherits: true); inherits: true);

6
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Styling;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -14,6 +15,11 @@ namespace Avalonia.Controls.Generators
/// </summary> /// </summary>
IEnumerable<ItemContainerInfo> Containers { get; } IEnumerable<ItemContainerInfo> Containers { get; }
/// <summary>
/// Gets or sets the theme to be applied to the items in the control.
/// </summary>
ControlTheme? ItemContainerTheme { get; set; }
/// <summary> /// <summary>
/// Gets or sets the data template used to display the items in the control. /// Gets or sets the data template used to display the items in the control.
/// </summary> /// </summary>

16
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -4,6 +4,7 @@ using System.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Styling;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -35,6 +36,11 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ItemContainerEventArgs>? Recycled; public event EventHandler<ItemContainerEventArgs>? Recycled;
/// <summary>
/// Gets or sets the theme to be applied to the items in the control.
/// </summary>
public ControlTheme? ItemContainerTheme { get; set; }
/// <summary> /// <summary>
/// Gets or sets the data template used to display the items in the control. /// Gets or sets the data template used to display the items in the control.
/// </summary> /// </summary>
@ -190,10 +196,18 @@ namespace Avalonia.Controls.Generators
result.SetValue( result.SetValue(
ContentPresenter.ContentTemplateProperty, ContentPresenter.ContentTemplateProperty,
ItemTemplate, ItemTemplate,
BindingPriority.TemplatedParent); BindingPriority.Style);
} }
} }
if (ItemContainerTheme != null)
{
result.SetValue(
StyledElement.ThemeProperty,
ItemContainerTheme,
BindingPriority.TemplatedParent);
}
return result; return result;
} }

21
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -44,28 +44,29 @@ namespace Avalonia.Controls.Generators
{ {
var container = item as T; var container = item as T;
if (container != null) if (container is null)
{ {
return container; container = new T();
}
else
{
var result = new T();
if (ContentTemplateProperty != null) if (ContentTemplateProperty != null)
{ {
result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
} }
result.SetValue(ContentProperty, item, BindingPriority.Style); container.SetValue(ContentProperty, item, BindingPriority.Style);
if (!(item is IControl)) if (!(item is IControl))
{ {
result.DataContext = item; container.DataContext = item;
} }
}
return result; if (ItemContainerTheme != null)
{
container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style);
} }
return container;
} }
/// <inheritdoc/> /// <inheritdoc/>

5
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -71,6 +71,11 @@ namespace Avalonia.Controls.Generators
var template = GetTreeDataTemplate(item, ItemTemplate); var template = GetTreeDataTemplate(item, ItemTemplate);
var result = new T(); var result = new T();
if (ItemContainerTheme != null)
{
result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style);
}
result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
var itemsSelector = template.ItemsSelector(item); var itemsSelector = template.ItemsSelector(item);

21
src/Avalonia.Controls/ItemsControl.cs

@ -15,6 +15,7 @@ using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Styling;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -36,6 +37,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty = public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v); AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemContainerTheme"/> property.
/// </summary>
public static readonly StyledProperty<ControlTheme?> ItemContainerThemeProperty =
AvaloniaProperty.Register<ItemsControl, ControlTheme?>(nameof(ItemContainerTheme));
/// <summary> /// <summary>
/// Defines the <see cref="ItemCount"/> property. /// Defines the <see cref="ItemCount"/> property.
/// </summary> /// </summary>
@ -88,6 +95,7 @@ namespace Avalonia.Controls
{ {
_itemContainerGenerator = CreateItemContainerGenerator(); _itemContainerGenerator = CreateItemContainerGenerator();
_itemContainerGenerator.ItemContainerTheme = ItemContainerTheme;
_itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.ItemTemplate = ItemTemplate;
_itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
_itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
@ -108,6 +116,15 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); } set { SetAndRaise(ItemsProperty, ref _items, value); }
} }
/// <summary>
/// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for each item.
/// </summary>
public ControlTheme? ItemContainerTheme
{
get { return GetValue(ItemContainerThemeProperty); }
set { SetValue(ItemContainerThemeProperty, value); }
}
/// <summary> /// <summary>
/// Gets the number of items in <see cref="Items"/>. /// Gets the number of items in <see cref="Items"/>.
/// </summary> /// </summary>
@ -349,6 +366,10 @@ namespace Avalonia.Controls
{ {
UpdatePseudoClasses(change.GetNewValue<int>()); UpdatePseudoClasses(change.GetNewValue<int>());
} }
else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
{
_itemContainerGenerator.ItemContainerTheme = change.GetNewValue<ControlTheme?>();
}
} }
/// <summary> /// <summary>

9
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -6,7 +6,6 @@ using Avalonia.Input.Raw;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Rendering; using Avalonia.Rendering;
using JetBrains.Annotations;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -151,13 +150,7 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the input focus is lost. /// Gets or sets a method called when the input focus is lost.
/// </summary> /// </summary>
Action? LostFocus { get; set; } Action? LostFocus { get; set; }
/// <summary>
/// Gets a mouse device associated with toplevel
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
IPopupImpl? CreatePopup(); IPopupImpl? CreatePopup();
/// <summary> /// <summary>

13
src/Avalonia.Controls/Platform/PlatformManager.cs

@ -20,17 +20,8 @@ namespace Avalonia.Controls.Platform
{ {
} }
public static ITrayIconImpl? CreateTrayIcon() public static ITrayIconImpl? CreateTrayIcon() =>
{ s_designerMode ? null : AvaloniaLocator.Current.GetService<IWindowingPlatform>()?.CreateTrayIcon();
var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
if (platform == null)
{
throw new Exception("Could not CreateTrayIcon(): IWindowingPlatform is not registered.");
}
return s_designerMode ? null : platform.CreateTrayIcon();
}
public static IWindowImpl CreateWindow() public static IWindowImpl CreateWindow()

11
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -45,6 +45,7 @@ Copyright © 2019 Nikita Tsukanov
*/ */
using System; using System;
using Avalonia.Input;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Media; using Avalonia.Media;
@ -452,16 +453,14 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect, PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect,
FlowDirection flowDirection) FlowDirection flowDirection)
{ {
// We need a better way for tracking the last pointer position
#pragma warning disable CS0618 // Type or member is obsolete
var pointer = topLevel.PointToClient(topLevel.PlatformImpl?.MouseDevice.Position ?? default);
#pragma warning restore CS0618 // Type or member is obsolete
positionerParameters.Offset = offset; positionerParameters.Offset = offset;
positionerParameters.ConstraintAdjustment = constraintAdjustment; positionerParameters.ConstraintAdjustment = constraintAdjustment;
if (placement == PlacementMode.Pointer) if (placement == PlacementMode.Pointer)
{ {
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1)); // We need a better way for tracking the last pointer position
var position = topLevel.PointToClient(topLevel.LastPointerPosition ?? default);
positionerParameters.AnchorRectangle = new Rect(position, new Size(1, 1));
positionerParameters.Anchor = PopupAnchor.TopLeft; positionerParameters.Anchor = PopupAnchor.TopLeft;
positionerParameters.Gravity = PopupGravity.BottomRight; positionerParameters.Gravity = PopupGravity.BottomRight;
} }

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

@ -367,6 +367,17 @@ namespace Avalonia.Controls.Primitives
{ {
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ThemeProperty)
{
foreach (var child in this.GetTemplateChildren())
child.InvalidateStyles();
}
}
/// <summary> /// <summary>
/// Called when the control's template is applied. /// Called when the control's template is applied.
/// </summary> /// </summary>

11
src/Avalonia.Controls/ProgressBar.cs

@ -100,6 +100,7 @@ namespace Avalonia.Controls
private double _indeterminateStartingOffset; private double _indeterminateStartingOffset;
private double _indeterminateEndingOffset; private double _indeterminateEndingOffset;
private Border? _indicator; private Border? _indicator;
private IDisposable? _trackSizeChangedListener;
public static readonly StyledProperty<bool> IsIndeterminateProperty = public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate)); AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
@ -195,8 +196,9 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
var result = base.ArrangeOverride(finalSize);
UpdateIndicator(); UpdateIndicator();
return base.ArrangeOverride(finalSize); return result;
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@ -216,8 +218,15 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
// dispose any previous track size listener
_trackSizeChangedListener?.Dispose();
_indicator = e.NameScope.Get<Border>("PART_Indicator"); _indicator = e.NameScope.Get<Border>("PART_Indicator");
// listen to size changes of the indicators track (parent) and update the indicator there.
_trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty)
.Subscribe(_ => UpdateIndicator());
UpdateIndicator(); UpdateIndicator();
} }

1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -12,7 +12,6 @@ using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input; using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport; using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading; using Avalonia.Threading;
using InputModifiers = Avalonia.Input.InputModifiers;
using Key = Avalonia.Input.Key; using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat; using PixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;

160
src/Avalonia.Controls/RichTextBlock.cs

@ -41,9 +41,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue); AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionForegroundBrush));
/// <summary> /// <summary>
/// Defines the <see cref="Inlines"/> property. /// Defines the <see cref="Inlines"/> property.
/// </summary> /// </summary>
@ -63,12 +60,13 @@ namespace Avalonia.Controls
private bool _canCopy; private bool _canCopy;
private int _selectionStart; private int _selectionStart;
private int _selectionEnd; private int _selectionEnd;
private int _wordSelectionStart = -1;
static RichTextBlock() static RichTextBlock()
{ {
FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true);
AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty); AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty);
} }
public RichTextBlock() public RichTextBlock()
@ -89,15 +87,6 @@ namespace Avalonia.Controls
set => SetValue(SelectionBrushProperty, value); set => SetValue(SelectionBrushProperty, value);
} }
/// <summary>
/// Gets or sets a value that defines the brush used for selected text.
/// </summary>
public IBrush? SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets a character index for the beginning of the current selection. /// Gets or sets a character index for the beginning of the current selection.
/// </summary> /// </summary>
@ -170,6 +159,8 @@ namespace Avalonia.Controls
remove => RemoveHandler(CopyingToClipboardEvent, value); remove => RemoveHandler(CopyingToClipboardEvent, value);
} }
internal bool HasComplexContent => Inlines.Count > 0;
/// <summary> /// <summary>
/// Copies the current selection to the Clipboard. /// Copies the current selection to the Clipboard.
/// </summary> /// </summary>
@ -198,7 +189,7 @@ namespace Avalonia.Controls
} }
} }
public override void Render(DrawingContext context) protected override void RenderTextLayout(DrawingContext context, Point origin)
{ {
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
@ -213,13 +204,16 @@ namespace Avalonia.Controls
var rects = TextLayout.HitTestTextRange(start, length); var rects = TextLayout.HitTestTextRange(start, length);
foreach (var rect in rects) using (context.PushPostTransform(Matrix.CreateTranslation(origin)))
{ {
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); foreach (var rect in rects)
{
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
}
} }
} }
base.Render(context); base.RenderTextLayout(context, origin);
} }
/// <summary> /// <summary>
@ -251,6 +245,30 @@ namespace Avalonia.Controls
SelectionEnd = SelectionStart; SelectionEnd = SelectionStart;
} }
protected void AddText(string? text)
{
if (string.IsNullOrEmpty(text))
{
return;
}
if (!HasComplexContent && string.IsNullOrEmpty(_text))
{
_text = text;
}
else
{
if (!string.IsNullOrEmpty(_text))
{
Inlines.Add(_text);
_text = null;
}
Inlines.Add(text);
}
}
protected override string? GetText() protected override string? GetText()
{ {
return _text ?? Inlines.Text; return _text ?? Inlines.Text;
@ -259,17 +277,8 @@ namespace Avalonia.Controls
protected override void SetText(string? text) protected override void SetText(string? text)
{ {
var oldValue = _text ?? Inlines?.Text; var oldValue = _text ?? Inlines?.Text;
if (Inlines is not null && Inlines.HasComplexContent) AddText(text);
{
Inlines.Text = text;
_text = null;
}
else
{
_text = text;
}
RaisePropertyChanged(TextProperty, oldValue, text); RaisePropertyChanged(TextProperty, oldValue, text);
} }
@ -280,8 +289,9 @@ namespace Avalonia.Controls
/// <returns>A <see cref="TextLayout"/> object.</returns> /// <returns>A <see cref="TextLayout"/> object.</returns>
protected override TextLayout CreateTextLayout(string? text) protected override TextLayout CreateTextLayout(string? text)
{ {
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var defaultProperties = new GenericTextRunProperties( var defaultProperties = new GenericTextRunProperties(
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), typeface,
FontSize, FontSize,
TextDecorations, TextDecorations,
Foreground); Foreground);
@ -293,7 +303,7 @@ namespace Avalonia.Controls
var inlines = Inlines; var inlines = Inlines;
if (inlines is not null && inlines.HasComplexContent) if (HasComplexContent)
{ {
var textRuns = new List<TextRun>(); var textRuns = new List<TextRun>();
@ -328,6 +338,8 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e);
var handled = false; var handled = false;
var modifiers = e.KeyModifiers; var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>(); var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
@ -346,6 +358,8 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
base.OnPointerPressed(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -356,7 +370,9 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed) if (text != null && clickInfo.Properties.IsLeftButtonPressed)
{ {
var point = e.GetPosition(this); var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift); var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
@ -365,8 +381,6 @@ namespace Avalonia.Controls
var hit = TextLayout.HitTestPoint(point); var hit = TextLayout.HitTestPoint(point);
var index = hit.TextPosition; var index = hit.TextPosition;
SelectionStart = SelectionEnd = index;
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount) switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
@ -374,12 +388,34 @@ namespace Avalonia.Controls
case 1: case 1:
if (clickToSelect) if (clickToSelect)
{ {
SelectionStart = Math.Min(oldIndex, index); if (_wordSelectionStart >= 0)
SelectionEnd = Math.Max(oldIndex, index); {
var previousWord = StringUtils.PreviousWord(text, index);
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
}
} }
else else
{ {
SelectionStart = SelectionEnd = index; if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
_wordSelectionStart = -1;
}
} }
break; break;
@ -389,9 +425,13 @@ namespace Avalonia.Controls
SelectionStart = StringUtils.PreviousWord(text, index); SelectionStart = StringUtils.PreviousWord(text, index);
} }
_wordSelectionStart = SelectionStart;
SelectionEnd = StringUtils.NextWord(text, index); SelectionEnd = StringUtils.NextWord(text, index);
break; break;
case 3: case 3:
_wordSelectionStart = -1;
SelectAll(); SelectAll();
break; break;
} }
@ -403,6 +443,8 @@ namespace Avalonia.Controls
protected override void OnPointerMoved(PointerEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
base.OnPointerMoved(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -411,20 +453,49 @@ namespace Avalonia.Controls
// selection should not change during pointer move if the user right clicks // selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
var point = e.GetPosition(this); var text = Text;
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
point = new Point( point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(Bounds.Width - 1, 0)), MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(Bounds.Height - 1, 0))); MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0)));
var hit = TextLayout.HitTestPoint(point); var hit = TextLayout.HitTestPoint(point);
var textPosition = hit.TextPosition;
if (text != null && _wordSelectionStart >= 0)
{
var distance = textPosition - _wordSelectionStart;
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, textPosition);
}
if (distance >= 0)
{
if (SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
}
SelectionEnd = StringUtils.NextWord(text, textPosition);
}
}
else
{
SelectionEnd = textPosition;
}
SelectionEnd = hit.TextPosition;
} }
} }
protected override void OnPointerReleased(PointerReleasedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
base.OnPointerReleased(e);
if (!IsTextSelectionEnabled) if (!IsTextSelectionEnabled)
{ {
return; return;
@ -437,7 +508,9 @@ namespace Avalonia.Controls
if (e.InitialPressMouseButton == MouseButton.Right) if (e.InitialPressMouseButton == MouseButton.Right)
{ {
var point = e.GetPosition(this); var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var hit = TextLayout.HitTestPoint(point); var hit = TextLayout.HitTestPoint(point);
@ -470,11 +543,6 @@ namespace Avalonia.Controls
InvalidateTextLayout(); InvalidateTextLayout();
break; break;
} }
case nameof(TextProperty):
{
InvalidateTextLayout();
break;
}
} }
} }

22
src/Avalonia.Controls/TextBlock.cs

@ -4,6 +4,7 @@ using Avalonia.Controls.Documents;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -11,7 +12,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control that displays a block of text. /// A control that displays a block of text.
/// </summary> /// </summary>
public class TextBlock : Control public class TextBlock : Control, IAddChild<string>
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
@ -128,7 +129,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty = public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.Register<TextBlock, TextDecorationCollection?>(nameof(TextDecorations)); AvaloniaProperty.Register<TextBlock, TextDecorationCollection?>(nameof(TextDecorations));
protected string? _text; internal string? _text;
protected TextLayout? _textLayout; protected TextLayout? _textLayout;
protected Size _constraint; protected Size _constraint;
@ -473,11 +474,6 @@ namespace Avalonia.Controls
control.SetValue(MaxLinesProperty, maxLines); control.SetValue(MaxLinesProperty, maxLines);
} }
public void Add(string text)
{
_text = text;
}
/// <summary> /// <summary>
/// Renders the <see cref="TextBlock"/> to a drawing context. /// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary> /// </summary>
@ -509,7 +505,17 @@ namespace Avalonia.Controls
} }
} }
TextLayout.Draw(context, new Point(padding.Left, top)); RenderTextLayout(context, new Point(padding.Left, top));
}
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
TextLayout.Draw(context, origin);
}
void IAddChild<string>.AddChild(string text)
{
_text = text;
} }
protected virtual string? GetText() protected virtual string? GetText()

285
src/Avalonia.Controls/TextBox.cs

@ -53,7 +53,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<char> PasswordCharProperty = public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush)); AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush));
@ -80,7 +80,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> MaxLinesProperty = public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty = public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>( TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text, o => o.Text,
@ -105,7 +105,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>(); TextBlock.TextWrappingProperty.AddOwner<TextBox>();
/// <summary> /// <summary>
/// Defines see <see cref="TextPresenter.LineHeight"/> property. /// Defines see <see cref="TextPresenter.LineHeight"/> property.
/// </summary> /// </summary>
@ -202,6 +202,7 @@ namespace Avalonia.Controls
private string _newLine = Environment.NewLine; private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
private int _wordSelectionStart = -1;
private int _selectedTextChangesMadeSinceLastUndoSnapshot; private int _selectedTextChangesMadeSinceLastUndoSnapshot;
private bool _hasDoneSnapshotOnce; private bool _hasDoneSnapshotOnce;
private const int _maxCharsBeforeUndoSnapshot = 7; private const int _maxCharsBeforeUndoSnapshot = 7;
@ -275,7 +276,7 @@ namespace Avalonia.Controls
get => GetValue(IsReadOnlyProperty); get => GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value); set => SetValue(IsReadOnlyProperty, value);
} }
public char PasswordChar public char PasswordChar
{ {
get => GetValue(PasswordCharProperty); get => GetValue(PasswordCharProperty);
@ -307,7 +308,7 @@ namespace Avalonia.Controls
{ {
value = CoerceCaretIndex(value); value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value); var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
if (changed) if (changed)
{ {
UpdateCommandStates(); UpdateCommandStates();
@ -327,12 +328,12 @@ namespace Avalonia.Controls
{ {
value = CoerceCaretIndex(value); value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (changed) if (changed)
{ {
UpdateCommandStates(); UpdateCommandStates();
} }
if (SelectionStart == value && CaretIndex != value) if (SelectionStart == value && CaretIndex != value)
{ {
CaretIndex = value; CaretIndex = value;
@ -351,7 +352,7 @@ namespace Avalonia.Controls
get => GetValue(MaxLinesProperty); get => GetValue(MaxLinesProperty);
set => SetValue(MaxLinesProperty, value); set => SetValue(MaxLinesProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the line height. /// Gets or sets the line height.
/// </summary> /// </summary>
@ -370,7 +371,7 @@ namespace Avalonia.Controls
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value); CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value);
@ -567,7 +568,7 @@ namespace Avalonia.Controls
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter"); _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter, this); _imClient.SetPresenter(_presenter, this);
if (IsFocused) if (IsFocused)
{ {
_presenter?.ShowCaret(); _presenter?.ShowCaret();
@ -577,7 +578,7 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
if (IsFocused) if (IsFocused)
{ {
_presenter?.ShowCaret(); _presenter?.ShowCaret();
@ -587,7 +588,7 @@ namespace Avalonia.Controls
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
_imClient.SetPresenter(null, null); _imClient.SetPresenter(null, null);
} }
@ -637,7 +638,7 @@ namespace Avalonia.Controls
} }
UpdateCommandStates(); UpdateCommandStates();
_imClient.SetPresenter(_presenter, this); _imClient.SetPresenter(_presenter, this);
_presenter?.ShowCaret(); _presenter?.ShowCaret();
@ -657,7 +658,7 @@ namespace Avalonia.Controls
UpdateCommandStates(); UpdateCommandStates();
_presenter?.HideCaret(); _presenter?.HideCaret();
_imClient.SetPresenter(null, null); _imClient.SetPresenter(null, null);
} }
@ -700,14 +701,14 @@ namespace Avalonia.Controls
if (grapheme.FirstCodepoint.IsBreakChar) if (grapheme.FirstCodepoint.IsBreakChar)
{ {
if(lineCount + 1 > MaxLines) if (lineCount + 1 > MaxLines)
{ {
break; break;
} }
else else
{ {
lineCount++; lineCount++;
} }
} }
length += grapheme.Text.Length; length += grapheme.Text.Length;
@ -736,7 +737,7 @@ namespace Avalonia.Controls
text = Text ?? string.Empty; text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection(); ClearSelection();
if (IsUndoEnabled) if (IsUndoEnabled)
{ {
_undoRedoHelper.DiscardRedo(); _undoRedoHelper.DiscardRedo();
@ -746,7 +747,7 @@ namespace Avalonia.Controls
{ {
RaisePropertyChanged(TextProperty, oldText, _text); RaisePropertyChanged(TextProperty, oldText, _text);
} }
CaretIndex = caretIndex + input.Length; CaretIndex = caretIndex + input.Length;
} }
} }
@ -828,7 +829,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
var movement = false; var movement = false;
@ -985,87 +986,87 @@ namespace Avalonia.Controls
break; break;
case Key.Up: case Key.Up:
{
selection = DetectSelection();
_presenter.MoveCaretVertical(LogicalDirection.Backward);
if (caretIndex != _presenter.CaretIndex)
{ {
movement = true; selection = DetectSelection();
}
if (selection) _presenter.MoveCaretVertical(LogicalDirection.Backward);
{
SelectionEnd = _presenter.CaretIndex; if (caretIndex != _presenter.CaretIndex)
} {
else movement = true;
{ }
CaretIndex = _presenter.CaretIndex;
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
}
break;
} }
break;
}
case Key.Down: case Key.Down:
{
selection = DetectSelection();
_presenter.MoveCaretVertical();
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{ {
CaretIndex = _presenter.CaretIndex; selection = DetectSelection();
_presenter.MoveCaretVertical();
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
}
break;
} }
break;
}
case Key.Back: case Key.Back:
{
SnapshotUndoRedo();
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{ {
SetSelectionForControlBackspace(); SnapshotUndoRedo();
}
if (!DeleteSelection())
{
var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace();
}
if (caretIndex != backspacePosition) if (!DeleteSelection())
{ {
var start = Math.Min(backspacePosition, caretIndex); var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
var end = Math.Max(backspacePosition, caretIndex);
var length = end - start; var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); if (caretIndex != backspacePosition)
{
var start = Math.Min(backspacePosition, caretIndex);
var end = Math.Max(backspacePosition, caretIndex);
SetTextInternal(editedText); var length = end - start;
CaretIndex = start; var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
}
}
SnapshotUndoRedo();
handled = true; SetTextInternal(editedText);
break;
} CaretIndex = start;
}
}
SnapshotUndoRedo();
handled = true;
break;
}
case Key.Delete: case Key.Delete:
SnapshotUndoRedo(); SnapshotUndoRedo();
if (hasWholeWordModifiers && SelectionStart == SelectionEnd) if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{ {
SetSelectionForControlDelete(); SetSelectionForControlDelete();
@ -1077,7 +1078,7 @@ namespace Avalonia.Controls
var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if(nextPosition != caretIndex) if (nextPosition != caretIndex)
{ {
var start = Math.Min(nextPosition, caretIndex); var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex); var end = Math.Max(nextPosition, caretIndex);
@ -1144,7 +1145,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
var text = Text; var text = Text;
var clickInfo = e.GetCurrentPoint(this); var clickInfo = e.GetCurrentPoint(this);
@ -1170,24 +1171,50 @@ namespace Avalonia.Controls
case 1: case 1:
if (clickToSelect) if (clickToSelect)
{ {
SelectionStart = Math.Min(oldIndex, index); if (_wordSelectionStart >= 0)
SelectionEnd = Math.Max(oldIndex, index); {
var previousWord = StringUtils.PreviousWord(text, index);
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
}
} }
else else
{ {
SelectionStart = SelectionEnd = index; if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
_wordSelectionStart = -1;
}
} }
break; break;
case 2: case 2:
if (!StringUtils.IsStartOfWord(text, index)) if (!StringUtils.IsStartOfWord(text, index))
{ {
SelectionStart = StringUtils.PreviousWord(text, index); SelectionStart = StringUtils.PreviousWord(text, index);
} }
_wordSelectionStart = SelectionStart;
SelectionEnd = StringUtils.NextWord(text, index); SelectionEnd = StringUtils.NextWord(text, index);
break; break;
case 3: case 3:
_wordSelectionStart = -1;
SelectAll(); SelectAll();
break; break;
} }
@ -1203,7 +1230,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
// selection should not change during pointer move if the user right clicks // selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
@ -1215,7 +1242,32 @@ namespace Avalonia.Controls
_presenter.MoveCaretToPoint(point); _presenter.MoveCaretToPoint(point);
SelectionEnd = _presenter.CaretIndex; var caretIndex = _presenter.CaretIndex;
var text = Text;
if (text != null && _wordSelectionStart >= 0)
{
var distance = caretIndex - _wordSelectionStart;
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, caretIndex);
}
if (distance >= 0)
{
if(SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
}
SelectionEnd = StringUtils.NextWord(text, caretIndex);
}
}
else
{
SelectionEnd = _presenter.CaretIndex;
}
} }
} }
@ -1234,9 +1286,9 @@ namespace Avalonia.Controls
if (e.InitialPressMouseButton == MouseButton.Right) if (e.InitialPressMouseButton == MouseButton.Right)
{ {
var point = e.GetPosition(_presenter); var point = e.GetPosition(_presenter);
_presenter.MoveCaretToPoint(point); _presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex; var caretIndex = _presenter.CaretIndex;
// see if mouse clicked inside current selection // see if mouse clicked inside current selection
@ -1250,7 +1302,7 @@ namespace Avalonia.Controls
CaretIndex = SelectionEnd = SelectionStart = caretIndex; CaretIndex = SelectionEnd = SelectionStart = caretIndex;
} }
} }
e.Pointer.Capture(null); e.Pointer.Capture(null);
} }
@ -1309,7 +1361,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
@ -1319,11 +1371,11 @@ namespace Avalonia.Controls
if (isSelecting) if (isSelecting)
{ {
_presenter.MoveCaretToTextPosition(selectionEnd); _presenter.MoveCaretToTextPosition(selectionEnd);
_presenter.MoveCaretHorizontal(direction > 0 ? _presenter.MoveCaretHorizontal(direction > 0 ?
LogicalDirection.Forward : LogicalDirection.Forward :
LogicalDirection.Backward); LogicalDirection.Backward);
SelectionEnd = _presenter.CaretIndex; SelectionEnd = _presenter.CaretIndex;
} }
else else
@ -1347,7 +1399,7 @@ namespace Avalonia.Controls
else else
{ {
int offset; int offset;
if (direction > 0) if (direction > 0)
{ {
offset = StringUtils.NextWord(text, selectionEnd) - selectionEnd; offset = StringUtils.NextWord(text, selectionEnd) - selectionEnd;
@ -1356,7 +1408,7 @@ namespace Avalonia.Controls
{ {
offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd; offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
} }
SelectionEnd += offset; SelectionEnd += offset;
_presenter.MoveCaretToTextPosition(SelectionEnd); _presenter.MoveCaretToTextPosition(SelectionEnd);
@ -1378,7 +1430,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
if (document) if (document)
@ -1401,7 +1453,7 @@ namespace Avalonia.Controls
{ {
return; return;
} }
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
@ -1432,8 +1484,9 @@ namespace Avalonia.Controls
private bool DeleteSelection(bool raiseTextChanged = true) private bool DeleteSelection(bool raiseTextChanged = true)
{ {
if (IsReadOnly) return true; if (IsReadOnly)
return true;
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
@ -1444,40 +1497,40 @@ namespace Avalonia.Controls
var text = Text!; var text = Text!;
SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged); SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
_presenter?.MoveCaretToTextPosition(start); _presenter?.MoveCaretToTextPosition(start);
CaretIndex= start; CaretIndex = start;
ClearSelection(); ClearSelection();
return true; return true;
} }
CaretIndex = SelectionStart; CaretIndex = SelectionStart;
return false; return false;
} }
private string GetSelection() private string GetSelection()
{ {
var text = Text; var text = Text;
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
{ {
return ""; return "";
} }
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd); var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd); var end = Math.Max(selectionStart, selectionEnd);
if (start == end || (Text?.Length ?? 0) < end) if (start == end || (Text?.Length ?? 0) < end)
{ {
return ""; return "";
} }
return text.Substring(start, end - start); return text.Substring(start, end - start);
} }
@ -1496,7 +1549,7 @@ namespace Avalonia.Controls
private void SetSelectionForControlBackspace() private void SetSelectionForControlBackspace()
{ {
var selectionStart = CaretIndex; var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false); MoveHorizontal(-1, true, false);
SelectionStart = selectionStart; SelectionStart = selectionStart;
@ -1508,9 +1561,9 @@ namespace Avalonia.Controls
{ {
return; return;
} }
SelectionStart = CaretIndex; SelectionStart = CaretIndex;
MoveHorizontal(1, true, true); MoveHorizontal(1, true, true);
if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ') if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')

5
src/Avalonia.Controls/TopLevel.cs

@ -285,6 +285,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public IRenderer Renderer { get; private set; } public IRenderer Renderer { get; private set; }
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition;
/// <summary> /// <summary>
/// Gets the access key handler for the window. /// Gets the access key handler for the window.
/// </summary> /// </summary>
@ -302,9 +304,6 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); } set { SetValue(PointerOverElementProperty, value); }
} }
/// <inheritdoc/>
IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>

5
src/Avalonia.Controls/TreeViewItem.cs

@ -121,6 +121,11 @@ namespace Avalonia.Controls
{ {
ItemTemplate = _treeView.ItemTemplate; ItemTemplate = _treeView.ItemTemplate;
} }
if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null)
{
ItemContainerTheme = _treeView.ItemContainerTheme;
}
} }
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)

22
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -37,6 +37,7 @@ namespace Avalonia.DesignerSupport
var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null; var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
var loaded = loader.Load(stream, localAsm, null, baseUri, true); var loaded = loader.Load(stream, localAsm, null, baseUri, true);
var style = loaded as IStyle; var style = loaded as IStyle;
var resources = loaded as ResourceDictionary;
if (style != null) if (style != null)
{ {
var substitute = Design.GetPreviewWith((AvaloniaObject)style); var substitute = Design.GetPreviewWith((AvaloniaObject)style);
@ -58,6 +59,27 @@ namespace Avalonia.DesignerSupport
} }
}; };
} }
else if (resources != null)
{
var substitute = Design.GetPreviewWith(resources);
if (substitute != null)
{
substitute.Resources.MergedDictionaries.Add(resources);
control = substitute;
}
else
control = new StackPanel
{
Children =
{
new TextBlock {Text = "ResourceDictionaries can't be previewed without Design.PreviewWith. Add"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>"},
new TextBlock {Text = "</Design.PreviewWith>"},
new TextBlock {Text = "in your resource dictionary"}
}
};
}
else if (loaded is Application) else if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"}; control = new TextBlock {Text = "Application can't be previewed in design view"};
else else

11
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -67,8 +67,15 @@ namespace Avalonia.Diagnostics.ViewModels
var setters = new List<SetterViewModel>(); var setters = new List<SetterViewModel>();
if (styleSource is Style style) if (styleSource is StyleBase style)
{ {
var selector = style switch
{
Style s => s.Selector?.ToString(),
ControlTheme t => t.TargetType?.Name.ToString(),
_ => null,
};
foreach (var setter in style.Setters) foreach (var setter in style.Setters)
{ {
if (setter is Setter regularSetter if (setter is Setter regularSetter
@ -105,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
AppliedStyles.Add(new StyleViewModel(appliedStyle, style.Selector?.ToString() ?? "No selector", setters)); AppliedStyles.Add(new StyleViewModel(appliedStyle, selector ?? "No selector", setters));
} }
} }

11
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -19,8 +19,10 @@ namespace Avalonia.Diagnostics.Views
internal class MainWindow : Window, IStyleHost internal class MainWindow : Window, IStyleHost
{ {
private readonly IDisposable? _keySubscription; private readonly IDisposable? _keySubscription;
private readonly IDisposable? _pointerSubscription;
private readonly Dictionary<Popup, IDisposable> _frozenPopupStates; private readonly Dictionary<Popup, IDisposable> _frozenPopupStates;
private AvaloniaObject? _root; private AvaloniaObject? _root;
private PixelPoint _lastPointerPosition;
public MainWindow() public MainWindow()
{ {
@ -30,6 +32,10 @@ namespace Avalonia.Diagnostics.Views
.OfType<RawKeyEventArgs>() .OfType<RawKeyEventArgs>()
.Where(x => x.Type == RawKeyEventType.KeyDown) .Where(x => x.Type == RawKeyEventType.KeyDown)
.Subscribe(RawKeyDown); .Subscribe(RawKeyDown);
_pointerSubscription = InputManager.Instance?.Process
.OfType<RawPointerEventArgs>()
.Subscribe(x => _lastPointerPosition = x.Root.PointToScreen(x.Position));
_frozenPopupStates = new Dictionary<Popup, IDisposable>(); _frozenPopupStates = new Dictionary<Popup, IDisposable>();
@ -84,6 +90,7 @@ namespace Avalonia.Diagnostics.Views
{ {
base.OnClosed(e); base.OnClosed(e);
_keySubscription?.Dispose(); _keySubscription?.Dispose();
_pointerSubscription?.Dispose();
foreach (var state in _frozenPopupStates) foreach (var state in _frozenPopupStates)
{ {
@ -108,9 +115,7 @@ namespace Avalonia.Diagnostics.Views
private IControl? GetHoveredControl(TopLevel topLevel) private IControl? GetHoveredControl(TopLevel topLevel)
{ {
#pragma warning disable CS0618 // Type or member is obsolete var point = topLevel.PointToClient(_lastPointerPosition);
var point = (topLevel as IInputRoot)?.MouseDevice?.GetPosition(topLevel) ?? default;
#pragma warning restore CS0618 // Type or member is obsolete
return (IControl?)topLevel.GetVisualsAt(point, x => return (IControl?)topLevel.GetVisualsAt(point, x =>
{ {

4
src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs

@ -21,7 +21,7 @@ namespace Avalonia.OpenGL.Angle
var display = IntPtr.Zero; var display = IntPtr.Zero;
AngleOptions.PlatformApi angleApi = default; AngleOptions.PlatformApi angleApi = default;
{ {
if (_egl.GetPlatformDisplayEXT == null) if (!_egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
@ -37,7 +37,7 @@ namespace Avalonia.OpenGL.Angle
else else
continue; continue;
display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE }); new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
if (display != IntPtr.Zero) if (display != IntPtr.Zero)
{ {

1
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -11,4 +11,5 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\DevAnalyzers.props" /> <Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
</Project> </Project>

14
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -66,11 +66,9 @@ namespace Avalonia.OpenGL.Controls
return; return;
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
var oneArr = new int[1]; _depthBuffer = gl.GenRenderbuffer();
gl.GenRenderbuffers(1, oneArr);
_depthBuffer = oneArr[0];
gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
gl.RenderbufferStorage(GL_RENDERBUFFER, gl.RenderbufferStorage(GL_RENDERBUFFER,
GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
@ -88,9 +86,9 @@ namespace Avalonia.OpenGL.Controls
var gl = _context.GlInterface; var gl = _context.GlInterface;
gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffers(1, new[] { _fb }); gl.DeleteFramebuffer(_fb);
_fb = 0; _fb = 0;
gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); gl.DeleteRenderbuffer(_depthBuffer);
_depthBuffer = 0; _depthBuffer = 0;
_attachment?.Dispose(); _attachment?.Dispose();
_attachment = null; _attachment = null;
@ -174,9 +172,7 @@ namespace Avalonia.OpenGL.Controls
{ {
_depthBufferSize = GetPixelSize(); _depthBufferSize = GetPixelSize();
var gl = _context.GlInterface; var gl = _context.GlInterface;
var oneArr = new int[1]; _fb = gl.GenFramebuffer();
gl.GenFramebuffers(1, oneArr);
_fb = oneArr[0];
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
EnsureDepthBufferAttachment(gl); EnsureDepthBufferAttachment(gl);

376
src/Avalonia.OpenGL/Egl/EglConsts.cs

@ -4,212 +4,212 @@ namespace Avalonia.OpenGL.Egl
{ {
public static class EglConsts public static class EglConsts
{ {
public const int EGL_ALPHA_SIZE = 0x3021; public const int EGL_ALPHA_SIZE = 0x3021;
public const int EGL_BAD_ACCESS = 0x3002; public const int EGL_BAD_ACCESS = 0x3002;
public const int EGL_BAD_ALLOC = 0x3003; public const int EGL_BAD_ALLOC = 0x3003;
public const int EGL_BAD_ATTRIBUTE = 0x3004; public const int EGL_BAD_ATTRIBUTE = 0x3004;
public const int EGL_BAD_CONFIG = 0x3005; public const int EGL_BAD_CONFIG = 0x3005;
public const int EGL_BAD_CONTEXT = 0x3006; public const int EGL_BAD_CONTEXT = 0x3006;
public const int EGL_BAD_CURRENT_SURFACE = 0x3007; public const int EGL_BAD_CURRENT_SURFACE = 0x3007;
public const int EGL_BAD_DISPLAY = 0x3008; public const int EGL_BAD_DISPLAY = 0x3008;
public const int EGL_BAD_MATCH = 0x3009; public const int EGL_BAD_MATCH = 0x3009;
public const int EGL_BAD_NATIVE_PIXMAP = 0x300A; public const int EGL_BAD_NATIVE_PIXMAP = 0x300A;
public const int EGL_BAD_NATIVE_WINDOW = 0x300B; public const int EGL_BAD_NATIVE_WINDOW = 0x300B;
public const int EGL_BAD_PARAMETER = 0x300C; public const int EGL_BAD_PARAMETER = 0x300C;
public const int EGL_BAD_SURFACE = 0x300D; public const int EGL_BAD_SURFACE = 0x300D;
public const int EGL_BLUE_SIZE = 0x3022; public const int EGL_BLUE_SIZE = 0x3022;
public const int EGL_BUFFER_SIZE = 0x3020; // public const int EGL_BUFFER_SIZE = 0x3020;
public const int EGL_CONFIG_CAVEAT = 0x3027; // public const int EGL_CONFIG_CAVEAT = 0x3027;
public const int EGL_CONFIG_ID = 0x3028; // public const int EGL_CONFIG_ID = 0x3028;
public const int EGL_CORE_NATIVE_ENGINE = 0x305B; public const int EGL_CORE_NATIVE_ENGINE = 0x305B;
public const int EGL_DEPTH_SIZE = 0x3025; public const int EGL_DEPTH_SIZE = 0x3025;
public const int EGL_DONT_CARE = -1; // public const int EGL_DONT_CARE = -1;
public const int EGL_DRAW = 0x3059; public const int EGL_DRAW = 0x3059;
public const int EGL_EXTENSIONS = 0x3055; public const int EGL_EXTENSIONS = 0x3055;
public const int EGL_FALSE = 0; public const int EGL_FALSE = 0;
public const int EGL_GREEN_SIZE = 0x3023; public const int EGL_GREEN_SIZE = 0x3023;
public const int EGL_HEIGHT = 0x3056; public const int EGL_HEIGHT = 0x3056;
public const int EGL_LARGEST_PBUFFER = 0x3058; // public const int EGL_LARGEST_PBUFFER = 0x3058;
public const int EGL_LEVEL = 0x3029; // public const int EGL_LEVEL = 0x3029;
public const int EGL_MAX_PBUFFER_HEIGHT = 0x302A; // public const int EGL_MAX_PBUFFER_HEIGHT = 0x302A;
public const int EGL_MAX_PBUFFER_PIXELS = 0x302B; // public const int EGL_MAX_PBUFFER_PIXELS = 0x302B;
public const int EGL_MAX_PBUFFER_WIDTH = 0x302C; // public const int EGL_MAX_PBUFFER_WIDTH = 0x302C;
public const int EGL_NATIVE_RENDERABLE = 0x302D; // public const int EGL_NATIVE_RENDERABLE = 0x302D;
public const int EGL_NATIVE_VISUAL_ID = 0x302E; // public const int EGL_NATIVE_VISUAL_ID = 0x302E;
public const int EGL_NATIVE_VISUAL_TYPE = 0x302F; // public const int EGL_NATIVE_VISUAL_TYPE = 0x302F;
public const int EGL_NONE = 0x3038; public const int EGL_NONE = 0x3038;
public const int EGL_NON_CONFORMANT_CONFIG = 0x3051; // public const int EGL_NON_CONFORMANT_CONFIG = 0x3051;
public const int EGL_NOT_INITIALIZED = 0x3001; public const int EGL_NOT_INITIALIZED = 0x3001;
public const int EGL_NO_CONTEXT = 0; public const int EGL_NO_CONTEXT = 0;
public const int EGL_NO_DISPLAY = 0; public const int EGL_NO_DISPLAY = 0;
public const int EGL_NO_SURFACE = 0; public const int EGL_NO_SURFACE = 0;
public const int EGL_PBUFFER_BIT = 0x0001; public const int EGL_PBUFFER_BIT = 0x0001;
public const int EGL_PIXMAP_BIT = 0x0002; // public const int EGL_PIXMAP_BIT = 0x0002;
public const int EGL_READ = 0x305A; public const int EGL_READ = 0x305A;
public const int EGL_RED_SIZE = 0x3024; public const int EGL_RED_SIZE = 0x3024;
public const int EGL_SAMPLES = 0x3031; public const int EGL_SAMPLES = 0x3031;
public const int EGL_SAMPLE_BUFFERS = 0x3032; // public const int EGL_SAMPLE_BUFFERS = 0x3032;
public const int EGL_SLOW_CONFIG = 0x3050; // public const int EGL_SLOW_CONFIG = 0x3050;
public const int EGL_STENCIL_SIZE = 0x3026; public const int EGL_STENCIL_SIZE = 0x3026;
public const int EGL_SUCCESS = 0x3000; public const int EGL_SUCCESS = 0x3000;
public const int EGL_SURFACE_TYPE = 0x3033; public const int EGL_SURFACE_TYPE = 0x3033;
public const int EGL_TRANSPARENT_BLUE_VALUE = 0x3035; // public const int EGL_TRANSPARENT_BLUE_VALUE = 0x3035;
public const int EGL_TRANSPARENT_GREEN_VALUE = 0x3036; // public const int EGL_TRANSPARENT_GREEN_VALUE = 0x3036;
public const int EGL_TRANSPARENT_RED_VALUE = 0x3037; // public const int EGL_TRANSPARENT_RED_VALUE = 0x3037;
public const int EGL_TRANSPARENT_RGB = 0x3052; // public const int EGL_TRANSPARENT_RGB = 0x3052;
public const int EGL_TRANSPARENT_TYPE = 0x3034; // public const int EGL_TRANSPARENT_TYPE = 0x3034;
public const int EGL_TRUE = 1; public const int EGL_TRUE = 1;
public const int EGL_VENDOR = 0x3053; // public const int EGL_VENDOR = 0x3053;
public const int EGL_VERSION = 0x3054; // public const int EGL_VERSION = 0x3054;
public const int EGL_WIDTH = 0x3057; public const int EGL_WIDTH = 0x3057;
public const int EGL_WINDOW_BIT = 0x0004; public const int EGL_WINDOW_BIT = 0x0004;
public const int EGL_BACK_BUFFER = 0x3084; // public const int EGL_BACK_BUFFER = 0x3084;
public const int EGL_BIND_TO_TEXTURE_RGB = 0x3039; // public const int EGL_BIND_TO_TEXTURE_RGB = 0x3039;
public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A; // public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A;
public const int EGL_CONTEXT_LOST = 0x300E; public const int EGL_CONTEXT_LOST = 0x300E;
public const int EGL_MIN_SWAP_INTERVAL = 0x303B; // public const int EGL_MIN_SWAP_INTERVAL = 0x303B;
public const int EGL_MAX_SWAP_INTERVAL = 0x303C; // public const int EGL_MAX_SWAP_INTERVAL = 0x303C;
public const int EGL_MIPMAP_TEXTURE = 0x3082; // public const int EGL_MIPMAP_TEXTURE = 0x3082;
public const int EGL_MIPMAP_LEVEL = 0x3083; // public const int EGL_MIPMAP_LEVEL = 0x3083;
public const int EGL_NO_TEXTURE = 0x305C; // public const int EGL_NO_TEXTURE = 0x305C;
public const int EGL_TEXTURE_2D = 0x305F; // public const int EGL_TEXTURE_2D = 0x305F;
public const int EGL_TEXTURE_FORMAT = 0x3080; // public const int EGL_TEXTURE_FORMAT = 0x3080;
public const int EGL_TEXTURE_RGB = 0x305D; // public const int EGL_TEXTURE_RGB = 0x305D;
public const int EGL_TEXTURE_RGBA = 0x305E; // public const int EGL_TEXTURE_RGBA = 0x305E;
public const int EGL_TEXTURE_TARGET = 0x3081; // public const int EGL_TEXTURE_TARGET = 0x3081;
public const int EGL_ALPHA_FORMAT = 0x3088; // public const int EGL_ALPHA_FORMAT = 0x3088;
public const int EGL_ALPHA_FORMAT_NONPRE = 0x308B; // public const int EGL_ALPHA_FORMAT_NONPRE = 0x308B;
public const int EGL_ALPHA_FORMAT_PRE = 0x308C; // public const int EGL_ALPHA_FORMAT_PRE = 0x308C;
public const int EGL_ALPHA_MASK_SIZE = 0x303E; // public const int EGL_ALPHA_MASK_SIZE = 0x303E;
public const int EGL_BUFFER_PRESERVED = 0x3094; // public const int EGL_BUFFER_PRESERVED = 0x3094;
public const int EGL_BUFFER_DESTROYED = 0x3095; // public const int EGL_BUFFER_DESTROYED = 0x3095;
public const int EGL_CLIENT_APIS = 0x308D; // public const int EGL_CLIENT_APIS = 0x308D;
public const int EGL_COLORSPACE = 0x3087; // public const int EGL_COLORSPACE = 0x3087;
public const int EGL_COLORSPACE_sRGB = 0x3089; // public const int EGL_COLORSPACE_sRGB = 0x3089;
public const int EGL_COLORSPACE_LINEAR = 0x308A; // public const int EGL_COLORSPACE_LINEAR = 0x308A;
public const int EGL_COLOR_BUFFER_TYPE = 0x303F; // public const int EGL_COLOR_BUFFER_TYPE = 0x303F;
public const int EGL_CONTEXT_CLIENT_TYPE = 0x3097; // public const int EGL_CONTEXT_CLIENT_TYPE = 0x3097;
public const int EGL_DISPLAY_SCALING = 10000; // public const int EGL_DISPLAY_SCALING = 10000;
public const int EGL_HORIZONTAL_RESOLUTION = 0x3090; // public const int EGL_HORIZONTAL_RESOLUTION = 0x3090;
public const int EGL_LUMINANCE_BUFFER = 0x308F; // public const int EGL_LUMINANCE_BUFFER = 0x308F;
public const int EGL_LUMINANCE_SIZE = 0x303D; // public const int EGL_LUMINANCE_SIZE = 0x303D;
public const int EGL_OPENGL_ES_BIT = 0x0001; public const int EGL_OPENGL_ES_BIT = 0x0001;
public const int EGL_OPENVG_BIT = 0x0002; // public const int EGL_OPENVG_BIT = 0x0002;
public const int EGL_OPENGL_ES_API = 0x30A0; public const int EGL_OPENGL_ES_API = 0x30A0;
public const int EGL_OPENVG_API = 0x30A1; // public const int EGL_OPENVG_API = 0x30A1;
public const int EGL_OPENVG_IMAGE = 0x3096; // public const int EGL_OPENVG_IMAGE = 0x3096;
public const int EGL_PIXEL_ASPECT_RATIO = 0x3092; // public const int EGL_PIXEL_ASPECT_RATIO = 0x3092;
public const int EGL_RENDERABLE_TYPE = 0x3040; public const int EGL_RENDERABLE_TYPE = 0x3040;
public const int EGL_RENDER_BUFFER = 0x3086; // public const int EGL_RENDER_BUFFER = 0x3086;
public const int EGL_RGB_BUFFER = 0x308E; // public const int EGL_RGB_BUFFER = 0x308E;
public const int EGL_SINGLE_BUFFER = 0x3085; // public const int EGL_SINGLE_BUFFER = 0x3085;
public const int EGL_SWAP_BEHAVIOR = 0x3093; // public const int EGL_SWAP_BEHAVIOR = 0x3093;
public const int EGL_UNKNOWN = -1; // public const int EGL_UNKNOWN = -1;
public const int EGL_VERTICAL_RESOLUTION = 0x3091; // public const int EGL_VERTICAL_RESOLUTION = 0x3091;
public const int EGL_CONFORMANT = 0x3042; // public const int EGL_CONFORMANT = 0x3042;
public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
public const int EGL_MATCH_NATIVE_PIXMAP = 0x3041; // public const int EGL_MATCH_NATIVE_PIXMAP = 0x3041;
public const int EGL_OPENGL_ES2_BIT = 0x0004; public const int EGL_OPENGL_ES2_BIT = 0x0004;
public const int EGL_VG_ALPHA_FORMAT = 0x3088; // public const int EGL_VG_ALPHA_FORMAT = 0x3088;
public const int EGL_VG_ALPHA_FORMAT_NONPRE = 0x308B; // public const int EGL_VG_ALPHA_FORMAT_NONPRE = 0x308B;
public const int EGL_VG_ALPHA_FORMAT_PRE = 0x308C; // public const int EGL_VG_ALPHA_FORMAT_PRE = 0x308C;
public const int EGL_VG_ALPHA_FORMAT_PRE_BIT = 0x0040; // public const int EGL_VG_ALPHA_FORMAT_PRE_BIT = 0x0040;
public const int EGL_VG_COLORSPACE = 0x3087; // public const int EGL_VG_COLORSPACE = 0x3087;
public const int EGL_VG_COLORSPACE_sRGB = 0x3089; // public const int EGL_VG_COLORSPACE_sRGB = 0x3089;
public const int EGL_VG_COLORSPACE_LINEAR = 0x308A; // public const int EGL_VG_COLORSPACE_LINEAR = 0x308A;
public const int EGL_VG_COLORSPACE_LINEAR_BIT = 0x0020; // public const int EGL_VG_COLORSPACE_LINEAR_BIT = 0x0020;
public const int EGL_DEFAULT_DISPLAY = 0; // public const int EGL_DEFAULT_DISPLAY = 0;
public const int EGL_MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200; // public const int EGL_MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200;
public const int EGL_MULTISAMPLE_RESOLVE = 0x3099; // public const int EGL_MULTISAMPLE_RESOLVE = 0x3099;
public const int EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A; // public const int EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A;
public const int EGL_MULTISAMPLE_RESOLVE_BOX = 0x309B; // public const int EGL_MULTISAMPLE_RESOLVE_BOX = 0x309B;
public const int EGL_OPENGL_API = 0x30A2; // public const int EGL_OPENGL_API = 0x30A2;
public const int EGL_OPENGL_BIT = 0x0008; // public const int EGL_OPENGL_BIT = 0x0008;
public const int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; // public const int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
public const int EGL_CONTEXT_MAJOR_VERSION = 0x3098; public const int EGL_CONTEXT_MAJOR_VERSION = 0x3098;
public const int EGL_CONTEXT_MINOR_VERSION = 0x30FB; public const int EGL_CONTEXT_MINOR_VERSION = 0x30FB;
public const int EGL_CONTEXT_OPENGL_PROFILE_MASK = 0x30FD; // public const int EGL_CONTEXT_OPENGL_PROFILE_MASK = 0x30FD;
public const int EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY = 0x31BD; // public const int EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY = 0x31BD;
public const int EGL_NO_RESET_NOTIFICATION = 0x31BE; // public const int EGL_NO_RESET_NOTIFICATION = 0x31BE;
public const int EGL_LOSE_CONTEXT_ON_RESET = 0x31BF; // public const int EGL_LOSE_CONTEXT_ON_RESET = 0x31BF;
public const int EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT = 0x00000001; // public const int EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT = 0x00000001;
public const int EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT = 0x00000002; // public const int EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT = 0x00000002;
public const int EGL_CONTEXT_OPENGL_DEBUG = 0x31B0; // public const int EGL_CONTEXT_OPENGL_DEBUG = 0x31B0;
public const int EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE = 0x31B1; // public const int EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE = 0x31B1;
public const int EGL_CONTEXT_OPENGL_ROBUST_ACCESS = 0x31B2; // public const int EGL_CONTEXT_OPENGL_ROBUST_ACCESS = 0x31B2;
public const int EGL_OPENGL_ES3_BIT = 0x00000040; public const int EGL_OPENGL_ES3_BIT = 0x00000040;
public const int EGL_CL_EVENT_HANDLE = 0x309C; // public const int EGL_CL_EVENT_HANDLE = 0x309C;
public const int EGL_SYNC_CL_EVENT = 0x30FE; // public const int EGL_SYNC_CL_EVENT = 0x30FE;
public const int EGL_SYNC_CL_EVENT_COMPLETE = 0x30FF; // public const int EGL_SYNC_CL_EVENT_COMPLETE = 0x30FF;
public const int EGL_SYNC_PRIOR_COMMANDS_COMPLETE = 0x30F0; // public const int EGL_SYNC_PRIOR_COMMANDS_COMPLETE = 0x30F0;
public const int EGL_SYNC_TYPE = 0x30F7; // public const int EGL_SYNC_TYPE = 0x30F7;
public const int EGL_SYNC_STATUS = 0x30F1; // public const int EGL_SYNC_STATUS = 0x30F1;
public const int EGL_SYNC_CONDITION = 0x30F8; // public const int EGL_SYNC_CONDITION = 0x30F8;
public const int EGL_SIGNALED = 0x30F2; // public const int EGL_SIGNALED = 0x30F2;
public const int EGL_UNSIGNALED = 0x30F3; // public const int EGL_UNSIGNALED = 0x30F3;
public const int EGL_SYNC_FLUSH_COMMANDS_BIT = 0x0001; // public const int EGL_SYNC_FLUSH_COMMANDS_BIT = 0x0001;
public const int EGL_TIMEOUT_EXPIRED = 0x30F5; // public const int EGL_TIMEOUT_EXPIRED = 0x30F5;
public const int EGL_CONDITION_SATISFIED = 0x30F6; // public const int EGL_CONDITION_SATISFIED = 0x30F6;
public const int EGL_NO_SYNC = 0; // public const int EGL_NO_SYNC = 0;
public const int EGL_SYNC_FENCE = 0x30F9; // public const int EGL_SYNC_FENCE = 0x30F9;
public const int EGL_GL_COLORSPACE = 0x309D; // public const int EGL_GL_COLORSPACE = 0x309D;
public const int EGL_GL_COLORSPACE_SRGB = 0x3089; // public const int EGL_GL_COLORSPACE_SRGB = 0x3089;
public const int EGL_GL_COLORSPACE_LINEAR = 0x308A; // public const int EGL_GL_COLORSPACE_LINEAR = 0x308A;
public const int EGL_GL_RENDERBUFFER = 0x30B9; // public const int EGL_GL_RENDERBUFFER = 0x30B9;
public const int EGL_GL_TEXTURE_2D = 0x30B1; // public const int EGL_GL_TEXTURE_2D = 0x30B1;
public const int EGL_GL_TEXTURE_LEVEL = 0x30BC; // public const int EGL_GL_TEXTURE_LEVEL = 0x30BC;
public const int EGL_GL_TEXTURE_3D = 0x30B2; // public const int EGL_GL_TEXTURE_3D = 0x30B2;
public const int EGL_GL_TEXTURE_ZOFFSET = 0x30BD; // public const int EGL_GL_TEXTURE_ZOFFSET = 0x30BD;
public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x30B3; // public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x30B3;
public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x30B4; // public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x30B4;
public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x30B5; // public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x30B5;
public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x30B6; // public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x30B6;
public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x30B7; // public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x30B7;
public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x30B8; // public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x30B8;
public const int EGL_IMAGE_PRESERVED = 0x30D2; // public const int EGL_IMAGE_PRESERVED = 0x30D2;
public const int EGL_NO_IMAGE = 0; // public const int EGL_NO_IMAGE = 0;
public const int EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207; public const int EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207;
public const int EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208; public const int EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208;
//EGL_ANGLE_platform_angle //EGL_ANGLE_platform_angle
public const int EGL_PLATFORM_ANGLE_ANGLE = 0x3202; public const int EGL_PLATFORM_ANGLE_ANGLE = 0x3202;
public const int EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203; public const int EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203;
public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204; // public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204;
public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205; // public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205;
public const int EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED = 0x3451; // public const int EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED = 0x3451;
public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206; // public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D; // public const int EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E; // public const int EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E;
//EGL_ANGLE_platform_angle_d3d //EGL_ANGLE_platform_angle_d3d
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209;
public const int EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F; // public const int EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C;
//EXT_device_query //EXT_device_query
public const int EGL_DEVICE_EXT = 0x322C; public const int EGL_DEVICE_EXT = 0x322C;
//ANGLE_device_d3d //ANGLE_device_d3d
public const int EGL_D3D9_DEVICE_ANGLE = 0x33A0; public const int EGL_D3D9_DEVICE_ANGLE = 0x33A0;
public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1; public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1;
public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200; public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200;
public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3; public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3;
public const int EGL_TEXTURE_OFFSET_X_ANGLE = 0x3490; public const int EGL_TEXTURE_OFFSET_X_ANGLE = 0x3490;
public const int EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491; public const int EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491;
public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6; public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6;
} }
} }

2
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -24,7 +24,7 @@ namespace Avalonia.OpenGL.Egl
SampleCount = sampleCount; SampleCount = sampleCount;
StencilSize = stencilSize; StencilSize = stencilSize;
using (MakeCurrent()) using (MakeCurrent())
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, b => _egl.GetProcAddress(b)); GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
} }
public IntPtr Context { get; } public IntPtr Context { get; }

4
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@ -34,9 +34,9 @@ namespace Avalonia.OpenGL.Egl
} }
else else
{ {
if (egl.GetPlatformDisplayEXT == null) if (!egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs); display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs);
} }
if (display == IntPtr.Zero) if (display == IntPtr.Zero)

225
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -2,31 +2,26 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Interop; using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL.Egl namespace Avalonia.OpenGL.Egl
{ {
public class EglInterface : GlInterfaceBase public unsafe partial class EglInterface
{ {
public EglInterface() : base(Load()) public EglInterface(Func<string, IntPtr> getProcAddress)
{ {
Initialize(getProcAddress);
}
public EglInterface(Func<Utf8Buffer,IntPtr> getProcAddress) : base(getProcAddress)
{
} }
public EglInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress) public EglInterface(string library) : this(Load(library))
{ {
} }
public EglInterface(string library) : base(Load(library)) public EglInterface() : this(Load())
{ {
} }
static Func<string, IntPtr> Load() static Func<string, IntPtr> Load()
{ {
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem; var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
@ -46,119 +41,75 @@ namespace Avalonia.OpenGL.Egl
} }
// ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable UnassignedGetOnlyAutoProperty
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int EglGetError(); [GetProcAddress("eglGetError")]
[GlEntryPoint("eglGetError")] public partial int GetError();
public EglGetError GetError { get; }
[GetProcAddress("eglGetDisplay")]
[UnmanagedFunctionPointer(CallingConvention.StdCall)] public partial IntPtr GetDisplay(IntPtr nativeDisplay);
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
[GlEntryPoint("eglGetDisplay")] [GetProcAddress("eglGetPlatformDisplayEXT", true)]
public EglGetDisplay GetDisplay { get; } public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs);
[UnmanagedFunctionPointer(CallingConvention.StdCall)] [GetProcAddress("eglInitialize")]
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs); public partial bool Initialize(IntPtr display, out int major, out int minor);
[GlEntryPoint("eglGetPlatformDisplayEXT")]
[GlOptionalEntryPoint] [GetProcAddress("eglGetProcAddress")]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; } public partial IntPtr GetProcAddress(IntPtr proc);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
[GlEntryPoint("eglInitialize")]
public EglInitialize Initialize { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
[GlEntryPoint("eglGetProcAddress")]
public EglGetProcAddress GetProcAddress { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglBindApi(int api);
[GlEntryPoint("eglBindAPI")]
public EglBindApi BindApi { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[GlEntryPoint("eglChooseConfig")]
public EglChooseConfig ChooseConfig { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)] [GetProcAddress("eglBindAPI")]
public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config, public partial bool BindApi(int api);
[GetProcAddress("eglChooseConfig")]
public partial bool ChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[GetProcAddress("eglCreateContext")]
public partial IntPtr CreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs); IntPtr share, int[] attrs);
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; } [GetProcAddress("eglDestroyContext")]
public partial bool DestroyContext(IntPtr display, IntPtr context);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglDestroyContext(IntPtr display, IntPtr context); [GetProcAddress("eglCreatePbufferSurface")]
[GlEntryPoint("eglDestroyContext")] public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
public EglDestroyContext DestroyContext { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[GlEntryPoint("eglCreatePbufferSurface")]
public EglCreatePBufferSurface CreatePBufferSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentContext();
[GlEntryPoint("eglGetCurrentContext")]
public EglGetCurrentContext GetCurrentContext { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentDisplay();
[GlEntryPoint("eglGetCurrentDisplay")]
public EglGetCurrentContext GetCurrentDisplay { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentSurface(int readDraw);
[GlEntryPoint("eglGetCurrentSurface")]
public EglGetCurrentSurface GetCurrentSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
[GlEntryPoint("eglSwapBuffers")]
public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr
EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[GlEntryPoint("eglCreateWindowSurface")]
public EglCreateWindowSurface CreateWindowSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitGL();
[GlEntryPoint("eglWaitGL")]
public EglWaitGL WaitGL { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitClient();
[GlEntryPoint("eglWaitClient")]
public EglWaitGL WaitClient { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitNative(int engine);
[GlEntryPoint("eglWaitNative")]
public EglWaitNative WaitNative { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglQueryString(IntPtr display, int i);
[GlEntryPoint("eglQueryString")]
public EglQueryString QueryStringNative { get; }
[GetProcAddress("eglMakeCurrent")]
public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GetProcAddress("eglGetCurrentContext")]
public partial IntPtr GetCurrentContext();
[GetProcAddress("eglGetCurrentDisplay")]
public partial IntPtr GetCurrentDisplay();
[GetProcAddress("eglGetCurrentSurface")]
public partial IntPtr GetCurrentSurface(int readDraw);
[GetProcAddress("eglDestroySurface")]
public partial void DestroySurface(IntPtr display, IntPtr surface);
[GetProcAddress("eglSwapBuffers")]
public partial void SwapBuffers(IntPtr display, IntPtr surface);
[GetProcAddress("eglCreateWindowSurface")]
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[GetProcAddress("eglGetConfigAttrib")]
public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[GetProcAddress("eglWaitGL")]
public partial bool WaitGL();
[GetProcAddress("eglWaitClient")]
public partial bool WaitClient();
[GetProcAddress("eglWaitNative")]
public partial bool WaitNative(int engine);
[GetProcAddress("eglQueryString")]
public partial IntPtr QueryStringNative(IntPtr display, int i);
public string QueryString(IntPtr display, int i) public string QueryString(IntPtr display, int i)
{ {
var rv = QueryStringNative(display, i); var rv = QueryStringNative(display, i);
@ -166,25 +117,15 @@ namespace Avalonia.OpenGL.Egl
return null; return null;
return Marshal.PtrToStringAnsi(rv); return Marshal.PtrToStringAnsi(rv);
} }
[GetProcAddress("eglCreatePbufferFromClientBuffer")]
public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
[GetProcAddress("eglQueryDisplayAttribEXT", true)]
public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list); [GetProcAddress("eglQueryDeviceAttribEXT", true)]
[GlEntryPoint("eglCreatePbufferFromClientBuffer")] public partial bool QueryDeviceAttribExt(IntPtr display, int attr, out IntPtr res);
public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
} }
} }

44
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@ -3,46 +3,24 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Platform.Interop; using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL namespace Avalonia.OpenGL
{ {
public class GlBasicInfoInterface : GlBasicInfoInterface<object> public unsafe partial class GlBasicInfoInterface
{ {
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null) public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress){
{ Initialize(getProcAddress);
}
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
{
} }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetIntegerv(int name, out int rv);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr GlGetString(int v);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr GlGetStringi(int v, int v1);
}
public class GlBasicInfoInterface<TContextInfo> : GlInterfaceBase<TContextInfo> [GetProcAddress("glGetIntegerv")]
{ public partial void GetIntegerv(int name, out int rv);
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress, TContextInfo context) : base(getProcAddress, context)
{
}
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContextInfo context) : base(nativeGetProcAddress, context) [GetProcAddress("glGetString")]
{ public partial IntPtr GetStringNative(int v);
}
[GetProcAddress("glGetStringi")]
[GlEntryPoint("glGetIntegerv")] public partial IntPtr GetStringiNative(int v, int v1);
public GlBasicInfoInterface.GlGetIntegerv GetIntegerv { get; }
[GlEntryPoint("glGetString")]
public GlBasicInfoInterface.GlGetString GetStringNative { get; }
[GlEntryPoint("glGetStringi")]
public GlBasicInfoInterface.GlGetStringi GetStringiNative { get; }
public string GetString(int v) public string GetString(int v)
{ {

10783
src/Avalonia.OpenGL/GlConsts.cs

File diff suppressed because it is too large

103
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,117 +2,54 @@ using System;
namespace Avalonia.OpenGL namespace Avalonia.OpenGL
{ {
public interface IGlEntryPointAttribute [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class GlMinVersionEntryPoint : Attribute
{ {
IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress); public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor)
}
public interface IGlEntryPointAttribute<in TContext>
{
IntPtr GetProcAddress(TContext context, Func<string, IntPtr> getProcAddress);
}
[AttributeUsage(AttributeTargets.Property)]
public class GlOptionalEntryPoint : Attribute
{
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlEntryPointAttribute : Attribute, IGlEntryPointAttribute
{
public string[] EntryPoints { get; }
public GlEntryPointAttribute(string entryPoint)
{
EntryPoints = new []{entryPoint};
}
/*
public GlEntryPointAttribute(params string[] entryPoints)
{ {
EntryPoints = entryPoints;
}
*/
public IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress)
{
foreach (var name in EntryPoints)
{
var rv = getProcAddress(name);
if (rv != IntPtr.Zero)
return rv;
}
return IntPtr.Zero;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlMinVersionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
{
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly int _minVersionMajor;
private readonly int _minVersionMinor;
public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor,
int minVersionMinor)
{
_entry = entry;
_profile = profile;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
} }
public GlMinVersionEntryPoint(string entry, int minVersionMajor, public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor, GlProfileType profile)
int minVersionMinor)
{ {
_entry = entry;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
} }
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress) public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
string entry, int minVersionMajor, int minVersionMinor, GlProfileType? profile = null)
{ {
if(_profile.HasValue && context.Version.Type != _profile) if(profile.HasValue && context.Version.Type != profile)
return IntPtr.Zero; return IntPtr.Zero;
if(context.Version.Major<_minVersionMajor) if(context.Version.Major<minVersionMajor)
return IntPtr.Zero; return IntPtr.Zero;
if (context.Version.Major == _minVersionMajor && context.Version.Minor < _minVersionMinor) if (context.Version.Major == minVersionMajor && context.Version.Minor < minVersionMinor)
return IntPtr.Zero; return IntPtr.Zero;
return getProcAddress(_entry); return getProcAddress(entry);
} }
} }
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class GlExtensionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo> class GlExtensionEntryPoint : Attribute
{ {
private readonly string _entry; public GlExtensionEntryPoint(string entry, string extension)
private readonly GlProfileType? _profile;
private readonly string _extension;
public GlExtensionEntryPoint(string entry, GlProfileType profile, string extension)
{ {
_entry = entry;
_profile = profile;
_extension = extension;
} }
public GlExtensionEntryPoint(string entry, string extension) public GlExtensionEntryPoint(string entry, string extension, GlProfileType profile)
{ {
_entry = entry;
_extension = extension;
} }
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress) public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
string entry, string extension, GlProfileType? profile = null)
{ {
// Ignore different profile type // Ignore different profile type
if (_profile.HasValue && _profile != context.Version.Type) if (profile.HasValue && profile != context.Version.Type)
return IntPtr.Zero; return IntPtr.Zero;
// Check if extension is supported by the current context // Check if extension is supported by the current context
if (!context.Extensions.Contains(_extension)) if (!context.Extensions.Contains(extension))
return IntPtr.Zero; return IntPtr.Zero;
return getProcAddress(_entry); return getProcAddress(entry);
} }
} }
} }

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

Loading…
Cancel
Save