Browse Source

Merge branch 'master' into button-flyout-diagnostics

pull/7902/head
Jumar Macato 4 years ago
committed by GitHub
parent
commit
d4449a0cb7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/ControlCatalog/Pages/PointersPage.cs
  2. 3
      samples/RenderDemo/MainWindow.xaml
  3. 7
      samples/RenderDemo/Pages/TextFormatterPage.axaml
  4. 118
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  5. 4
      samples/RenderDemo/RenderDemo.csproj
  6. 18
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  7. 26
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  8. 2
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  9. 2
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  10. 17
      src/Avalonia.Controls/ApiCompatBaseline.txt
  11. 24
      src/Avalonia.Controls/Control.cs
  12. 167
      src/Avalonia.Controls/Documents/TextElement.cs
  13. 69
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  14. 26
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  15. 201
      src/Avalonia.Controls/TextBlock.cs
  16. 111
      src/Avalonia.Controls/TextBox.cs
  17. 1
      src/Avalonia.Controls/Window.cs
  18. 24
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json
  19. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml
  20. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
  21. 7
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  22. 2
      src/Avalonia.Themes.Default/Controls/Button.xaml
  23. 2
      src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml
  24. 2
      src/Avalonia.Themes.Default/Controls/CheckBox.xaml
  25. 18
      src/Avalonia.Themes.Default/Controls/DatePicker.xaml
  26. 2
      src/Avalonia.Themes.Default/Controls/RepeatButton.xaml
  27. 22
      src/Avalonia.Themes.Default/Controls/SplitButton.xaml
  28. 12
      src/Avalonia.Themes.Default/Controls/TimePicker.xaml
  29. 2
      src/Avalonia.Themes.Default/Controls/ToggleButton.xaml
  30. 14
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  31. 6
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  32. 2
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  33. 18
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  34. 14
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  35. 18
      src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml
  36. 2
      src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml
  37. 18
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  38. 2
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  39. 2
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  40. 14
      src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml
  41. 6
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  42. 8
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  43. 6
      src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
  44. 6
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml
  45. 22
      src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml
  46. 18
      src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
  47. 18
      src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
  48. 12
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  49. 22
      src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
  50. 14
      src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
  51. 2
      src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml
  52. 17
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  53. 16
      src/Avalonia.Visuals/Media/FormattedText.cs
  54. 10
      src/Avalonia.Visuals/Media/TextDecorationCollection.cs
  55. 5
      src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
  56. 27
      src/Avalonia.Visuals/Media/TextFormatting/FormattedTextSource.cs
  57. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  58. 29
      src/Avalonia.Visuals/Media/TextFormatting/TextBounds.cs
  59. 25
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  60. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
  61. 109
      src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs
  62. 291
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  63. 204
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  64. 127
      src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  65. 18
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  66. 8
      src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs
  67. 937
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  68. 8
      src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
  69. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
  70. 5
      src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs
  71. 49
      src/Avalonia.Visuals/Media/TextFormatting/TextShaperOptions.cs
  72. 8
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
  73. 8
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
  74. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
  75. 13
      src/Avalonia.Visuals/Platform/ITextShaperImpl.cs
  76. 1
      src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs
  77. 19
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  78. 17
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  79. 29
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  80. 42
      tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json
  81. 9
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  82. 9
      tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
  83. 7
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
  84. 7
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
  85. 124
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  86. 18
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  87. 221
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  88. 24
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs
  89. 9
      tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
  90. 11
      tests/Avalonia.UnitTests/MockTextShaperImpl.cs

3
samples/ControlCatalog/Pages/PointersPage.cs

@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
@ -131,7 +132,7 @@ public class PointersPage : Decorator
{
public PointerIntermediatePointsTab()
{
this[TextBlock.ForegroundProperty] = Brushes.Black;
this[TextElement.ForegroundProperty] = Brushes.Black;
var slider = new Slider
{
Margin = new Thickness(5),

3
samples/RenderDemo/MainWindow.xaml

@ -60,6 +60,9 @@
<TabItem Header="FormattedText">
<pages:FormattedTextPage />
</TabItem>
<TabItem Header="TextFormatter">
<pages:TextFormatterPage />
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>

7
samples/RenderDemo/Pages/TextFormatterPage.axaml

@ -0,0 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.TextFormatterPage">
</UserControl>

118
samples/RenderDemo/Pages/TextFormatterPage.axaml.cs

@ -0,0 +1,118 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace RenderDemo.Pages
{
public class TextFormatterPage : UserControl
{
private TextLine _textLine;
public TextFormatterPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public override void Render(DrawingContext context)
{
_textLine?.Draw(context, new Point());
}
protected override Size MeasureOverride(Size availableSize)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black,
baselineAlignment: BaselineAlignment.Center);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
var control = new Button { Content = new TextBlock { Text = "ClickMe" } };
Content = control;
var textSource = new CustomTextSource(control, defaultRunProperties);
control.Measure(Size.Infinity);
_textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
var currentX = 0d;
foreach (var textRun in _textLine.TextRuns)
{
if (textRun is ControlRun controlRun)
{
controlRun.Control.Arrange(new Rect(new Point(currentX, 0), controlRun.Size));
}
if (textRun is DrawableTextRun drawableTextRun)
{
currentX += drawableTextRun.Size.Width;
}
}
return finalSize;
}
private class CustomTextSource : ITextSource
{
private readonly Control _control;
private readonly TextRunProperties _defaultProperties;
private readonly string _text = "<-Hello World->";
public CustomTextSource(Control control, TextRunProperties defaultProperties)
{
_control = control;
_defaultProperties = defaultProperties;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength)
{
return null;
}
if (textSourceIndex == _text.Length)
{
return new ControlRun(_control, _defaultProperties);
}
return new TextCharacters(_text.AsMemory(), _defaultProperties);
}
}
private class ControlRun : DrawableTextRun
{
private readonly Control _control;
public ControlRun(Control control, TextRunProperties properties)
{
_control = control;
Properties = properties;
}
public Control Control => _control;
public override Size Size => _control.DesiredSize;
public override double Baseline => 0;
public override TextRunProperties? Properties { get; }
public override void Draw(DrawingContext drawingContext, Point origin)
{
// noop
}
}
}
}

4
samples/RenderDemo/RenderDemo.csproj

@ -5,6 +5,10 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" />
<Compile Update="Pages\TextFormatterPage.axaml.cs">
<DependentUpon>TextFormatterPage.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

18
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -195,9 +195,9 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</Panel>
</Border>
</ControlTemplate>
@ -216,25 +216,25 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
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="TextBlock.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
<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="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
<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="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<Style Selector=":is(Button).NavigationButton:pressed">
<Setter Property="RenderTransform" Value="none" />

26
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@ -10,6 +10,7 @@ using System;
using System.ComponentModel;
using Avalonia.Layout;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Controls.Documents;
namespace Avalonia.Controls
{
@ -32,7 +33,7 @@ namespace Avalonia.Controls
/// Identifies the FontFamily dependency property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<DataGridTextColumn>();
TextElement.FontFamilyProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font name.
@ -47,7 +48,7 @@ namespace Avalonia.Controls
/// Identifies the FontSize dependency property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<DataGridTextColumn>();
TextElement.FontSizeProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font size.
@ -64,7 +65,7 @@ namespace Avalonia.Controls
/// Identifies the FontStyle dependency property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<DataGridTextColumn>();
TextElement.FontStyleProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font style.
@ -79,7 +80,7 @@ namespace Avalonia.Controls
/// Identifies the FontWeight dependency property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<DataGridTextColumn>();
TextElement.FontWeightProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font weight or thickness.
@ -90,11 +91,26 @@ namespace Avalonia.Controls
set => SetValue(FontWeightProperty, value);
}
/// <summary>
/// Identifies the FontStretch dependency property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font weight or thickness.
/// </summary>
public FontStretch FontStretch
{
get => GetValue(FontStretchProperty);
set => SetValue(FontStretchProperty, value);
}
/// <summary>
/// Identifies the Foreground dependency property.
/// </summary>
public static readonly AttachedProperty<IBrush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<DataGridTextColumn>();
TextElement.ForegroundProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets a brush that describes the foreground of the column cells.

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

@ -17,7 +17,7 @@
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

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

@ -124,7 +124,7 @@
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />

17
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -29,6 +29,21 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChang
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.Controls.TextBlock.FontFamilyProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.Controls.TextBlock.FontStyleProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.Controls.TextBlock.FontWeightProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.Controls.TextBlock.ForegroundProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<System.Double> Avalonia.AttachedProperty<System.Double> Avalonia.Controls.TextBlock.FontSizeProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontFamily Avalonia.Controls.TextBlock.GetFontFamily(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.TextBlock.GetFontSize(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontStyle Avalonia.Controls.TextBlock.GetFontStyle(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontWeight Avalonia.Controls.TextBlock.GetFontWeight(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.IBrush Avalonia.Controls.TextBlock.GetForeground(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontFamily(Avalonia.Controls.Control, Avalonia.Media.FontFamily)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontSize(Avalonia.Controls.Control, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontStyle(Avalonia.Controls.Control, Avalonia.Media.FontStyle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontWeight(Avalonia.Controls.Control, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetForeground(Avalonia.Controls.Control, Avalonia.Media.IBrush)' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Viewbox' does not inherit from base type 'Avalonia.Controls.Decorator' in the implementation but it does in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
@ -72,4 +87,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 73
Total Issues: 88

24
src/Avalonia.Controls/Control.cs

@ -1,7 +1,7 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
@ -67,7 +67,7 @@ namespace Avalonia.Controls
/// </summary>
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
private AutomationPeer? _automationPeer;
@ -137,6 +137,26 @@ namespace Avalonia.Controls
public new IControl? Parent => (IControl?)base.Parent;
/// <summary>
/// Gets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The flow direction.</returns>
public static FlowDirection GetFlowDirection(Control control)
{
return control.GetValue(FlowDirectionProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFlowDirection(Control control, FlowDirection value)
{
control.SetValue(FlowDirectionProperty, value);
}
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;

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

@ -13,37 +13,60 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<IBrush?> BackgroundProperty =
AvaloniaProperty.Register<TextElement, IBrush?>(nameof(Background), inherits: true);
Border.BackgroundProperty.AddOwner<TextElement>();
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, FontFamily>(
nameof(FontFamily),
defaultValue: FontFamily.Default,
inherits: true);
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, double>(
nameof(FontSize),
defaultValue: 12,
inherits: true);
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStyle>(
nameof(FontStyle),
inherits: true);
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, FontWeight>(
nameof(FontWeight),
inherits: true,
defaultValue: FontWeight.Normal);
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStretch>(
nameof(FontStretch),
inherits: true,
defaultValue: FontStretch.Normal);
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, IBrush?>(
nameof(Foreground),
Brushes.Black,
inherits: true);
/// <summary>
/// Gets or sets a brush used to paint the control's background.
@ -90,6 +113,15 @@ namespace Avalonia.Controls.Documents
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>
@ -98,7 +130,127 @@ namespace Avalonia.Controls.Documents
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontFamily GetFontFamily(Control control)
{
return control.GetValue(FontFamilyProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontFamily(Control control, FontFamily value)
{
control.SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font size.</returns>
public static double GetFontSize(Control control)
{
return control.GetValue(FontSizeProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontSize(Control control, double value)
{
control.SetValue(FontSizeProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font style.</returns>
public static FontStyle GetFontStyle(Control control)
{
return control.GetValue(FontStyleProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStyle(Control control, FontStyle value)
{
control.SetValue(FontStyleProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font weight.</returns>
public static FontWeight GetFontWeight(Control control)
{
return control.GetValue(FontWeightProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontWeight(Control control, FontWeight value)
{
control.SetValue(FontWeightProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font stretch.</returns>
public static FontStretch GetFontStretch(Control control)
{
return control.GetValue(FontStretchProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStretch(Control control, FontStretch value)
{
control.SetValue(FontStretchProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The foreground.</returns>
public static IBrush? GetForeground(Control control)
{
return control.GetValue(ForegroundProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetForeground(Control control, IBrush? value)
{
control.SetValue(ForegroundProperty, value);
}
/// <summary>
/// Raised when the visual representation of the text element changes.
/// </summary>
@ -115,6 +267,7 @@ namespace Avalonia.Controls.Documents
case nameof(FontSize):
case nameof(FontStyle):
case nameof(FontWeight):
case nameof(FontStretch):
case nameof(Foreground):
Invalidate();
break;

69
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Utilities;
using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
namespace Avalonia.Controls.Presenters
{
@ -121,8 +122,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public FontFamily FontFamily
{
get => TextBlock.GetFontFamily(this);
set => TextBlock.SetFontFamily(this, value);
get => TextElement.GetFontFamily(this);
set => TextElement.SetFontFamily(this, value);
}
/// <summary>
@ -130,8 +131,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public double FontSize
{
get => TextBlock.GetFontSize(this);
set => TextBlock.SetFontSize(this, value);
get => TextElement.GetFontSize(this);
set => TextElement.SetFontSize(this, value);
}
/// <summary>
@ -139,8 +140,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public FontStyle FontStyle
{
get => TextBlock.GetFontStyle(this);
set => TextBlock.SetFontStyle(this, value);
get => TextElement.GetFontStyle(this);
set => TextElement.SetFontStyle(this, value);
}
/// <summary>
@ -148,8 +149,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public FontWeight FontWeight
{
get => TextBlock.GetFontWeight(this);
set => TextBlock.SetFontWeight(this, value);
get => TextElement.GetFontWeight(this);
set => TextElement.SetFontWeight(this, value);
}
/// <summary>
@ -157,8 +158,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public FontStretch FontStretch
{
get => TextBlock.GetFontStretch(this);
set => TextBlock.SetFontStretch(this, value);
get => TextElement.GetFontStretch(this);
set => TextElement.SetFontStretch(this, value);
}
/// <summary>
@ -166,8 +167,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public IBrush? Foreground
{
get => TextBlock.GetForeground(this);
set => TextBlock.SetForeground(this, value);
get => TextElement.GetForeground(this);
set => TextElement.SetForeground(this, value);
}
/// <summary>
@ -353,7 +354,7 @@ namespace Avalonia.Controls.Presenters
foreach (var rect in rects)
{
context.FillRectangle(selectionBrush, rect);
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
}
}
@ -528,9 +529,9 @@ namespace Avalonia.Controls.Presenters
{
return finalSize;
}
_constraint = finalSize;
_constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height));
_textLayout = null;
return finalSize;
@ -637,14 +638,7 @@ namespace Avalonia.Controls.Presenters
if (Text is null)
{
return default;
}
if (FlowDirection == FlowDirection.RightToLeft)
{
direction = direction == LogicalDirection.Forward ?
LogicalDirection.Backward :
LogicalDirection.Forward;
}
}
var characterHit = _lastCharacterHit;
var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
@ -666,7 +660,7 @@ namespace Avalonia.Controls.Presenters
caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (textLine.NewLineLength > 0 && caretIndex == textLine.TextRange.Start + textLine.TextRange.Length)
if (textLine.NewLineLength > 0 && caretIndex == textLine.FirstTextSourceIndex + textLine.Length)
{
characterHit = new CharacterHit(caretIndex);
}
@ -678,7 +672,7 @@ namespace Avalonia.Controls.Presenters
break;
}
if (caretIndex - textLine.NewLineLength == textLine.TextRange.Start + textLine.TextRange.Length)
if (caretIndex - textLine.NewLineLength == textLine.FirstTextSourceIndex + textLine.Length)
{
break;
}
@ -719,6 +713,13 @@ namespace Avalonia.Controls.Presenters
public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward)
{
if (FlowDirection == FlowDirection.RightToLeft)
{
direction = direction == LogicalDirection.Forward ?
LogicalDirection.Backward :
LogicalDirection.Forward;
}
var characterHit = GetNextCharacterHit(direction);
UpdateCaret(characterHit);
@ -779,19 +780,25 @@ namespace Avalonia.Controls.Presenters
switch (change.Property.Name)
{
case nameof (TextBlock.Foreground):
case nameof (TextBlock.FontSize):
case nameof (TextBlock.FontStyle):
case nameof (TextBlock.FontWeight):
case nameof (TextBlock.FontFamily):
case nameof (Foreground):
case nameof (FontSize):
case nameof (FontStyle):
case nameof (FontWeight):
case nameof (FontFamily):
case nameof (FontStretch):
case nameof (Text):
case nameof (TextAlignment):
case nameof (TextWrapping):
case nameof (SelectionStart):
case nameof (SelectionEnd):
case nameof (SelectionForegroundBrush):
case nameof (PasswordChar):
case nameof (RevealPassword):
case nameof(FlowDirection):
{
InvalidateTextLayout();
break;

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

@ -1,4 +1,5 @@
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.Logging;
@ -41,31 +42,37 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TemplatedControl>();
TextElement.FontFamilyProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TemplatedControl>();
TextElement.FontSizeProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TemplatedControl>();
TextElement.FontStyleProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly StyledProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<TemplatedControl>();
TextElement.FontWeightProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly StyledProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TemplatedControl>();
TextElement.ForegroundProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="Padding"/> property.
@ -185,6 +192,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// Gets or sets the font stretch used to draw the control's text.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary>

201
src/Avalonia.Controls/TextBlock.cs

@ -28,61 +28,50 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Thickness> PaddingProperty =
Decorator.PaddingProperty.AddOwner<TextBlock>();
// TODO: Define these attached properties elsewhere (e.g. on a Text class) and AddOwner
// them into TextBlock.
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontFamily>(
nameof(FontFamily),
defaultValue: FontFamily.Default,
inherits: true);
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(FontSize),
defaultValue: 12,
inherits: true);
public static readonly StyledProperty<double> FontSizeProperty =
TextElement.FontSizeProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontStyle>(
nameof(FontStyle),
inherits: true);
public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextElement.FontStyleProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontWeight>(
nameof(FontWeight),
inherits: true,
defaultValue: FontWeight.Normal);
public static readonly StyledProperty<FontWeight> FontWeightProperty =
TextElement.FontWeightProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontStretch>(
nameof(FontStretch),
inherits: true,
defaultValue: FontStretch.Normal);
public static readonly StyledProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush?>(
nameof(Foreground),
Brushes.Black,
inherits: true);
public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<TextBlock>();
/// <summary>
/// DependencyProperty for <see cref="BaselineOffset" /> property.
/// </summary>
public static readonly AttachedProperty<double> BaselineOffsetProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(BaselineOffset),
0,
true);
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
@ -222,7 +211,7 @@ namespace Avalonia.Controls
public InlineCollection Inlines { get; }
/// <summary>
/// Gets or sets the font family.
/// Gets or sets the font family used to draw the control's text.
/// </summary>
public FontFamily FontFamily
{
@ -231,7 +220,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the font size.
/// Gets or sets the size of the control's text in points.
/// </summary>
public double FontSize
{
@ -240,7 +229,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the font style.
/// Gets or sets the font style used to draw the control's text.
/// </summary>
public FontStyle FontStyle
{
@ -249,16 +238,16 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the font weight.
/// Gets or sets the font weight used to draw the control's text.
/// </summary>
public FontWeight FontWeight
{
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// Gets or sets the font stretch.
/// Gets or sets the font stretch used to draw the control's text.
/// </summary>
public FontStretch FontStretch
{
@ -267,7 +256,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets a brush used to paint the text.
/// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary>
public IBrush? Foreground
{
@ -328,125 +317,43 @@ namespace Avalonia.Controls
get => GetValue(TextDecorationsProperty);
set => SetValue(TextDecorationsProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontFamily GetFontFamily(Control control)
{
return control.GetValue(FontFamilyProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font size.</returns>
public static double GetFontSize(Control control)
{
return control.GetValue(FontSizeProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font style.</returns>
public static FontStyle GetFontStyle(Control control)
{
return control.GetValue(FontStyleProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font weight.</returns>
public static FontWeight GetFontWeight(Control control)
{
return control.GetValue(FontWeightProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font stretch.</returns>
public static FontStretch GetFontStretch(Control control)
{
return control.GetValue(FontStretchProperty);
}
/// <summary>
/// Gets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// The BaselineOffset property provides an adjustment to baseline offset
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The foreground.</returns>
public static IBrush? GetForeground(Control control)
public double BaselineOffset
{
return control.GetValue(ForegroundProperty);
get { return (double)GetValue(BaselineOffsetProperty); }
set { SetValue(BaselineOffsetProperty, value); }
}
/// <summary>
/// Sets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontFamily(Control control, FontFamily value)
/// <param name="control">The element to which to read the attached property.</param>
public static double GetBaselineOffset(Control control)
{
control.SetValue(FontFamilyProperty, value);
}
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
/// <summary>
/// Sets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontSize(Control control, double value)
{
control.SetValue(FontSizeProperty, value);
return control.GetValue(BaselineOffsetProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStyle(Control control, FontStyle value)
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="value">The property value to set</param>
public static void SetBaselineOffset(Control control, double value)
{
control.SetValue(FontStyleProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontWeight(Control control, FontWeight value)
{
control.SetValue(FontWeightProperty, value);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStretch(Control control, FontStretch value)
{
control.SetValue(FontStretchProperty, value);
}
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
/// <summary>
/// Sets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetForeground(Control control, IBrush? value)
{
control.SetValue(ForegroundProperty, value);
control.SetValue(BaselineOffsetProperty, value);
}
/// <summary>
@ -562,7 +469,7 @@ namespace Avalonia.Controls
return finalSize;
}
_constraint = finalSize;
_constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height));
_textLayout = null;
@ -588,15 +495,19 @@ namespace Avalonia.Controls
case nameof (FontWeight):
case nameof (FontStyle):
case nameof (FontFamily):
case nameof (FontStretch):
case nameof (TextWrapping):
case nameof (TextTrimming):
case nameof (TextAlignment):
case nameof (FlowDirection):
case nameof (Padding):
case nameof (LineHeight):
case nameof (MaxLines):
case nameof (InlinesProperty):
case nameof (Text):
case nameof (TextDecorations):

111
src/Avalonia.Controls/TextBox.cs

@ -78,6 +78,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> MaxLengthProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text,
@ -350,6 +353,12 @@ namespace Avalonia.Controls
set { SetValue(MaxLengthProperty, value); }
}
public int MaxLines
{
get { return GetValue(MaxLinesProperty); }
set { SetValue(MaxLinesProperty, value); }
}
[Content]
public string? Text
{
@ -671,6 +680,39 @@ namespace Avalonia.Controls
_selectedTextChangesMadeSinceLastUndoSnapshot++;
SnapshotUndoRedo(ignoreChangeCount: false);
if (_presenter != null && MaxLines > 0)
{
var lineCount = _presenter.TextLayout.TextLines.Count;
var length = 0;
var graphemeEnumerator = new GraphemeEnumerator(input.AsMemory());
while (graphemeEnumerator.MoveNext())
{
var grapheme = graphemeEnumerator.Current;
if (grapheme.FirstCodepoint.IsBreakChar)
{
if(lineCount + 1 > MaxLines)
{
break;
}
else
{
lineCount++;
}
}
length += grapheme.Text.Length;
}
if (length < input.Length)
{
input = input.Remove(Math.Max(0, length));
}
}
var text = Text ?? string.Empty;
var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
@ -998,18 +1040,25 @@ namespace Avalonia.Controls
SetSelectionForControlBackspace();
}
if (!DeleteSelection() && caretIndex > 0)
if (!DeleteSelection())
{
_presenter.MoveCaretHorizontal(LogicalDirection.Backward);
var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
var removedCharacters = Math.Max(0, caretIndex - _presenter.CaretIndex);
var length = Math.Max(0, caretIndex - removedCharacters);
var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
SetTextInternal(text.Substring(0, length) +
text.Substring(caretIndex));
if (caretIndex != backspacePosition)
{
var start = Math.Min(backspacePosition, caretIndex);
var end = Math.Max(backspacePosition, caretIndex);
CaretIndex = _presenter.CaretIndex;
var length = end - start;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
SetTextInternal(editedText);
CaretIndex = end;
}
}
SnapshotUndoRedo();
@ -1025,15 +1074,21 @@ namespace Avalonia.Controls
SetSelectionForControlDelete();
}
if (!DeleteSelection() && caretIndex < text.Length)
if (!DeleteSelection())
{
var characterHit = _presenter.GetNextCharacterHit();
var removedCharacters = Math.Max(0,
characterHit.FirstCharacterIndex + characterHit.TrailingLength - caretIndex);
var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if(nextPosition != caretIndex)
{
var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex);
SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters));
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
SetTextInternal(editedText);
}
}
SnapshotUndoRedo();
@ -1250,17 +1305,17 @@ namespace Avalonia.Controls
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
{
if (_presenter == null)
{
return;
}
var text = Text ?? string.Empty;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
if (!wholeWord)
{
if (_presenter == null)
{
return;
}
if (isSelecting)
{
_presenter.MoveCaretToTextPosition(selectionEnd);
@ -1304,10 +1359,16 @@ namespace Avalonia.Controls
SelectionEnd += offset;
_presenter.MoveCaretToTextPosition(SelectionEnd);
if (!isSelecting)
{
CaretIndex = SelectionEnd;
}
else
{
SelectionStart = selectionStart;
}
}
}
@ -1327,10 +1388,10 @@ namespace Avalonia.Controls
else
{
var textLines = _presenter.TextLayout.TextLines;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, true);
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, false);
var textLine = textLines[lineIndex];
_presenter.MoveCaretToTextPosition(textLine.TextRange.Start);
_presenter.MoveCaretToTextPosition(textLine.FirstTextSourceIndex);
}
}
@ -1351,16 +1412,10 @@ namespace Avalonia.Controls
else
{
var textLines = _presenter.TextLayout.TextLines;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, true);
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, false);
var textLine = textLines[lineIndex];
if (caretIndex == textLine.TextRange.Start + textLine.TextRange.Length - textLine.NewLineLength &&
lineIndex + 1 < textLines.Count)
{
textLine = textLines[++lineIndex];
}
var textPosition = textLine.TextRange.Start + textLine.TextRange.Length - textLine.NewLineLength;
var textPosition = textLine.FirstTextSourceIndex + textLine.Length;
_presenter.MoveCaretToTextPosition(textPosition, true);
}

1
src/Avalonia.Controls/Window.cs

@ -12,7 +12,6 @@ using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using JetBrains.Annotations;
namespace Avalonia.Controls
{

24
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json

@ -1078,12 +1078,6 @@
"json5": "^2.1.2"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
@ -1636,9 +1630,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mobx": {
@ -1839,12 +1833,6 @@
"ms": "^2.1.1"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@ -2592,12 +2580,6 @@
"json5": "^2.1.2"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",

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

@ -11,7 +11,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel Margin="{TemplateBinding Padding}">
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock>
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0"></TextBlock>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
@ -44,7 +44,7 @@
<DataTemplate>
<StackPanel Orientation="Vertical">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock>
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0"></TextBlock>
<TextBlock Text="{Binding Input}"/>
</DockPanel>
<TextBlock Foreground="{Binding Foreground}" Text="{Binding Output}"/>

2
src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml

@ -47,7 +47,7 @@
<controls:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top"
HorizontalAlignment="Center"
TextBlock.Foreground="{StaticResource ThicknessEditorForeground}"
Foreground="{StaticResource ThicknessEditorForeground}"
Background="{StaticResource MarginBackgroundBrush}"
Highlight="{StaticResource MarginHighlightBrush}"
Thickness="{Binding MarginThickness}">

7
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -133,9 +133,12 @@ namespace Avalonia.Headless
class HeadlessTextShaperStub : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
return new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
}
}

2
src/Avalonia.Themes.Default/Controls/Button.xaml

@ -18,7 +18,7 @@
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>

2
src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml

@ -4,7 +4,7 @@
<Setter Property="MaxHeight" Value="30" />
<Setter Property="Template">
<ControlTemplate>
<StackPanel Spacing="2" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal">
<StackPanel Spacing="2" VerticalAlignment="Stretch" TextElement.FontSize="10" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Panel">
<Setter Property="Width" Value="45" />

2
src/Avalonia.Themes.Default/Controls/CheckBox.xaml

@ -37,7 +37,7 @@
</Panel>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"

18
src/Avalonia.Themes.Default/Controls/DatePicker.xaml

@ -36,7 +36,7 @@
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem">
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/>
@ -58,7 +58,7 @@
BorderBrush="{DynamicResource ThemeControlTransparentBrush}"
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{DynamicResource ThemeForegroundBrush}"
TextElement.Foreground="{DynamicResource ThemeForegroundBrush}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
@ -71,13 +71,13 @@
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightLowBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlTransparentBrush}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlTransparentBrush}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
@ -151,7 +151,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"/>
</ControlTemplate>
@ -196,7 +196,7 @@
</Setter>
</Style>
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="DatePicker:disabled /template/ Rectangle">
<!--<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/>-->
@ -206,7 +206,7 @@
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlHighBrush}"/>
<!--<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/>-->
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter">
@ -214,13 +214,13 @@
<Setter Property="Background">
<SolidColorBrush Color="{DynamicResource ThemeControlMidHighColor}" Opacity="0.6" />
</Setter>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter">
<!--<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>-->
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>-->
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>

2
src/Avalonia.Themes.Default/Controls/RepeatButton.xaml

@ -24,7 +24,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>

22
src/Avalonia.Themes.Default/Controls/SplitButton.xaml

@ -146,7 +146,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPointerOver}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pointerover PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
@ -157,7 +157,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -169,7 +169,7 @@
SplitButton:pressed /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton:pressed /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -181,7 +181,7 @@
SplitButton:flyout-open /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton:flyout-open /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -193,7 +193,7 @@
SplitButton:disabled /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundDisabled}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
</Style>
<Style Selector="SplitButton:disabled /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
@ -205,7 +205,7 @@
SplitButton:checked /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundChecked}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushChecked}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
@ -216,7 +216,7 @@
SplitButton:checked /template/ Button#PART_SecondaryButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPointerOver}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton:pointerover PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
@ -227,7 +227,7 @@
SplitButton:checked /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -239,7 +239,7 @@
SplitButton:pressed:checked /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:pressed:checked /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -251,7 +251,7 @@
SplitButton:checked:flyout-open /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:checked:flyout-open /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -263,7 +263,7 @@
SplitButton:checked:disabled /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedDisabled}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />
</Style>
<Style Selector="SplitButton:checked:disabled /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />

12
src/Avalonia.Themes.Default/Controls/TimePicker.xaml

@ -48,7 +48,7 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
TextBlock.Foreground="{DynamicResource ThemeForegroundColor}"
TextElement.Foreground="{DynamicResource ThemeForegroundColor}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
@ -74,7 +74,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</ControlTemplate>
@ -135,7 +135,7 @@
</Style>
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="TimePicker:disabled /template/ Rectangle">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
@ -144,7 +144,7 @@
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<!--<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/>-->
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlHighBrush}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter">
@ -152,13 +152,13 @@
<SolidColorBrush Color="{DynamicResource ThemeControlMidHighColor}" Opacity="0.6" />
</Setter>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlLowBrush}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter">
<!--<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>-->
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>-->
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>

2
src/Avalonia.Themes.Default/Controls/ToggleButton.xaml

@ -18,7 +18,7 @@
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>

14
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -45,37 +45,37 @@
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForeground}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
<Style Selector="Button, RepeatButton, ToggleButton">
@ -94,6 +94,6 @@
<Style Selector="Button.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -61,17 +61,17 @@
</Style>
<Style Selector="Button.CalendarHeader:pointerover /template/ ContentPresenter#Text">
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewNavigationButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:pressed /template/ ContentPresenter#Text">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:disabled /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
</Style>
</Border.Styles>
<!-- To keep calendar from resizing when switching DisplayMode

2
src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml

@ -3,7 +3,7 @@
<Setter Property="MaxHeight" Value="30" />
<Setter Property="Template">
<ControlTemplate>
<StackPanel Spacing="2" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal">
<StackPanel Spacing="2" VerticalAlignment="Stretch" TextElement.FontSize="10" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Panel">
<Setter Property="Width" Value="45" />

18
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -76,7 +76,7 @@
<!-- Unchecked PointerOver State -->
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#PART_Border">
@ -95,7 +95,7 @@
<!-- Unchecked Pressed State -->
<Style Selector="CheckBox:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
</Style>
<Style Selector="CheckBox:pressed /template/ Border#PART_Border">
@ -114,7 +114,7 @@
<!-- Unchecked Disabled state -->
<Style Selector="CheckBox:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Border#PART_Border">
@ -156,7 +156,7 @@
<!-- Checked PointerOver State -->
<Style Selector="CheckBox:checked:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Border#PART_Border">
@ -175,7 +175,7 @@
<!-- Checked Pressed State -->
<Style Selector="CheckBox:checked:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Border#PART_Border">
@ -194,7 +194,7 @@
<!-- Checked Disabled State -->
<Style Selector="CheckBox:checked:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#PART_Border">
@ -236,7 +236,7 @@
<!-- Indeterminate PointerOver State -->
<Style Selector="CheckBox:indeterminate:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#PART_Border">
@ -255,7 +255,7 @@
<!-- Indeterminate Pressed State -->
<Style Selector="CheckBox:indeterminate:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#PART_Border">
@ -274,7 +274,7 @@
<!-- Indeterminate Disabled State -->
<Style Selector="CheckBox:indeterminate:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#PART_Border">

14
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -53,7 +53,7 @@
Grid.Column="0"
Grid.ColumnSpan="2"
IsVisible="False"
TextBlock.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
TextElement.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
Margin="{DynamicResource ComboBoxTopHeaderMargin}"
VerticalAlignment="Top" />
<Border x:Name="Background"
@ -177,11 +177,11 @@
</Style>
<Style Selector="ComboBox:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ TextBlock#PlaceholderTextBlock">
@ -199,11 +199,11 @@
</Style>
<Style Selector="ComboBox:focus-visible /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<Style Selector="ComboBox:focus-visible /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<Style Selector="ComboBox:focus-visible /template/ PathIcon#DropDownGlyph">
@ -212,11 +212,11 @@
<!-- Focus Pressed State -->
<Style Selector="ComboBox:focused:pressed /template/ ContentControl#ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocusedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ TextBlock#PlaceholderTextBlock">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ PathIcon#DropDownGlyph">

18
src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml

@ -18,14 +18,14 @@
</Design.PreviewWith>
<Style Selector="ComboBoxItem">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForeground}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForeground}" />
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackground}" />
<Setter Property="Padding" Value="{DynamicResource ComboBoxItemThemePadding}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
@ -43,48 +43,48 @@
<Style Selector="ComboBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundPointerOver}" />
</Style>
<!-- Disabled state -->
<Style Selector="ComboBoxItem:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundDisabled}" />
</Style>
<!-- Pressed state -->
<Style Selector="ComboBoxItem:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundPressed}" />
</Style>
<!-- Selected state -->
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelected}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelected}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelected}" />
</Style>
<!-- Selected Disabled state -->
<Style Selector="ComboBoxItem:selected:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedDisabled}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="ComboBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPointerOver}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="ComboBoxItem:selected:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPressed}" />
</Style>
</Styles>

2
src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml

@ -29,7 +29,7 @@
<Style Selector="DataValidationErrors">
<Style.Resources>
<DataTemplate x:Key="InlineDataValidationErrorTemplate">
<ItemsControl Items="{Binding}" x:DataType="DataValidationErrors" TextBlock.Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl Items="{Binding}" x:DataType="DataValidationErrors" Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />

18
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -43,7 +43,7 @@
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem">
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/>
@ -70,7 +70,7 @@
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}"
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
TextElement.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
@ -84,13 +84,13 @@
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
</Style>
@ -165,7 +165,7 @@
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius}"/>
@ -212,7 +212,7 @@
</Setter>
</Style>
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
</Style>
<Style Selector="DatePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/>
@ -221,19 +221,19 @@
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
</Style>
<!-- Changes foreground for watermark text when SelectedDate is null-->

2
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -110,7 +110,7 @@
BorderThickness="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
TextElement.Foreground="{DynamicResource ExpanderForeground}" />
<Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="32"

2
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -10,7 +10,7 @@
</Border>
</Design.PreviewWith>
<Style Selector="ListBox">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ListBoxBorderThemeThickness}" />

14
src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml

@ -41,7 +41,7 @@
<!-- Disabled State -->
<Style Selector="ListBoxItem:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
</Style>
<!-- PointerOver State -->
@ -49,7 +49,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListLowBrush}" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Pressed State -->
@ -57,7 +57,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListMediumBrush}" />
</Style>
<Style Selector="ListBoxItem:pressed /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected State -->
@ -65,7 +65,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected Unfocused State -->
@ -73,7 +73,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:not(:focus) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected PointerOver State -->
@ -81,7 +81,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentMediumBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected Pressed State -->
@ -89,6 +89,6 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentHighBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -218,7 +218,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPointerOver}" />
</Style>
<Style Selector="MenuItem:selected /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
</Style>
<Style Selector="MenuItem:selected /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPointerOver}" />
@ -232,7 +232,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPressed}" />
@ -245,7 +245,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundDisabled}" />
</Style>
<Style Selector="MenuItem:disabled /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
</Style>
<Style Selector="MenuItem:disabled /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundDisabled}" />

8
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

@ -49,7 +49,7 @@
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
@ -81,7 +81,7 @@
<!-- PointerOver State -->
<Style Selector="RadioButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="(TextBlock.Foreground)" Value="{DynamicResource RadioButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundPointerOver}" />
</Style>
<Style Selector="RadioButton:pointerover /template/ Border#RootBorder">
@ -107,7 +107,7 @@
<!-- Pressed State -->
<Style Selector="RadioButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="(TextBlock.Foreground)" Value="{DynamicResource RadioButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundPressed}" />
</Style>
<Style Selector="RadioButton:pressed /template/ Border#RootBorder">
@ -133,7 +133,7 @@
<!-- Disabled State -->
<Style Selector="RadioButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="(TextBlock.Foreground)" Value="{DynamicResource RadioButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundDisabled}" />
</Style>
<Style Selector="RadioButton:disabled /template/ Border#RootBorder">

6
src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml

@ -46,18 +46,18 @@
<Style Selector="RepeatButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource RepeatButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundPointerOver}" />
</Style>
<Style Selector="RepeatButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource RepeatButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundPressed}" />
</Style>
<Style Selector="RepeatButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/Slider.xaml

@ -45,7 +45,7 @@
<DataValidationErrors>
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="grid" Margin="{TemplateBinding Padding}" RowDefinitions="Auto, *">
<ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextBlock.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextBlock.Foreground="{DynamicResource SliderHeaderForeground}"
<ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextElement.Foreground="{DynamicResource SliderHeaderForeground}"
Margin="{DynamicResource SliderTopHeaderMargin}" />
<Grid x:Name="SliderContainer" Grid.Row="1">
<Grid.Styles>
@ -107,7 +107,7 @@
<DataValidationErrors>
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{TemplateBinding CornerRadius}">
<Grid Name="grid" Margin="{TemplateBinding Padding}" RowDefinitions="Auto, *">
<ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextBlock.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextBlock.Foreground="{DynamicResource SliderHeaderForeground}"
<ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextElement.Foreground="{DynamicResource SliderHeaderForeground}"
Margin="{DynamicResource SliderTopHeaderMargin}" />
<Grid x:Name="SliderContainer" Grid.Row="1">
<Grid.Styles>
@ -208,7 +208,7 @@
<!-- Disabled State -->
<Style Selector="Slider:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ RepeatButton#PART_DecreaseButton">

22
src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml

@ -122,7 +122,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPointerOver}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pointerover PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
@ -133,7 +133,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -145,7 +145,7 @@
SplitButton:pressed /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton:pressed /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -157,7 +157,7 @@
SplitButton:flyout-open /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton:flyout-open /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -169,7 +169,7 @@
SplitButton:disabled /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundDisabled}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
</Style>
<Style Selector="SplitButton:disabled /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundDisabled}" />
@ -181,7 +181,7 @@
SplitButton:checked /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundChecked}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushChecked}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundChecked}" />
@ -192,7 +192,7 @@
SplitButton:checked /template/ Button#PART_SecondaryButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPointerOver}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton:pointerover PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPointerOver}" />
@ -203,7 +203,7 @@
SplitButton:checked /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -215,7 +215,7 @@
SplitButton:pressed:checked /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:pressed:checked /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -227,7 +227,7 @@
SplitButton:checked:flyout-open /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:checked:flyout-open /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
@ -239,7 +239,7 @@
SplitButton:checked:disabled /template/ Border#SeparatorBorder">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedDisabled}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />
</Style>
<Style Selector="SplitButton:checked:disabled /template/ Button#PART_SecondaryButton PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedDisabled}" />

18
src/Avalonia.Themes.Fluent/Controls/TabItem.xaml

@ -37,9 +37,9 @@
Content="{TemplateBinding Header}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel>
@ -61,7 +61,7 @@
<!-- We don't use selector to PART_LayoutRoot, so developer can override selected item background with TabStripItem.Background -->
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelected}" />
<Setter Property="Foreground" Value="{DynamicResource TabItemHeaderForegroundSelected}" />
</Style>
<Style Selector="TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
@ -70,31 +70,31 @@
<!-- PointerOver state -->
<Style Selector="TabItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="TabItem:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPointerOver}" />
</Style>
<!-- Pressed state -->
<Style Selector="TabItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabItem:selected:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
</Style>
<!-- Disabled state -->
<Style Selector="TabItem:disabled /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundDisabled}" />
</Style>
<!-- TabStripPlacement States Group -->

18
src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml

@ -36,9 +36,9 @@
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel>
@ -69,7 +69,7 @@
<!-- We don't use selector to PART_LayoutRoot, so developer can override selected item background with TabStripItem.Background -->
<Style Selector="TabStripItem:selected">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelected}" />
<Setter Property="Foreground" Value="{DynamicResource TabItemHeaderForegroundSelected}" />
</Style>
<Style Selector="TabStripItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
@ -78,30 +78,30 @@
<!-- PointerOver state -->
<Style Selector="TabStripItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="TabStripItem:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPointerOver}" />
</Style>
<!-- Pressed state -->
<Style Selector="TabStripItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabStripItem:selected:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
</Style>
<!-- Disabled state -->
<Style Selector="TabStripItem:disabled /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundDisabled}" />
</Style>
</Styles>

12
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -51,7 +51,7 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
TextBlock.Foreground="{DynamicResource TimePickerHeaderForeground}"
TextElement.Foreground="{DynamicResource TimePickerHeaderForeground}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
@ -77,7 +77,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</ControlTemplate>
@ -139,7 +139,7 @@
</Style>
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
@ -148,19 +148,19 @@
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:hasnotime /template/ Button#FlyoutButton TextBlock">

22
src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml

@ -48,66 +48,66 @@
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundPointerOver}" />
</Style>
<Style Selector="ToggleButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundPressed}" />
</Style>
<Style Selector="ToggleButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundDisabled}" />
</Style>
<Style Selector="ToggleButton:checked /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushChecked}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundChecked}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundChecked}" />
</Style>
<Style Selector="ToggleButton:checked:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="ToggleButton:checked:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="ToggleButton:checked:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedDisabled}" />
</Style>
<Style Selector="ToggleButton:indeterminate /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminate}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminate}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminate}" />
</Style>
<Style Selector="ToggleButton:indeterminate:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminatePointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminatePointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="ToggleButton:indeterminate:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminatePressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminatePressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePressed}" />
</Style>
<Style Selector="ToggleButton:indeterminate:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminateDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminateDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminateDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminateDisabled}" />
</Style>
</Styles>

14
src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml

@ -107,7 +107,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushPointerOver}" />
</Style>
<Style Selector="TreeViewItem /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundPointerOver}" />
</Style>
<!-- Pressed state -->
@ -116,7 +116,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushPressed}" />
</Style>
<Style Selector="TreeViewItem:pressed /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundPressed}" />
</Style>
<!-- Disabled state -->
@ -125,7 +125,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushDisabled}" />
</Style>
<Style Selector="TreeViewItem:disabled /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundDisabled}" />
</Style>
<!-- Selected state -->
@ -134,7 +134,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelected}" />
</Style>
<Style Selector="TreeViewItem:selected /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundSelected}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelected}" />
</Style>
<!-- Selected PointerOver state -->
@ -143,7 +143,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedPointerOver}" />
</Style>
<Style Selector="TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPointerOver}" />
</Style>
<!-- Selected Pressed state -->
@ -152,7 +152,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedPressed}" />
</Style>
<Style Selector="TreeViewItem:pressed:selected /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPressed}" />
</Style>
<!-- Disabled Selected state -->
@ -161,7 +161,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedDisabled}" />
</Style>
<Style Selector="TreeViewItem:disabled:selected /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedDisabled}" />
</Style>
<!-- ExpandCollapseChevron Group states -->

2
src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml

@ -9,7 +9,7 @@
<ReversibleStackPanel Name="PART_Items">
<ReversibleStackPanel.DataTemplates>
<DataTemplate DataType="INotification">
<StackPanel Spacing="8" Margin="12" TextBlock.Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}">
<StackPanel Spacing="8" Margin="12" TextElement.Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}">
<TextBlock Text="{Binding Title}" FontWeight="Medium" />
<TextBlock MaxHeight="80" Text="{Binding Message}" TextWrapping="Wrap" Margin="0,0,12,0"/>
</StackPanel>

17
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -67,6 +67,8 @@ MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableRadialG
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableSolidColorBrush..ctor(Avalonia.Media.Color, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableTileBrush..ctor(Avalonia.Media.AlignmentX, Avalonia.Media.AlignmentY, Avalonia.RelativeRect, System.Double, Avalonia.RelativeRect, Avalonia.Media.Stretch, Avalonia.Media.TileMode, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.DrawableTextRun.Baseline' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.DrawableTextRun.Baseline.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -85,7 +87,7 @@ MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextC
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult Avalonia.Media.TextFormatting.ShapedTextCharacters.Split(System.Int32)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Boolean Avalonia.Media.TextFormatting.TextCharacters.TryGetRunProperties(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, Avalonia.Media.Typeface, System.Int32)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList<Avalonia.Media.TextFormatting.TextRun> Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine)' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Collections.Generic.List<Avalonia.Media.TextFormatting.DrawableTextRun> Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine)' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextCollapsingProperties.Style.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.TextCollapsingStyle' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextEndOfLine..ctor()' does not exist in the implementation but it does exist in the contract.
@ -94,8 +96,10 @@ MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.
MembersMustExist : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.TextLayout.Size.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Baseline' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Extent' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.FirstTextSourceIndex' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Boolean Avalonia.Media.TextFormatting.TextLine.HasOverflowed' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Height' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.Length' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.NewLineLength' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.OverhangAfter' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.OverhangLeading' is abstract in the implementation but is missing in the contract.
@ -108,18 +112,23 @@ CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextForma
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Extent.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.FirstTextSourceIndex.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList<Avalonia.Media.TextFormatting.TextBounds> Avalonia.Media.TextFormatting.TextLine.GetTextBounds(System.Int32, System.Int32)' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Boolean Avalonia.Media.TextFormatting.TextLine.HasOverflowed.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Height.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.Length.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineMetrics Avalonia.Media.TextFormatting.TextLine.LineMetrics.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.NewLineLength.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.OverhangAfter.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.OverhangLeading.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.OverhangTrailing.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Start.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextRange Avalonia.Media.TextFormatting.TextLine.TextRange.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Int32 Avalonia.Media.TextFormatting.TextLine.TrailingWhitespaceLength.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.Width.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextLine.WidthIncludingTrailingWhitespace.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLineBreak..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.TextFormatting.ShapedTextCharacters>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyList<Avalonia.Media.TextFormatting.ShapedTextCharacters> Avalonia.Media.TextFormatting.TextLineBreak.RemainingCharacters.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLineMetrics..ctor(Avalonia.Size, System.Double, Avalonia.Media.TextFormatting.TextRange, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineMetrics Avalonia.Media.TextFormatting.TextLineMetrics.Create(System.Collections.Generic.IEnumerable<Avalonia.Media.TextFormatting.TextRun>, Avalonia.Media.TextFormatting.TextRange, System.Double, Avalonia.Media.TextFormatting.TextParagraphProperties)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.TextLineMetrics.Size.get()' does not exist in the implementation but it does exist in the contract.
@ -130,8 +139,6 @@ CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextForma
CannotAddAbstractMembers : Member 'public System.Boolean Avalonia.Media.TextFormatting.TextParagraphProperties.FirstLineInParagraph.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.FlowDirection Avalonia.Media.TextFormatting.TextParagraphProperties.FlowDirection.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextParagraphProperties.Indent.get()' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Media.TextFormatting.TextShaper.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract.
@ -171,9 +178,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avaloni
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.TextFormatting.ShapedBuffer Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.GlyphTypeface, System.Double, System.Globalization.CultureInfo, System.SByte)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.TextFormatting.ShapedBuffer Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.TextFormatting.TextShaperOptions)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable<System.Int32>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 177
Total Issues: 184

16
src/Avalonia.Visuals/Media/FormattedText.cs

@ -56,7 +56,7 @@ namespace Avalonia.Media
FlowDirection flowDirection,
Typeface typeface,
double emSize,
IBrush foreground)
IBrush? foreground)
{
if (culture is null)
{
@ -160,7 +160,7 @@ namespace Avalonia.Media
/// <param name="foregroundBrush">Foreground brush</param>
/// <param name="startIndex">The start index of initial character to apply the change to.</param>
/// <param name="count">The number of characters the change should be applied to.</param>
public void SetForegroundBrush(IBrush foregroundBrush, int startIndex, int count)
public void SetForegroundBrush(IBrush? foregroundBrush, int startIndex, int count)
{
var limit = ValidateRange(startIndex, count);
for (var i = startIndex; i < limit;)
@ -768,7 +768,7 @@ namespace Avalonia.Media
// as a result of the next line measurement
// maybe there is no next line at all
if (Position + Current.TextRange.Length < _that._text.Length)
if (Position + Current.Length < _that._text.Length)
{
bool nextLineFits;
@ -780,7 +780,7 @@ namespace Avalonia.Media
{
_nextLine = FormatLine(
_textSource,
Position + Current.TextRange.Length,
Position + Current.Length,
MaxLineLength(_lineCount + 1),
_that._defaultParaProps,
currentLineBreak
@ -819,7 +819,7 @@ namespace Avalonia.Media
_previousHeight = Current.Height;
Length = Current.TextRange.Length;
Length = Current.Length;
_previousLineBreak = currentLineBreak;
@ -839,17 +839,17 @@ namespace Avalonia.Media
lineBreak
);
if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.TextRange.Length > 0)
if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0)
{
// what I really need here is the last displayed text run of the line
// textSourcePosition + line.Length - 1 works except the end of paragraph case,
// where line length includes the fake paragraph break run
Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.TextRange.Length <= _that._text.Length + 1);
Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1);
var thatFormatRider = new SpanRider(
_that._formatRuns,
_that._latestPosition,
Math.Min(textSourcePosition + line.TextRange.Length - 1, _that._text.Length - 1)
Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1)
);
var lastRunProps = (GenericTextRunProperties)thatFormatRider.CurrentElement!;

10
src/Avalonia.Visuals/Media/TextDecorationCollection.cs

@ -10,6 +10,16 @@ namespace Avalonia.Media
/// </summary>
public class TextDecorationCollection : AvaloniaList<TextDecoration>
{
public TextDecorationCollection()
{
}
public TextDecorationCollection(IEnumerable<TextDecoration> textDecorations) : base(textDecorations)
{
}
/// <summary>
/// Parses a <see cref="TextDecorationCollection"/> string.
/// </summary>

5
src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs

@ -9,6 +9,11 @@
/// Gets the size.
/// </summary>
public abstract Size Size { get; }
/// <summary>
/// Run baseline in ratio relative to run height
/// </summary>
public abstract double Baseline { get; }
/// <summary>
/// Draws the <see cref="DrawableTextRun"/> at the given origin.

27
src/Avalonia.Visuals/Media/TextFormatting/FormattedTextSource.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
@ -30,10 +29,10 @@ namespace Avalonia.Media.TextFormatting
if (runText.IsEmpty)
{
return new TextEndOfParagraph();
return null;
}
var textStyleRun = CreateTextStyleRun(runText, _defaultProperties, _textModifier);
var textStyleRun = CreateTextStyleRun(runText, textSourceIndex, _defaultProperties, _textModifier);
return new TextCharacters(runText.Take(textStyleRun.Length), textStyleRun.Value);
}
@ -42,17 +41,18 @@ namespace Avalonia.Media.TextFormatting
/// Creates a span of text run properties that has modifier applied.
/// </summary>
/// <param name="text">The text to create the properties for.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="defaultProperties">The default text properties.</param>
/// <param name="textModifier">The text properties modifier.</param>
/// <returns>
/// The created text style run.
/// </returns>
private static ValueSpan<TextRunProperties> CreateTextStyleRun(ReadOnlySlice<char> text,
private static ValueSpan<TextRunProperties> CreateTextStyleRun(ReadOnlySlice<char> text, int firstTextSourceIndex,
TextRunProperties defaultProperties, IReadOnlyList<ValueSpan<TextRunProperties>>? textModifier)
{
if (textModifier == null || textModifier.Count == 0)
{
return new ValueSpan<TextRunProperties>(text.Start, text.Length, defaultProperties);
return new ValueSpan<TextRunProperties>(firstTextSourceIndex, text.Length, defaultProperties);
}
var currentProperties = defaultProperties;
@ -69,28 +69,28 @@ namespace Avalonia.Media.TextFormatting
var textRange = new TextRange(propertiesOverride.Start, propertiesOverride.Length);
if (textRange.Start + textRange.Length <= text.Start)
if (textRange.Start + textRange.Length <= firstTextSourceIndex)
{
continue;
}
if (textRange.Start > text.End)
if (textRange.Start > firstTextSourceIndex + text.Length)
{
length = text.Length;
break;
}
if (textRange.Start > text.Start)
if (textRange.Start > firstTextSourceIndex)
{
if (propertiesOverride.Value != currentProperties)
{
length = Math.Min(Math.Abs(textRange.Start - text.Start), text.Length);
length = Math.Min(Math.Abs(textRange.Start - firstTextSourceIndex), text.Length);
break;
}
}
length += Math.Max(0, textRange.Start + textRange.Length - text.Start);
length = Math.Max(0, textRange.Start + textRange.Length - firstTextSourceIndex);
if (hasOverride)
{
@ -116,12 +116,7 @@ namespace Avalonia.Media.TextFormatting
length = text.Length;
}
if (length != text.Length)
{
text = text.Take(length);
}
return new ValueSpan<TextRunProperties>(text.Start, length, currentProperties);
return new ValueSpan<TextRunProperties>(firstTextSourceIndex, length, currentProperties);
}
}
}

2
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -38,6 +38,8 @@ namespace Avalonia.Media.TextFormatting
public FontMetrics FontMetrics { get; }
public override double Baseline => -FontMetrics.Ascent;
public override Size Size => GlyphRun.Size;
public GlyphRun GlyphRun

29
src/Avalonia.Visuals/Media/TextFormatting/TextBounds.cs

@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// The bounding rectangle of a range of characters
/// </summary>
public sealed class TextBounds
{
/// <summary>
/// Constructing TextBounds object
/// </summary>
internal TextBounds(Rect bounds, FlowDirection flowDirection)
{
Rectangle = bounds;
FlowDirection = flowDirection;
}
/// <summary>
/// Bounds rectangle
/// </summary>
public Rect Rectangle { get; }
/// <summary>
/// Text flow direction inside the boundary rectangle
/// </summary>
public FlowDirection FlowDirection { get; }
}
}

25
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -38,7 +38,7 @@ namespace Avalonia.Media.TextFormatting
/// Gets a list of <see cref="ShapeableTextCharacters"/>.
/// </summary>
/// <returns>The shapeable text characters.</returns>
internal IList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel,
internal IReadOnlyList<ShapeableTextCharacters> GetShapeableCharacters(ReadOnlySlice<char> runText, sbyte biDiLevel,
ref TextRunProperties? previousProperties)
{
var shapeableCharacters = new List<ShapeableTextCharacters>(2);
@ -72,11 +72,11 @@ namespace Avalonia.Media.TextFormatting
var currentTypeface = defaultTypeface;
var previousTypeface = previousProperties?.Typeface;
if (TryGetShapeableLength(text, currentTypeface, out var count, out var script))
if (TryGetShapeableLength(text, currentTypeface, null, out var count, out var script))
{
if (script == Script.Common && previousTypeface is not null)
{
if(TryGetShapeableLength(text, previousTypeface.Value, out var fallbackCount, out _))
if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _))
{
return new ShapeableTextCharacters(text.Take(fallbackCount),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -89,7 +89,7 @@ namespace Avalonia.Media.TextFormatting
if (previousTypeface is not null)
{
if(TryGetShapeableLength(text, previousTypeface.Value, out count, out _))
if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _))
{
return new ShapeableTextCharacters(text.Take(count),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@ -118,7 +118,7 @@ namespace Avalonia.Media.TextFormatting
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
out currentTypeface);
if (matchFound && TryGetShapeableLength(text, currentTypeface, out count, out _))
if (matchFound && TryGetShapeableLength(text, currentTypeface, defaultTypeface, out count, out _))
{
//Fallback found
return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface),
@ -152,14 +152,19 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="text">The text.</param>
/// <param name="typeface">The typeface that is used to find matching characters.</param>
/// <param name="defaultTypeface"></param>
/// <param name="length">The shapeable length.</param>
/// <param name="script"></param>
/// <returns></returns>
protected static bool TryGetShapeableLength(ReadOnlySlice<char> text, Typeface typeface, out int length,
protected static bool TryGetShapeableLength(
ReadOnlySlice<char> text,
Typeface typeface,
Typeface? defaultTypeface,
out int length,
out Script script)
{
length = 0;
script = Script.Unknown;
script = Script.Unknown;
if (text.Length == 0)
{
@ -167,6 +172,7 @@ namespace Avalonia.Media.TextFormatting
}
var font = typeface.GlyphTypeface;
var defaultFont = defaultTypeface?.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
@ -176,6 +182,11 @@ namespace Avalonia.Media.TextFormatting
var currentScript = currentGrapheme.FirstCodepoint.Script;
if (currentScript != Script.Common && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
//Stop at the first missing glyph
if (!currentGrapheme.FirstCodepoint.IsBreakChar && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{

2
src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs

@ -21,6 +21,6 @@ namespace Avalonia.Media.TextFormatting
/// Collapses given text line.
/// </summary>
/// <param name="textLine">Text line to collapse.</param>
public abstract IReadOnlyList<TextRun>? Collapse(TextLine textLine);
public abstract List<DrawableTextRun>? Collapse(TextLine textLine);
}
}

109
src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs

@ -3,13 +3,11 @@ using Avalonia.Media.TextFormatting.Unicode;
namespace Avalonia.Media.TextFormatting
{
internal class TextEllipsisHelper
internal static class TextEllipsisHelper
{
public static List<ShapedTextCharacters>? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
public static List<DrawableTextRun>? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
if (textLine.TextRuns is not List<DrawableTextRun> textRuns || textRuns.Count == 0)
{
return null;
}
@ -17,74 +15,103 @@ namespace Avalonia.Media.TextFormatting
var runIndex = 0;
var currentWidth = 0.0;
var collapsedLength = 0;
var textRange = textLine.TextRange;
var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
{
return new List<ShapedTextCharacters>(0);
//Not enough space to fit in the symbol
return new List<DrawableTextRun>(0);
}
var availableWidth = properties.Width - shapedSymbol.Size.Width;
while (runIndex < shapedTextRuns.Count)
while (runIndex < textRuns.Count)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
var currentRun = textRuns[runIndex];
if (currentWidth > availableWidth)
switch (currentRun)
{
if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
case ShapedTextCharacters shapedRun:
{
if (isWordEllipsis && measuredLength < textRange.End)
currentWidth += shapedRun.Size.Width;
if (currentWidth > availableWidth)
{
var currentBreakPosition = 0;
if (shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength))
{
if (isWordEllipsis && measuredLength < textLine.Length)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition >= measuredLength)
{
break;
if (nextBreakPosition >= measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
collapsedLength += measuredLength;
currentBreakPosition = nextBreakPosition;
var collapsedRuns = new List<DrawableTextRun>(textRuns.Count);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);
collapsedRuns.AddRange(splitResult.First);
}
measuredLength = currentBreakPosition;
collapsedRuns.Add(shapedSymbol);
return collapsedRuns;
}
}
collapsedLength += measuredLength;
availableWidth -= currentRun.Size.Width;
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
break;
}
if (collapsedLength > 0)
case { } drawableRun:
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, collapsedLength);
//The whole run needs to fit into available space
if (currentWidth + drawableRun.Size.Width > availableWidth)
{
var collapsedRuns = new List<DrawableTextRun>(textRuns.Count);
shapedTextCharacters.AddRange(splitResult.First);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength);
TextLineImpl.SortRuns(shapedTextCharacters);
}
collapsedRuns.AddRange(splitResult.First);
}
shapedTextCharacters.Add(shapedSymbol);
collapsedRuns.Add(shapedSymbol);
return shapedTextCharacters;
return collapsedRuns;
}
break;
}
}
availableWidth -= currentRun.Size.Width;
collapsedLength += currentRun.GlyphRun.Characters.Length;
collapsedLength += currentRun.TextSourceLength;
runIndex++;
}

291
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
@ -17,22 +15,22 @@ namespace Avalonia.Media.TextFormatting
var textWrapping = paragraphProperties.TextWrapping;
FlowDirection flowDirection;
TextLineBreak? nextLineBreak = null;
List<ShapedTextCharacters> shapedRuns;
List<DrawableTextRun> drawableTextRuns;
var textRuns = FetchTextRuns(textSource, firstTextSourceIndex,
out var textEndOfLine, out var textRange);
out var textEndOfLine, out var textSourceLength);
if (previousLineBreak?.RemainingCharacters != null)
if (previousLineBreak?.RemainingRuns != null)
{
flowDirection = previousLineBreak.FlowDirection;
shapedRuns = previousLineBreak.RemainingCharacters.ToList();
drawableTextRuns = previousLineBreak.RemainingRuns.ToList();
nextLineBreak = previousLineBreak;
}
else
{
shapedRuns = ShapeTextRuns(textRuns, paragraphProperties.FlowDirection,out flowDirection);
drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out flowDirection);
if(nextLineBreak == null && textEndOfLine != null)
if (nextLineBreak == null && textEndOfLine != null)
{
nextLineBreak = new TextLineBreak(textEndOfLine, flowDirection);
}
@ -44,10 +42,8 @@ namespace Avalonia.Media.TextFormatting
{
case TextWrapping.NoWrap:
{
TextLineImpl.SortRuns(shapedRuns);
textLine = new TextLineImpl(shapedRuns, textRange, paragraphWidth, paragraphProperties,
flowDirection, nextLineBreak);
textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength,
paragraphWidth, paragraphProperties, flowDirection, nextLineBreak);
textLine.FinalizeLine();
@ -56,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
case TextWrapping.WrapWithOverflow:
case TextWrapping.Wrap:
{
textLine = PerformTextWrapping(shapedRuns, textRange, paragraphWidth, paragraphProperties,
textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties,
flowDirection, nextLineBreak);
break;
}
@ -73,7 +69,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textRuns">The text run's.</param>
/// <param name="length">The length to split at.</param>
/// <returns>The split text runs.</returns>
internal static SplitResult<List<ShapedTextCharacters>> SplitShapedRuns(List<ShapedTextCharacters> textRuns, int length)
internal static SplitResult<List<DrawableTextRun>> SplitDrawableRuns(List<DrawableTextRun> textRuns, int length)
{
var currentLength = 0;
@ -83,13 +79,14 @@ namespace Avalonia.Media.TextFormatting
if (currentLength + currentRun.Text.Length < length)
{
currentLength += currentRun.Text.Length;
currentLength += currentRun.TextSourceLength;
continue;
}
var firstCount = currentRun.Text.Length >= 1 ? i + 1 : i;
var first = new List<ShapedTextCharacters>(firstCount);
var first = new List<DrawableTextRun>(firstCount);
if (firstCount > 1)
{
@ -103,7 +100,7 @@ namespace Avalonia.Media.TextFormatting
if (currentLength + currentRun.Text.Length == length)
{
var second = secondCount > 0 ? new List<ShapedTextCharacters>(secondCount) : null;
var second = secondCount > 0 ? new List<DrawableTextRun>(secondCount) : null;
if (second != null)
{
@ -117,15 +114,20 @@ namespace Avalonia.Media.TextFormatting
first.Add(currentRun);
return new SplitResult<List<ShapedTextCharacters>>(first, second);
return new SplitResult<List<DrawableTextRun>>(first, second);
}
else
{
secondCount++;
var second = new List<ShapedTextCharacters>(secondCount);
var second = new List<DrawableTextRun>(secondCount);
if (currentRun is not ShapedTextCharacters shapedTextCharacters)
{
throw new NotSupportedException("Only shaped runs can be split in between.");
}
var split = currentRun.Split(length - currentLength);
var split = shapedTextCharacters.Split(length - currentLength);
first.Add(split.First);
@ -136,32 +138,43 @@ namespace Avalonia.Media.TextFormatting
second.Add(textRuns[i + j]);
}
return new SplitResult<List<ShapedTextCharacters>>(first, second);
return new SplitResult<List<DrawableTextRun>>(first, second);
}
}
return new SplitResult<List<ShapedTextCharacters>>(textRuns, null);
return new SplitResult<List<DrawableTextRun>>(textRuns, null);
}
/// <summary>
/// Shape specified text runs with specified paragraph embedding.
/// </summary>
/// <param name="textRuns">The text runs to shape.</param>
/// <param name="flowDirection">The paragraph embedding level.</param>
/// <param name="paragraphProperties">The default paragraph properties.</param>
/// <param name="resolvedFlowDirection">The resolved flow direction.</param>
/// <returns>
/// A list of shaped text characters.
/// </returns>
private static List<ShapedTextCharacters> ShapeTextRuns(List<TextCharacters> textRuns,
FlowDirection flowDirection, out FlowDirection resolvedFlowDirection)
private static List<DrawableTextRun> ShapeTextRuns(List<TextRun> textRuns, TextParagraphProperties paragraphProperties,
out FlowDirection resolvedFlowDirection)
{
var shapedTextCharacters = new List<ShapedTextCharacters>();
var flowDirection = paragraphProperties.FlowDirection;
var drawableTextRuns = new List<DrawableTextRun>();
var biDiData = new BidiData((sbyte)flowDirection);
foreach (var textRun in textRuns)
{
biDiData.Append(textRun.Text);
if (textRun.Text.IsEmpty)
{
var text = new char[textRun.TextSourceLength];
biDiData.Append(text);
}
else
{
biDiData.Append(textRun.Text);
}
}
var biDi = BidiAlgorithm.Instance.Value!;
@ -173,68 +186,90 @@ namespace Avalonia.Media.TextFormatting
resolvedFlowDirection =
(resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft;
var shapeableRuns = new List<ShapeableTextCharacters>(textRuns.Count);
var processedRuns = new List<TextRun>(textRuns.Count);
foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels))
{
shapeableRuns.AddRange(coalescedRuns);
processedRuns.AddRange(coalescedRuns);
}
for (var index = 0; index < shapeableRuns.Count; index++)
for (var index = 0; index < processedRuns.Count; index++)
{
var currentRun = shapeableRuns[index];
var groupedRuns = new List<ShapeableTextCharacters>(2) { currentRun };
var text = currentRun.Text;
var start = currentRun.Text.Start;
var length = currentRun.Text.Length;
var bufferOffset = currentRun.Text.BufferOffset;
while (index + 1 < shapeableRuns.Count)
{
var nextRun = shapeableRuns[index + 1];
if (currentRun.CanShapeTogether(nextRun))
{
groupedRuns.Add(nextRun);
var currentRun = processedRuns[index];
length += nextRun.Text.Length;
if (start > nextRun.Text.Start)
switch (currentRun)
{
case DrawableTextRun drawableRun:
{
start = nextRun.Text.Start;
drawableTextRuns.Add(drawableRun);
break;
}
if (bufferOffset > nextRun.Text.BufferOffset)
case ShapeableTextCharacters shapeableRun:
{
bufferOffset = nextRun.Text.BufferOffset;
}
var groupedRuns = new List<ShapeableTextCharacters>(2) { shapeableRun };
var text = currentRun.Text;
var start = currentRun.Text.Start;
var length = currentRun.Text.Length;
var bufferOffset = currentRun.Text.BufferOffset;
text = new ReadOnlySlice<char>(text.Buffer, start, length, bufferOffset);
index++;
while (index + 1 < processedRuns.Count)
{
if (processedRuns[index + 1] is not ShapeableTextCharacters nextRun)
{
break;
}
currentRun = nextRun;
if (shapeableRun.CanShapeTogether(nextRun))
{
groupedRuns.Add(nextRun);
continue;
}
length += nextRun.Text.Length;
break;
}
if (start > nextRun.Text.Start)
{
start = nextRun.Text.Start;
}
if (bufferOffset > nextRun.Text.BufferOffset)
{
bufferOffset = nextRun.Text.BufferOffset;
}
text = new ReadOnlySlice<char>(text.Buffer, start, length, bufferOffset);
shapedTextCharacters.AddRange(ShapeTogether(groupedRuns, text));
index++;
shapeableRun = nextRun;
continue;
}
break;
}
var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface,
currentRun.Properties.FontRenderingEmSize,
shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab);
drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions));
break;
}
}
}
return shapedTextCharacters;
return drawableTextRuns;
}
private static IReadOnlyList<ShapedTextCharacters> ShapeTogether(
IReadOnlyList<ShapeableTextCharacters> textRuns, ReadOnlySlice<char> text)
IReadOnlyList<ShapeableTextCharacters> textRuns, ReadOnlySlice<char> text, TextShaperOptions options)
{
var shapedRuns = new List<ShapedTextCharacters>(textRuns.Count);
var firstRun = textRuns[0];
var shapedBuffer = TextShaper.Current.ShapeText(text, firstRun.Properties.Typeface.GlyphTypeface,
firstRun.Properties.FontRenderingEmSize, firstRun.Properties.CultureInfo, firstRun.BidiLevel);
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
for (var i = 0; i < textRuns.Count; i++)
{
@ -256,8 +291,8 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textCharacters">The text characters to form <see cref="ShapeableTextCharacters"/> from.</param>
/// <param name="levels">The bidi levels.</param>
/// <returns></returns>
private static IEnumerable<IList<ShapeableTextCharacters>> CoalesceLevels(
IReadOnlyList<TextCharacters> textCharacters,
private static IEnumerable<IReadOnlyList<TextRun>> CoalesceLevels(
IReadOnlyList<TextRun> textCharacters,
ReadOnlySlice<sbyte> levels)
{
if (levels.Length == 0)
@ -275,7 +310,19 @@ namespace Avalonia.Media.TextFormatting
for (var i = 0; i < textCharacters.Count; i++)
{
var j = 0;
currentRun = textCharacters[i];
currentRun = textCharacters[i] as TextCharacters;
if (currentRun == null)
{
var drawableRun = textCharacters[i];
yield return new[] { drawableRun };
levelIndex += drawableRun.TextSourceLength;
continue;
}
runText = currentRun.Text;
for (; j < runText.Length;)
@ -330,18 +377,18 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textSource">The text source.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="endOfLine"></param>
/// <param name="textRange"></param>
/// <param name="textSourceLength"></param>
/// <returns>
/// The formatted text runs.
/// </returns>
private static List<TextCharacters> FetchTextRuns(ITextSource textSource, int firstTextSourceIndex,
out TextEndOfLine? endOfLine, out TextRange textRange)
private static List<TextRun> FetchTextRuns(ITextSource textSource, int firstTextSourceIndex,
out TextEndOfLine? endOfLine, out int textSourceLength)
{
var length = 0;
textSourceLength = 0;
endOfLine = null;
var textRuns = new List<TextCharacters>();
var textRuns = new List<TextRun>();
var textRunEnumerator = new TextRunEnumerator(textSource, firstTextSourceIndex);
@ -349,8 +396,19 @@ namespace Avalonia.Media.TextFormatting
{
var textRun = textRunEnumerator.Current;
if(textRun == null)
if (textRun == null)
{
break;
}
if (textRun is TextEndOfLine textEndOfLine)
{
endOfLine = textEndOfLine;
textRuns.Add(textRun);
textSourceLength += textRun.TextSourceLength;
break;
}
@ -365,9 +423,7 @@ namespace Avalonia.Media.TextFormatting
textRuns.Add(splitResult);
length += runLineBreak.PositionWrap;
textRange = new TextRange(firstTextSourceIndex, length);
textSourceLength += runLineBreak.PositionWrap;
return textRuns;
}
@ -376,16 +432,16 @@ namespace Avalonia.Media.TextFormatting
break;
}
case TextEndOfLine textEndOfLine:
endOfLine = textEndOfLine;
break;
case DrawableTextRun drawableTextRun:
{
textRuns.Add(drawableTextRun);
break;
}
}
length += textRun.Text.Length;
textSourceLength += textRun.TextSourceLength;
}
textRange = new TextRange(firstTextSourceIndex, length);
return textRuns;
}
@ -415,48 +471,74 @@ namespace Avalonia.Media.TextFormatting
return false;
}
private static int MeasureLength(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange,
double paragraphWidth)
private static bool TryMeasureLength(IReadOnlyList<DrawableTextRun> textRuns, int firstTextSourceIndex, double paragraphWidth, out int measuredLength)
{
measuredLength = 0;
var currentWidth = 0.0;
var lastCluster = textRange.Start;
var lastCluster = firstTextSourceIndex;
foreach (var currentRun in textRuns)
{
for (var i = 0; i < currentRun.ShapedBuffer.Length; i++)
switch (currentRun)
{
var glyphInfo = currentRun.ShapedBuffer[i];
case ShapedTextCharacters shapedTextCharacters:
{
for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
{
var glyphInfo = shapedTextCharacters.ShapedBuffer[i];
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
{
var measuredLength = lastCluster - textRange.Start;
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
{
goto found;
}
return measuredLength == 0 ? 1 : measuredLength;
}
lastCluster = glyphInfo.GlyphCluster;
currentWidth += glyphInfo.GlyphAdvance;
}
break;
}
case { } drawableTextRun:
{
if (currentWidth + drawableTextRun.Size.Width > paragraphWidth)
{
goto found;
}
lastCluster = glyphInfo.GlyphCluster;
currentWidth += glyphInfo.GlyphAdvance;
lastCluster += currentRun.TextSourceLength;
currentWidth += currentRun.Size.Width;
break;
}
}
}
return textRange.Length;
found:
measuredLength = Math.Max(0, lastCluster - firstTextSourceIndex + 1);
return measuredLength != 0;
}
/// <summary>
/// Performs text wrapping returns a list of text lines.
/// </summary>
/// <param name="textRuns"></param>
/// <param name="textRange">The text range that is covered by the text runs.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="paragraphProperties">The text paragraph properties.</param>
/// <param name="flowDirection"></param>
/// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
/// <returns>The wrapped text line.</returns>
private static TextLineImpl PerformTextWrapping(List<ShapedTextCharacters> textRuns, TextRange textRange,
private static TextLineImpl PerformTextWrapping(List<DrawableTextRun> textRuns, int firstTextSourceIndex,
double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection,
TextLineBreak? currentLineBreak)
{
var measuredLength = MeasureLength(textRuns, textRange, paragraphWidth);
if (!TryMeasureLength(textRuns, firstTextSourceIndex, paragraphWidth, out var measuredLength))
{
measuredLength = 1;
}
var currentLength = 0;
@ -568,9 +650,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
var splitResult = SplitShapedRuns(textRuns, measuredLength);
textRange = new TextRange(textRange.Start, measuredLength);
var splitResult = SplitDrawableRuns(textRuns, measuredLength);
var remainingCharacters = splitResult.Second;
@ -583,9 +663,8 @@ namespace Avalonia.Media.TextFormatting
lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, flowDirection);
}
TextLineImpl.SortRuns(splitResult.First);
var textLine = new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties, flowDirection,
var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength,
paragraphWidth, paragraphProperties, flowDirection,
lineBreak);
return textLine.FinalizeLine();
@ -644,7 +723,9 @@ namespace Avalonia.Media.TextFormatting
var cultureInfo = textRun.Properties.CultureInfo;
var shapedBuffer = textShaper.ShapeText(textRun.Text, glyphTypeface, fontRenderingEmSize, cultureInfo, (sbyte)flowDirection);
var shaperOptions = new TextShaperOptions(glyphTypeface, fontRenderingEmSize, (sbyte)flowDirection, cultureInfo);
var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions);
return new ShapedTextCharacters(shapedBuffer, textRun.Properties);
}

204
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -162,7 +162,9 @@ namespace Avalonia.Media.TextFormatting
foreach (var textLine in TextLines)
{
if (textLine.TextRange.End < textPosition)
var end = textLine.FirstTextSourceIndex + textLine.Length - 1;
if (end < textPosition)
{
currentY += textLine.Height;
@ -193,187 +195,34 @@ namespace Avalonia.Media.TextFormatting
var result = new List<Rect>(TextLines.Count);
var currentY = 0d;
var currentPosition = 0;
var currentRect = Rect.Empty;
foreach (var textLine in TextLines)
{
//Current line isn't covered.
if (currentPosition + textLine.TextRange.Length <= start)
if (textLine.FirstTextSourceIndex + textLine.Length <= start)
{
currentY += textLine.Height;
currentPosition += textLine.TextRange.Length;
continue;
}
//The whole line is covered.
if (currentPosition >= start && start + length > currentPosition + textLine.TextRange.Length)
{
currentRect = new Rect(textLine.Start, currentY, textLine.WidthIncludingTrailingWhitespace,
textLine.Height);
result.Add(currentRect);
currentY += textLine.Height;
currentPosition += textLine.TextRange.Length;
continue;
}
var startX = textLine.Start;
//A portion of the line is covered.
for (var index = 0; index < textLine.TextRuns.Count; index++)
{
var currentRun = (ShapedTextCharacters)textLine.TextRuns[index];
ShapedTextCharacters? nextRun = null;
var textBounds = textLine.GetTextBounds(start, length);
if (index + 1 < textLine.TextRuns.Count)
{
nextRun = (ShapedTextCharacters)textLine.TextRuns[index + 1];
}
if (nextRun != null)
{
if (nextRun.Text.Start < currentRun.Text.Start && start + length < currentRun.Text.End)
{
goto skip;
}
if (currentRun.Text.Start >= start + length)
{
goto skip;
}
if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < start)
{
goto skip;
}
if (currentRun.Text.End < start)
{
goto skip;
}
goto noop;
skip:
{
startX += currentRun.Size.Width;
currentPosition = currentRun.Text.Start;
}
continue;
noop:{ }
}
var endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(
currentRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(start + length) :
new CharacterHit(start));
var endX = startX + endOffset;
var startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(
currentRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(start) :
new CharacterHit(start + length));
startX += startOffset;
var characterHit = currentRun.GlyphRun.IsLeftToRight ?
currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) :
currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if(nextRun != null)
{
if (currentRun.ShapedBuffer.IsLeftToRight == nextRun.ShapedBuffer.IsLeftToRight)
{
endOffset = nextRun.GlyphRun.GetDistanceFromCharacterHit(
nextRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(start + length) :
new CharacterHit(start));
index++;
endX += endOffset;
currentRun = nextRun;
if (currentRun.ShapedBuffer.IsLeftToRight)
{
characterHit = nextRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
}
}
}
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
var width = endX - startX;
foreach (var bounds in textBounds)
{
Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
if (result.Count > 0 && MathUtilities.AreClose(currentRect.Top, currentY) &&
MathUtilities.AreClose(currentRect.Right, startX))
{
result[result.Count - 1] = currentRect.WithWidth(currentRect.Width + width);
}
else
{
currentRect = new Rect(startX, currentY, width, textLine.Height);
result.Add(currentRect);
}
if (currentRun.ShapedBuffer.IsLeftToRight)
if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
{
if (nextRun != null)
{
if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= start + length)
{
break;
}
currentPosition = nextRun.Text.End;
}
else
{
if (currentPosition >= start + length)
{
break;
}
}
result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
}
else
{
if (currentPosition <= start)
{
break;
}
}
if (!currentRun.ShapedBuffer.IsLeftToRight && currentPosition != currentRun.Text.Start)
{
endX += currentRun.GlyphRun.Size.Width - endOffset;
}
startX = endX;
result.Add(bounds.Rectangle.WithY(currentY));
}
}
if (currentPosition == start || currentPosition == start + length)
{
break;
}
if (textLine.TextRange.Start + textLine.TextRange.Length >= start + length)
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)
{
break;
}
@ -433,12 +282,13 @@ namespace Avalonia.Media.TextFormatting
{
var textLine = TextLines[index];
if (textLine.TextRange.Start + textLine.TextRange.Length < charIndex)
if (textLine.FirstTextSourceIndex + textLine.Length < charIndex)
{
continue;
}
if (charIndex >= textLine.TextRange.Start && charIndex <= textLine.TextRange.End + (trailingEdge ? 1 : 0))
if (charIndex >= textLine.FirstTextSourceIndex &&
charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
{
return index;
}
@ -451,11 +301,11 @@ namespace Avalonia.Media.TextFormatting
{
var (x, y) = point;
var lastTrailingIndex = textLine.TextRange.Start + textLine.TextRange.Length;
var lastTrailingIndex = textLine.FirstTextSourceIndex + textLine.Length;
var isInside = x >= 0 && x <= textLine.Width && y >= 0 && y <= textLine.Height;
if (x >= textLine.Width && textLine.TextRange.Length > 0 && textLine.NewLineLength > 0)
if (x >= textLine.Width && textLine.Length > 0 && textLine.NewLineLength > 0)
{
lastTrailingIndex -= textLine.NewLineLength;
}
@ -465,7 +315,7 @@ namespace Avalonia.Media.TextFormatting
var isTrailing = lastTrailingIndex == textPosition && characterHit.TrailingLength > 0 ||
y > Bounds.Bottom;
if (textPosition == textLine.TextRange.Start + textLine.TextRange.Length)
if (textPosition == textLine.FirstTextSourceIndex + textLine.Length)
{
textPosition -= textLine.NewLineLength;
}
@ -529,23 +379,21 @@ namespace Avalonia.Media.TextFormatting
/// Creates an empty text line.
/// </summary>
/// <returns>The empty text line.</returns>
private TextLine CreateEmptyTextLine(int startingIndex)
private TextLine CreateEmptyTextLine(int firstTextSourceIndex)
{
var flowDirection = _paragraphProperties.FlowDirection;
var properties = _paragraphProperties.DefaultTextRunProperties;
var glyphTypeface = properties.Typeface.GlyphTypeface;
var text = new ReadOnlySlice<char>(s_empty, startingIndex, 1);
var text = new ReadOnlySlice<char>(s_empty, firstTextSourceIndex, 1);
var glyph = glyphTypeface.GetGlyph(s_empty[0]);
var glyphInfos = new[] { new GlyphInfo(glyph, startingIndex) };
var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex) };
var shapedBuffer = new ShapedBuffer(text, glyphInfos, glyphTypeface, properties.FontRenderingEmSize,
(sbyte)flowDirection);
var textRuns = new List<ShapedTextCharacters> { new ShapedTextCharacters(shapedBuffer, properties) };
var textRange = new TextRange(startingIndex, 1);
var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) };
return new TextLineImpl(textRuns, textRange, MaxWidth, _paragraphProperties, flowDirection).FinalizeLine();
return new TextLineImpl(textRuns, firstTextSourceIndex, 1, MaxWidth, _paragraphProperties, flowDirection).FinalizeLine();
}
private IReadOnlyList<TextLine> CreateTextLines()
@ -576,13 +424,13 @@ namespace Avalonia.Media.TextFormatting
_paragraphProperties, previousLine?.TextLineBreak);
#if DEBUG
if (textLine.TextRange.Length == 0)
if (textLine.Length == 0)
{
throw new InvalidOperationException($"{nameof(textLine)} should not be empty.");
}
#endif
currentPosition += textLine.TextRange.Length;
currentPosition += textLine.Length;
//Fulfill max height constraint
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)

127
src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@ -17,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="prefixLength">Length of leading prefix.</param>
/// <param name="width">width in which collapsing is constrained to</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param>
/// <param name="textRunProperties">text run properties of ellipsis symbol</param>
public TextLeadingPrefixCharacterEllipsis(
ReadOnlySlice<char> ellipsis,
int prefixLength,
@ -35,16 +35,14 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc/>
public sealed override double Width { get; }
public override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
public override TextRun Symbol { get; }
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
public override List<DrawableTextRun>? Collapse(TextLine textLine)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
if (textLine.TextRuns is not List<DrawableTextRun> textRuns || textRuns.Count == 0)
{
return null;
}
@ -55,84 +53,103 @@ namespace Avalonia.Media.TextFormatting
if (Width < shapedSymbol.GlyphRun.Size.Width)
{
return new List<ShapedTextCharacters>(0);
return new List<DrawableTextRun>(0);
}
// Overview of ellipsis structure
// Prefix length run | Ellipsis symbol | Post split run growing from the end |
var availableWidth = Width - shapedSymbol.Size.Width;
while (runIndex < shapedTextRuns.Count)
while (runIndex < textRuns.Count)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
var currentRun = textRuns[runIndex];
if (currentWidth > availableWidth)
switch (currentRun)
{
currentRun.TryMeasureCharacters(availableWidth, out var measuredLength);
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
if (measuredLength > 0)
case ShapedTextCharacters shapedRun:
{
List<ShapedTextCharacters>? preSplitRuns = null;
List<ShapedTextCharacters>? postSplitRuns = null;
currentWidth += currentRun.Size.Width;
if (_prefixLength > 0)
if (currentWidth > availableWidth)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength));
shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength);
shapedTextCharacters.AddRange(splitResult.First);
var collapsedRuns = new List<DrawableTextRun>(textRuns.Count);
TextLineImpl.SortRuns(shapedTextCharacters);
preSplitRuns = splitResult.First;
postSplitRuns = splitResult.Second;
}
else
{
postSplitRuns = shapedTextRuns;
}
if (measuredLength > 0)
{
List<DrawableTextRun>? preSplitRuns = null;
List<DrawableTextRun>? postSplitRuns;
shapedTextCharacters.Add(shapedSymbol);
if (_prefixLength > 0)
{
var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns,
Math.Min(_prefixLength, measuredLength));
if (measuredLength > _prefixLength && postSplitRuns is not null)
{
var availableSuffixWidth = availableWidth;
collapsedRuns.AddRange(splitResult.First);
if (preSplitRuns is not null)
{
foreach (var run in preSplitRuns)
preSplitRuns = splitResult.First;
postSplitRuns = splitResult.Second;
}
else
{
availableSuffixWidth -= run.Size.Width;
postSplitRuns = textRuns;
}
}
for (int i = postSplitRuns.Count - 1; i >= 0; i--)
{
var run = postSplitRuns[i];
collapsedRuns.Add(shapedSymbol);
if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth))
if (measuredLength <= _prefixLength || postSplitRuns is null)
{
availableSuffixWidth -= suffixWidth;
return collapsedRuns;
}
var availableSuffixWidth = availableWidth;
if (suffixCount > 0)
if (preSplitRuns is not null)
{
foreach (var run in preSplitRuns)
{
var splitSuffix = run.Split(run.TextSourceLength - suffixCount);
availableSuffixWidth -= run.Size.Width;
}
}
for (var i = postSplitRuns.Count - 1; i >= 0; i--)
{
var run = postSplitRuns[i];
shapedTextCharacters.Add(splitSuffix.Second!);
switch (run)
{
case ShapedTextCharacters endShapedRun:
{
if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth,
out var suffixCount, out var suffixWidth))
{
availableSuffixWidth -= suffixWidth;
if (suffixCount > 0)
{
var splitSuffix =
endShapedRun.Split(run.TextSourceLength - suffixCount);
collapsedRuns.Add(splitSuffix.Second!);
}
}
break;
}
}
}
}
else
{
collapsedRuns.Add(shapedSymbol);
}
return collapsedRuns;
}
}
else
{
shapedTextCharacters.Add(shapedSymbol);
}
return shapedTextCharacters;
break;
}
}
availableWidth -= currentRun.Size.Width;

18
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@ -16,13 +16,9 @@ namespace Avalonia.Media.TextFormatting
/// </value>
public abstract IReadOnlyList<TextRun> TextRuns { get; }
/// <summary>
/// Gets the text range that is covered by the line.
/// </summary>
/// <value>
/// The text range that is covered by the line.
/// </value>
public abstract TextRange TextRange { get; }
public abstract int FirstTextSourceIndex { get; }
public abstract int Length { get; }
/// <summary>
/// Gets the state of the line when broken by line breaking process.
@ -189,6 +185,14 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The <see cref="CharacterHit"/> after backspacing.</returns>
public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit);
/// <summary>
/// Get an array of bounding rectangles of a range of characters within a text line.
/// </summary>
/// <param name="firstTextSourceCharacterIndex">index of first character of specified range</param>
/// <param name="textLength">number of characters of the specified range</param>
/// <returns>an array of bounding rectangles.</returns>
public abstract IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength);
/// <summary>
/// Gets the text line offset x.
/// </summary>

8
src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs

@ -5,11 +5,11 @@ namespace Avalonia.Media.TextFormatting
public class TextLineBreak
{
public TextLineBreak(TextEndOfLine? textEndOfLine = null, FlowDirection flowDirection = FlowDirection.LeftToRight,
IReadOnlyList<ShapedTextCharacters>? remainingCharacters = null)
IReadOnlyList<DrawableTextRun>? remainingRuns = null)
{
TextEndOfLine = textEndOfLine;
FlowDirection = flowDirection;
RemainingCharacters = remainingCharacters;
RemainingRuns = remainingRuns;
}
/// <summary>
@ -23,8 +23,8 @@ namespace Avalonia.Media.TextFormatting
public FlowDirection FlowDirection { get; }
/// <summary>
/// Get the remaining shaped characters that were split up by the <see cref="TextFormatter"/> during the formatting process.
/// Get the remaining runs that were split up by the <see cref="TextFormatter"/> during the formatting process.
/// </summary>
public IReadOnlyList<ShapedTextCharacters>? RemainingCharacters { get; }
public IReadOnlyList<DrawableTextRun>? RemainingRuns { get; }
}
}

937
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

File diff suppressed because it is too large

8
src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs

@ -63,5 +63,13 @@
{
get { return 0; }
}
/// <summary>
/// Default Incremental Tab
/// </summary>
public virtual double DefaultIncrementalTab
{
get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; }
}
}
}

2
src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs

@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary>
/// Run vertical box alignment
/// </summary>
public abstract BaselineAlignment BaselineAlignment { get; }
public virtual BaselineAlignment BaselineAlignment => BaselineAlignment.Baseline;
public bool Equals(TextRunProperties? other)
{

5
src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs

@ -45,10 +45,9 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc cref="ITextShaperImpl.ShapeText"/>
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo? culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
return _platformImpl.ShapeText(text, typeface, fontRenderingEmSize, culture, bidiLevel);
return _platformImpl.ShapeText(text, options);
}
}
}

49
src/Avalonia.Visuals/Media/TextFormatting/TextShaperOptions.cs

@ -0,0 +1,49 @@
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Options to customize text shaping.
/// </summary>
public readonly struct TextShaperOptions
{
public TextShaperOptions(
GlyphTypeface typeface,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
double incrementalTabWidth = 0)
{
Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;
BidLevel = bidiLevel;
Culture = culture;
IncrementalTabWidth = incrementalTabWidth;
}
/// <summary>
/// Get the typeface.
/// </summary>
public GlyphTypeface Typeface { get; }
/// <summary>
/// Get the font rendering em size.
/// </summary>
public double FontRenderingEmSize { get; }
/// <summary>
/// Get the bidi level of the text.
/// </summary>
public sbyte BidLevel { get; }
/// <summary>
/// Get the culture.
/// </summary>
public CultureInfo? Culture { get; }
/// <summary>
/// Get the incremental tab width.
/// </summary>
public double IncrementalTabWidth { get; }
}
}

8
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs

@ -14,7 +14,7 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="width">Width in which collapsing is constrained to.</param>
/// <param name="textRunProperties">Text run properties of ellispis symbol.</param>
/// <param name="textRunProperties">Text run properties of ellipsis symbol.</param>
public TextTrailingCharacterEllipsis(ReadOnlySlice<char> ellipsis, double width, TextRunProperties textRunProperties)
{
Width = width;
@ -22,12 +22,12 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc/>
public sealed override double Width { get; }
public override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
public override TextRun Symbol { get; }
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
public override List<DrawableTextRun>? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, false);
}

8
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs

@ -14,7 +14,7 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="width">width in which collapsing is constrained to.</param>
/// <param name="textRunProperties">text run properties of ellispis symbol.</param>
/// <param name="textRunProperties">text run properties of ellipsis symbol.</param>
public TextTrailingWordEllipsis(
ReadOnlySlice<char> ellipsis,
double width,
@ -26,12 +26,12 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc/>
public sealed override double Width { get; }
public override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
public override TextRun Symbol { get; }
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
public override List<DrawableTextRun>? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, true);
}

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiAlgorithm.cs

@ -302,7 +302,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// </summary>
/// <param name="data">The data to be evaluated</param>
/// <returns>The resolved embedding level</returns>
public sbyte ResolveEmbeddingLevel(ReadOnlySlice<BidiClass> data)
public sbyte ResolveEmbeddingLevel(ArraySlice<BidiClass> data)
{
// P2
for (var i = 0; i < data.Length; ++i)

13
src/Avalonia.Visuals/Platform/ITextShaperImpl.cs

@ -1,6 +1,4 @@
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Platform
@ -14,11 +12,8 @@ namespace Avalonia.Platform
/// Shapes the specified region within the text and returns a shaped buffer.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="typeface">The typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="culture">The culture.</param>
/// <param name="bidiLevel">The bidi level.</param>
/// <param name="options">Text shaper options to customize the shaping process.</param>
/// <returns>A shaped glyph run.</returns>
ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize, CultureInfo? culture, sbyte bidiLevel);
}
ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options);
}
}

1
src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs

@ -59,6 +59,7 @@ namespace Avalonia.Rendering.SceneGraph
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawEllipse(Brush, Pen, Rect);
}

19
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -13,12 +13,16 @@ namespace Avalonia.Skia
{
internal class TextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var culture = options.Culture;
using (var buffer = new Buffer())
{
buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length);
buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length);
MergeBreakPair(buffer);
@ -61,6 +65,15 @@ namespace Avalonia.Skia
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if(glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
glyphAdvance = options.IncrementalTabWidth > 0 ?
options.IncrementalTabWidth :
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
var targetInfo = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
shapedBuffer[i] = targetInfo;

17
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -14,9 +14,13 @@ namespace Avalonia.Direct2D1.Media
internal class TextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var culture = options.Culture;
using (var buffer = new Buffer())
{
buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length);
@ -62,6 +66,15 @@ internal class TextShaperImpl : ITextShaperImpl
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
if (glyphIndex == 0 && text[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
glyphAdvance = options.IncrementalTabWidth > 0 ?
options.IncrementalTabWidth :
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
var targetInfo =
new Avalonia.Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance,
glyphOffset);

29
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -865,6 +865,35 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Should_Fullfill_MaxLines_Contraint()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "ABC",
MaxLines = 1,
AcceptsReturn= true
};
target.Measure(Size.Infinity);
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>();
var clipboard = AvaloniaLocator.CurrentMutable.GetService<IClipboard>();
clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult();
RaiseTextEvent(target, Environment.NewLine);
Assert.Equal("ABC", target.Text);
}
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),

42
tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json

@ -389,9 +389,9 @@
"dev": true
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true
},
"ansi-styles": {
@ -587,9 +587,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"string-width": {
@ -1333,9 +1333,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mocha": {
@ -1437,9 +1437,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"cliui": {
@ -2117,9 +2117,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"ansi-styles": {
@ -2211,9 +2211,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"find-up": {
@ -2305,9 +2305,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"find-up": {

9
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -16,6 +16,7 @@ using System.ComponentModel;
using System.Linq;
using System.Xml;
using Xunit;
using Avalonia.Controls.Documents;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
@ -47,12 +48,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
public void Attached_Property_Is_Set()
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.FontSize='21'/>";
@"<ContentControl xmlns='https://github.com/avaloniaui' TextElement.FontSize='21'/>";
var target = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.NotNull(target);
Assert.Equal(21.0, TextBlock.GetFontSize(target));
Assert.Equal(21.0, TextElement.GetFontSize(target));
}
[Fact]
@ -90,13 +91,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml =
@"<Window xmlns='https://github.com/avaloniaui' TextBlock.FontSize='{Binding}'/>";
@"<Window xmlns='https://github.com/avaloniaui' TextElement.FontSize='{Binding}'/>";
var target = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
target.DataContext = 21.0;
Assert.Equal(21.0, TextBlock.GetFontSize(target));
Assert.Equal(21.0, TextElement.GetFontSize(target));
}
}

9
tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs

@ -17,8 +17,9 @@ namespace Avalonia.Skia.UnitTests.Media
{
using (Start())
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
TextShaper.Current.ShapeText(text.AsMemory(), Typeface.Default.GlyphTypeface, 10, CultureInfo.CurrentCulture, direction);
TextShaper.Current.ShapeText(text.AsMemory(), options);
var glyphRun = CreateGlyphRun(shapedBuffer);
@ -59,8 +60,9 @@ namespace Avalonia.Skia.UnitTests.Media
{
using (Start())
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
TextShaper.Current.ShapeText(text.AsMemory(), Typeface.Default.GlyphTypeface, 10, CultureInfo.CurrentCulture, direction);
TextShaper.Current.ShapeText(text.AsMemory(), options);
var glyphRun = CreateGlyphRun(shapedBuffer);
@ -103,8 +105,9 @@ namespace Avalonia.Skia.UnitTests.Media
{
using (Start())
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
TextShaper.Current.ShapeText(text.AsMemory(), Typeface.Default.GlyphTypeface, 10, CultureInfo.CurrentCulture, direction);
TextShaper.Current.ShapeText(text.AsMemory(), options);
var glyphRun = CreateGlyphRun(shapedBuffer);

7
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs

@ -20,15 +20,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public TextRun GetTextRun(int textSourceIndex)
{
if (textSourceIndex > 50)
if (textSourceIndex >= 50)
{
return null;
}
if (textSourceIndex == 50)
{
return new TextEndOfParagraph();
}
var index = textSourceIndex / 10;

7
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs

@ -23,13 +23,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
var runText = _text.Skip(textSourceIndex);
if (runText.IsEmpty)
{
return new TextEndOfParagraph();
}
return new TextCharacters(runText, _defaultGenericPropertiesRunProperties);
return runText.IsEmpty ? null : new TextCharacters(runText, _defaultGenericPropertiesRunProperties);
}
}
}

124
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -58,7 +58,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(5, textLine.TextRuns.Count);
Assert.Equal(50, textLine.TextRange.Length);
Assert.Equal(50, textLine.Length);
}
}
@ -89,7 +89,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
Assert.Equal(text.Length, textLine.TextRange.Length);
Assert.Equal(text.Length, textLine.Length);
for (var i = 0; i < GenericTextRunPropertiesRuns.Length; i++)
{
@ -195,10 +195,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
if (text.Length - currentPosition > expectedCharactersPerLine)
{
Assert.Equal(expectedCharactersPerLine, textLine.TextRange.Length);
Assert.Equal(expectedCharactersPerLine, textLine.Length);
}
currentPosition += textLine.TextRange.Length;
currentPosition += textLine.Length;
numberOfLines++;
}
@ -249,16 +249,18 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, currentPosition, paragraphWidth,
new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.Wrap));
Assert.True(expected.Contains(textLine.TextRange.End));
var end = textLine.FirstTextSourceIndex + textLine.Length - 1;
var index = expected.IndexOf(textLine.TextRange.End);
Assert.True(expected.Contains(end));
var index = expected.IndexOf(end);
for (var i = 0; i <= index; i++)
{
expected.RemoveAt(0);
}
currentPosition += textLine.TextRange.Length;
currentPosition += textLine.Length;
}
}
}
@ -312,7 +314,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.True(textLine.Width <= 200);
textSourceIndex += textLine.TextRange.Length;
textSourceIndex += textLine.Length;
}
}
}
@ -336,9 +338,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties);
Assert.NotEqual(0, textLine.TextRange.Length);
Assert.NotEqual(0, textLine.Length);
textSourceIndex += textLine.TextRange.Length;
textSourceIndex += textLine.Length;
}
Assert.Equal(text.Length, textSourceIndex);
@ -383,7 +385,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, currentPosition, 300,
new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow));
currentPosition += textLine.TextRange.Length;
currentPosition += textLine.Length;
if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
{
@ -392,7 +394,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
currentHeight += textLine.Height;
var currentText = text.Substring(textLine.TextRange.Start, textLine.TextRange.Length);
var currentText = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
Assert.Equal(expectedLines[currentLineIndex], currentText);
@ -485,9 +487,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, textPosition, 50, paragraphProperties, lastBreak);
Assert.Equal(textLine.TextRange.Length, textLine.TextRuns.Sum(x => x.TextSourceLength));
Assert.Equal(textLine.Length, textLine.TextRuns.Sum(x => x.TextSourceLength));
textPosition += textLine.TextRange.Length;
textPosition += textLine.Length;
lastBreak = textLine.TextLineBreak;
}
@ -508,7 +510,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 33, paragraphProperties);
Assert.NotNull(textLine.TextLineBreak?.RemainingCharacters);
Assert.NotNull(textLine.TextLineBreak?.RemainingRuns);
}
}
@ -562,6 +564,98 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_FormatLine_With_DrawableRuns()
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
var textSource = new CustomTextSource("Hello World ->");
using (Start())
{
var textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
Assert.Equal(3, textLine.TextRuns.Count);
Assert.True(textLine.TextRuns[1] is RectangleRun);
}
}
[Fact]
public void Should_Format_With_EndOfLineRun()
{
using (Start())
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
var textSource = new EndOfLineTextSource();
var textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
Assert.NotNull(textLine.TextLineBreak);
Assert.Equal(TextRun.DefaultTextSourceLength, textLine.Length);
}
}
private class EndOfLineTextSource : ITextSource
{
public TextRun? GetTextRun(int textSourceIndex)
{
return new TextEndOfLine();
}
}
private class CustomTextSource : ITextSource
{
private readonly string _text;
public CustomTextSource(string text)
{
_text = text;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _text.Length + TextRun.DefaultTextSourceLength + _text.Length)
{
return null;
}
if (textSourceIndex == _text.Length)
{
return new RectangleRun(new Rect(0, 0, 50, 50), Brushes.Green);
}
return new TextCharacters(_text.AsMemory(),
new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black));
}
}
private class RectangleRun : DrawableTextRun
{
private readonly Rect _rect;
private readonly IBrush _fill;
public RectangleRun(Rect rect, IBrush fill)
{
_rect = rect;
_fill = fill;
}
public override Size Size => _rect.Size;
public override double Baseline => 0;
public override void Draw(DrawingContext drawingContext, Point origin)
{
using (drawingContext.PushPreTransform(Matrix.CreateTranslation(new Vector(origin.X, 0))))
{
drawingContext.FillRectangle(_fill, _rect);
}
}
}
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

18
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -88,8 +88,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textWrapping: TextWrapping.Wrap,
maxWidth: 200);
var expectedLines = expected.TextLines.Select(x => text.Substring(x.TextRange.Start,
x.TextRange.Length)).ToList();
var expectedLines = expected.TextLines.Select(x => text.Substring(x.FirstTextSourceIndex,
x.Length)).ToList();
var spans = new[]
{
@ -106,8 +106,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
maxWidth: 200,
textStyleOverrides: spans);
var actualLines = actual.TextLines.Select(x => text.Substring(x.TextRange.Start,
x.TextRange.Length)).ToList();
var actualLines = actual.TextLines.Select(x => text.Substring(x.FirstTextSourceIndex,
x.Length)).ToList();
Assert.Equal(expectedLines.Count, actualLines.Count);
@ -140,7 +140,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
black,
textWrapping: TextWrapping.Wrap);
var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast<ShapedTextCharacters>().SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList();
var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast<ShapedTextCharacters>()
.SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList();
var outer = new GraphemeEnumerator(text.AsMemory());
var inner = new GraphemeEnumerator(text.AsMemory());
@ -172,7 +173,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textWrapping: TextWrapping.Wrap,
textStyleOverrides: spans);
var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast<ShapedTextCharacters>().SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList();
var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast<ShapedTextCharacters>()
.SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList();
Assert.Equal(expectedGlyphs.Count, actualGlyphs.Count);
@ -348,7 +350,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
12.0f,
Brushes.Black.ToImmutable());
Assert.Equal(MultiLineText.Length, layout.TextLines.Sum(x => x.TextRange.Length));
Assert.Equal(MultiLineText.Length, layout.TextLines.Sum(x => x.Length));
}
}
@ -813,7 +815,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.True(textLine.Width <= maxWidth);
var actual = new string(textLine.TextRuns.Cast<ShapedTextCharacters>().OrderBy(x => x.Text.Start).SelectMany(x => x.Text).ToArray());
var expected = text.Substring(textLine.TextRange.Start, textLine.TextRange.Length);
var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
Assert.Equal(expected, actual);
}

221
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -35,9 +35,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue));
Assert.Equal(textLine.TextRange.Start, firstCharacterHit.FirstCharacterIndex);
Assert.Equal(textLine.FirstTextSourceIndex, firstCharacterHit.FirstCharacterIndex);
currentIndex += textLine.TextRange.Length;
currentIndex += textLine.Length;
}
}
}
@ -63,10 +63,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue));
Assert.Equal(textLine.TextRange.Start + textLine.TextRange.Length,
Assert.Equal(textLine.FirstTextSourceIndex + textLine.Length,
lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength);
currentIndex += textLine.TextRange.Length;
currentIndex += textLine.Length;
}
}
}
@ -277,6 +277,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
firstCharacterHit = previousCharacterHit;
firstCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
@ -416,24 +418,160 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
[Fact]
public void TextLineBreak_Should_Contain_TextEndOfLine()
public void Should_Get_Next_CharacterHit_For_Drawable_Runs()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new DrawableRunTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
Assert.Equal(4, textLine.TextRuns.Count);
var currentHit = textLine.GetNextCaretCharacterHit(new CharacterHit(0));
Assert.Equal(1, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetNextCaretCharacterHit(currentHit);
Assert.Equal(2, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetNextCaretCharacterHit(currentHit);
Assert.Equal(3, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetNextCaretCharacterHit(currentHit);
Assert.Equal(3, currentHit.FirstCharacterIndex);
Assert.Equal(1, currentHit.TrailingLength);
}
}
[Fact]
public void Should_Get_Previous_CharacterHit_For_Drawable_Runs()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new DrawableRunTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
Assert.Equal(4, textLine.TextRuns.Count);
var currentHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(3,1));
Assert.Equal(3, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
Assert.Equal(2, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
Assert.Equal(1, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
Assert.Equal(0, currentHit.FirstCharacterIndex);
Assert.Equal(0, currentHit.TrailingLength);
}
}
[Fact]
public void Should_Get_CharacterHit_From_Distance_For_Drawable_Runs()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new DrawableRunTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var characterHit = textLine.GetCharacterHitFromDistance(50);
Assert.Equal(3, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetCharacterHitFromDistance(32);
Assert.Equal(2, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
}
}
[Fact]
public void Should_Get_Distance_From_CharacterHit_Drawable_Runs()
{
using (Start())
{
var defaultTextRunProperties =
new GenericTextRunProperties(Typeface.Default);
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new DrawableRunTextSource();
const string text = "0123456789";
var formatter = new TextFormatterImpl();
var source = new SingleBufferTextSource(text, defaultTextRunProperties);
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textParagraphProperties = new GenericTextParagraphProperties(defaultTextRunProperties);
var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(1));
var formatter = TextFormatter.Current;
Assert.Equal(14, distance);
var textLine = formatter.FormatLine(source, 0, double.PositiveInfinity, textParagraphProperties);
distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(2));
Assert.NotNull(textLine.TextLineBreak.TextEndOfLine);
Assert.True(distance > 14);
}
}
private class DrawableRunTextSource : ITextSource
{
const string Text = "_A_A";
public TextRun? GetTextRun(int textSourceIndex)
{
switch (textSourceIndex)
{
case 0:
return new CustomDrawableRun();
case 1:
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 1, 1, 1), new GenericTextRunProperties(Typeface.Default));
case 2:
return new CustomDrawableRun();
case 3:
return new TextCharacters(new ReadOnlySlice<char>(Text.AsMemory(), 3, 1, 3), new GenericTextRunProperties(Typeface.Default));
default:
return null;
}
}
}
private class CustomDrawableRun : DrawableTextRun
{
public override Size Size => new(14, 14);
public override double Baseline => 14;
public override void Draw(DrawingContext drawingContext, Point origin)
{
}
}
@ -517,6 +655,63 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
return rects;
}
[Fact]
public void Should_Get_TextBounds()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var text = "0123".AsMemory();
var ltrOptions = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture);
var rtlOptions = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 1, CultureInfo.CurrentCulture);
var textRuns = new List<TextRun>
{
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text), ltrOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length, text.Length), ltrOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 2, text.Length), rtlOptions), defaultProperties),
new ShapedTextCharacters(TextShaper.Current.ShapeText(new ReadOnlySlice<char>(text, text.Length * 3, text.Length), ltrOptions), defaultProperties)
};
var textSource = new FixedRunsTextSource(textRuns);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textBounds = textLine.GetTextBounds(0, text.Length * 4);
Assert.Equal(3, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
private class FixedRunsTextSource : ITextSource
{
private readonly IReadOnlyList<TextRun> _textRuns;
public FixedRunsTextSource(IReadOnlyList<TextRun> textRuns)
{
_textRuns = textRuns;
}
public TextRun? GetTextRun(int textSourceIndex)
{
foreach (var textRun in _textRuns)
{
if(textRun.Text.Start == textSourceIndex)
{
return textRun;
}
}
return null;
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

24
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextShaperTests.cs

@ -15,12 +15,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
using (Start())
{
var text = "\n\r\n".AsMemory();
var shapedBuffer = TextShaper.Current.ShapeText(
text,
Typeface.Default.GlyphTypeface,
12,
CultureInfo.CurrentCulture, 0);
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 12,0, CultureInfo.CurrentCulture);
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
Assert.Equal(shapedBuffer.Text.Length, text.Length);
Assert.Equal(shapedBuffer.GlyphClusters.Count, text.Length);
@ -29,7 +25,21 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(1, shapedBuffer.GlyphClusters[2]);
}
}
[Fact]
public void Should_Apply_IncrementalTabWidth()
{
using (Start())
{
var text = "\t".AsMemory();
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 12, 0, CultureInfo.CurrentCulture, 100);
var shapedBuffer = TextShaper.Current.ShapeText(text, options);
Assert.Equal(shapedBuffer.Length, text.Length);
Assert.Equal(100, shapedBuffer.GlyphAdvances[0]);
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

9
tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

@ -1,6 +1,5 @@
using System;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
@ -12,9 +11,13 @@ namespace Avalonia.UnitTests
{
public class HarfBuzzTextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var culture = options.Culture;
using (var buffer = new Buffer())
{
buffer.AddUtf16(text.Buffer.Span, text.Start, text.Length);

11
tests/Avalonia.UnitTests/MockTextShaperImpl.cs

@ -1,6 +1,4 @@
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
@ -9,9 +7,12 @@ namespace Avalonia.UnitTests
{
public class MockTextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, GlyphTypeface typeface, double fontRenderingEmSize,
CultureInfo culture, sbyte bidiLevel)
public ShapedBuffer ShapeText(ReadOnlySlice<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidLevel;
var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
for (var i = 0; i < shapedBuffer.Length;)

Loading…
Cancel
Save