Browse Source

Merge branch 'master' into demoApp/TransitioningContentControl

pull/7769/head
Max Katz 5 years ago
committed by GitHub
parent
commit
18f608f5d8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Avalonia.Controls/ContextMenu.cs
  2. 28
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  3. 2
      src/Avalonia.Controls/TextBlock.cs
  4. 2
      src/Avalonia.Input/MouseDevice.cs
  5. 43
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  6. 8
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  7. 17
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  8. 19
      src/Avalonia.Visuals/Media/FormattedText.cs
  9. 16
      src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs
  10. 23
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  11. 15
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
  12. 18
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
  13. 95
      src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs
  14. 13
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  15. 146
      src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  16. 79
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  17. 26
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
  18. 26
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
  19. 31
      src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs
  20. 18
      src/Avalonia.Visuals/Media/TextNoneTrimming.cs
  21. 36
      src/Avalonia.Visuals/Media/TextTrailingTrimming.cs
  22. 63
      src/Avalonia.Visuals/Media/TextTrimming.cs
  23. 1
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  24. 26
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  25. 10
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  26. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  27. 31
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  28. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  29. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  30. 38
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

6
src/Avalonia.Controls/ContextMenu.cs

@ -351,6 +351,11 @@ namespace Avalonia.Controls
? PlacementMode.Bottom ? PlacementMode.Bottom
: PlacementMode; : PlacementMode;
//Position of the line below is really important.
//All styles are being applied only when control has logical parent.
//Line below will add ContextMenu as child to the Popup and this will trigger styles and they would be applied.
//If you will move line below somewhere else it may cause that ContextMenu will behave differently from what you are expecting.
_popup.Child = this;
_popup.PlacementTarget = placementTarget; _popup.PlacementTarget = placementTarget;
_popup.HorizontalOffset = HorizontalOffset; _popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset; _popup.VerticalOffset = VerticalOffset;
@ -359,7 +364,6 @@ namespace Avalonia.Controls
_popup.PlacementGravity = PlacementGravity; _popup.PlacementGravity = PlacementGravity;
_popup.PlacementRect = PlacementRect; _popup.PlacementRect = PlacementRect;
_popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint; _popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint;
_popup.Child = this;
IsOpen = true; IsOpen = true;
_popup.IsOpen = true; _popup.IsOpen = true;

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

@ -2,6 +2,8 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
@ -58,18 +60,17 @@ namespace Avalonia.Controls.Primitives
private Vector _offset; private Vector _offset;
private bool _hasInit; private bool _hasInit;
private bool _suppressUpdateOffset; private bool _suppressUpdateOffset;
private ListBoxItem? _pressedItem;
public DateTimePickerPanel() public DateTimePickerPanel()
{ {
FormatDate = DateTime.Now; FormatDate = DateTime.Now;
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble); AddHandler(TappedEvent, OnItemTapped, RoutingStrategies.Bubble);
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble);
} }
static DateTimePickerPanel() static DateTimePickerPanel()
{ {
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true); FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true);
BackgroundProperty.OverrideDefaultValue<DateTimePickerPanel>(Brushes.Transparent);
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty); AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty);
} }
@ -523,26 +524,13 @@ namespace Avalonia.Controls.Primitives
return newValue; return newValue;
} }
private void OnItemPointerDown(object? sender, PointerPressedEventArgs e) private void OnItemTapped(object? sender, TappedEventArgs e)
{ {
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && if (e.Source is IVisual source &&
e.Source is IVisual source) GetItemFromSource(source) is ListBoxItem listBoxItem &&
{ listBoxItem.Tag is int tag)
_pressedItem = GetItemFromSource(source);
e.Handled = true;
}
}
private void OnItemPointerUp(object? sender, PointerReleasedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased &&
_pressedItem != null &&
e.Source is IVisual source &&
GetItemFromSource(source) is ListBoxItem item &&
item.Tag is int tag)
{ {
SelectedValue = tag; SelectedValue = tag;
_pressedItem = null;
e.Handled = true; e.Handled = true;
} }
} }

2
src/Avalonia.Controls/TextBlock.cs

@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="TextTrimming"/> property. /// Defines the <see cref="TextTrimming"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextTrimming> TextTrimmingProperty = public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming)); AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming), defaultValue: TextTrimming.None);
/// <summary> /// <summary>
/// Defines the <see cref="TextDecorations"/> property. /// Defines the <see cref="TextDecorations"/> property.

2
src/Avalonia.Input/MouseDevice.cs

@ -147,6 +147,8 @@ namespace Avalonia.Input
if(mouse._disposed) if(mouse._disposed)
return; return;
if (e.Type == RawPointerEventType.NonClientLeftButtonDown) return;
_position = e.Root.PointToScreen(e.Position); _position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e); var props = CreateProperties(e);
var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers); var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers);

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

@ -101,23 +101,17 @@
IsVisible="False" IsVisible="False"
HorizontalAlignment="Right" /> HorizontalAlignment="Right" />
<Viewbox UseLayoutRounding="False" <PathIcon x:Name="DropDownGlyph"
MinHeight="{DynamicResource ComboBoxMinHeight}" Grid.Row="1"
Grid.Row="1" Grid.Column="1"
Grid.Column="1" UseLayoutRounding="False"
IsHitTestVisible="False" IsHitTestVisible="False"
Margin="0,0,10,0" Height="12"
Height="12" Width="12"
Width="12" Margin="0,0,10,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center">
<Panel>
<Panel Height="12"
Width="12" />
<Path x:Name="DropDownGlyph"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</Panel>
</Viewbox>
<Popup Name="PART_Popup" <Popup Name="PART_Popup"
WindowManagerAddShadowHint="False" WindowManagerAddShadowHint="False"
IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}" IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
@ -159,10 +153,9 @@
<Setter Property="IsVisible" Value="False" /> <Setter Property="IsVisible" Value="False" />
</Style> </Style>
<Style Selector="ComboBox /template/ Path#DropDownGlyph"> <Style Selector="ComboBox /template/ PathIcon#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
<Setter Property="Data" Value="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" /> <Setter Property="Data" Value="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
<Setter Property="Stretch" Value="Uniform" />
</Style> </Style>
<!-- PointerOver State --> <!-- PointerOver State -->
@ -195,8 +188,8 @@
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style> </Style>
<Style Selector="ComboBox:disabled /template/ Path#DropDownGlyph"> <Style Selector="ComboBox:disabled /template/ PathIcon#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" />
</Style> </Style>
<!-- Focused State --> <!-- Focused State -->
@ -213,8 +206,8 @@
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style> </Style>
<Style Selector="ComboBox:focus-visible /template/ Path#DropDownGlyph"> <Style Selector="ComboBox:focus-visible /template/ PathIcon#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocused}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocused}" />
</Style> </Style>
<!-- Focus Pressed State --> <!-- Focus Pressed State -->
@ -226,8 +219,8 @@
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" />
</Style> </Style>
<Style Selector="ComboBox:focused:pressed /template/ Path#DropDownGlyph"> <Style Selector="ComboBox:focused:pressed /template/ PathIcon#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" />
</Style> </Style>
<!-- Error State --> <!-- Error State -->

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

@ -1,4 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20">
<TextBox Text="Sample" <TextBox Text="Sample"
@ -121,7 +123,7 @@
</Style> </Style>
<!-- PointerOver State--> <!-- PointerOver State-->
<Style Selector="TextBox:disabled"> <Style Selector="TextBox:pointerover">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundPointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForegroundPointerOver}" />
</Style> </Style>
@ -168,7 +170,7 @@
<Setter Property="InnerRightContent"> <Setter Property="InnerRightContent">
<Template> <Template>
<ToggleButton Classes="passwordBoxRevealButton" <ToggleButton Classes="passwordBoxRevealButton"
IsChecked="{CompiledBinding $parent[TextBox].RevealPassword, Mode=TwoWay}" /> IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
</Template> </Template>
</Setter> </Setter>
</Style> </Style>

17
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -52,6 +52,12 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult..ctor()'
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsInside.set(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsInside.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTrailing.set(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTrailing.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract.
CannotMakeTypeAbstract : Type 'Avalonia.Media.TextTrimming' is abstract in the implementation but is not abstract in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.TextTrimming' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.CharacterEllipsis' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.None' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.WordEllipsis' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Int32 System.Int32 Avalonia.Media.TextTrimming.value__' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Double)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Double)' does not exist in the implementation but it does exist in the contract.
@ -79,6 +85,9 @@ 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. 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. 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. 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.
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. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextEndOfLine..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout..ctor(System.String, Avalonia.Media.Typeface, System.Double, Avalonia.Media.IBrush, Avalonia.Media.TextAlignment, Avalonia.Media.TextWrapping, Avalonia.Media.TextTrimming, Avalonia.Media.TextDecorationCollection, System.Double, System.Double, System.Double, System.Int32, System.Collections.Generic.IReadOnlyList<Avalonia.Utilities.ValueSpan<Avalonia.Media.TextFormatting.TextRunProperties>>)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout..ctor(System.String, Avalonia.Media.Typeface, System.Double, Avalonia.Media.IBrush, Avalonia.Media.TextAlignment, Avalonia.Media.TextWrapping, Avalonia.Media.TextTrimming, Avalonia.Media.TextDecorationCollection, System.Double, System.Double, System.Double, System.Int32, System.Collections.Generic.IReadOnlyList<Avalonia.Utilities.ValueSpan<Avalonia.Media.TextFormatting.TextRunProperties>>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
@ -124,6 +133,12 @@ CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextForma
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' 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. 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. 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.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingWordEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingWordEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingWordEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
@ -161,4 +176,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR
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 '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 '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. 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: 162 Total Issues: 177

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

@ -854,19 +854,9 @@ namespace Avalonia.Media
var lastRunProps = (GenericTextRunProperties)thatFormatRider.CurrentElement!; var lastRunProps = (GenericTextRunProperties)thatFormatRider.CurrentElement!;
TextCollapsingProperties trailingEllipsis; TextCollapsingProperties collapsingProperties = _that._trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(maxLineLength, lastRunProps));
if (_that._trimming == TextTrimming.CharacterEllipsis) var collapsedLine = line.Collapse(collapsingProperties);
{
trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps);
}
else
{
Debug.Assert(_that._trimming == TextTrimming.WordEllipsis);
trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps);
}
var collapsedLine = line.Collapse(trailingEllipsis);
line = collapsedLine; line = collapsedLine;
} }
@ -1121,11 +1111,6 @@ namespace Avalonia.Media
{ {
set set
{ {
if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis)
{
throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(TextTrimming));
}
_trimming = value; _trimming = value;
_defaultParaProps.SetTextWrapping(_trimming == TextTrimming.None ? _defaultParaProps.SetTextWrapping(_trimming == TextTrimming.None ?

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

@ -0,0 +1,16 @@
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
public readonly struct TextCollapsingCreateInfo
{
public readonly double Width;
public readonly TextRunProperties TextRunProperties;
public TextCollapsingCreateInfo(double width, TextRunProperties textRunProperties)
{
Width = width;
TextRunProperties = textRunProperties;
}
}
}

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

@ -132,6 +132,29 @@ namespace Avalonia.Media.TextFormatting
return length > 0; return length > 0;
} }
internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width)
{
length = 0;
width = 0;
for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
{
var advance = ShapedBuffer.GlyphAdvances[i];
if (width + advance > availableWidth)
{
break;
}
Codepoint.ReadAt(GlyphRun.Characters, length, out var count);
length += count;
width += advance;
}
return length > 0;
}
internal SplitResult<ShapedTextCharacters> Split(int length) internal SplitResult<ShapedTextCharacters> Split(int length)
{ {
if (IsReversed) if (IsReversed)

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

@ -1,23 +1,26 @@
namespace Avalonia.Media.TextFormatting using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// Properties of text collapsing /// Properties of text collapsing.
/// </summary> /// </summary>
public abstract class TextCollapsingProperties public abstract class TextCollapsingProperties
{ {
/// <summary> /// <summary>
/// Gets the width in which the collapsible range is constrained to /// Gets the width in which the collapsible range is constrained to.
/// </summary> /// </summary>
public abstract double Width { get; } public abstract double Width { get; }
/// <summary> /// <summary>
/// Gets the text run that is used as collapsing symbol /// Gets the text run that is used as collapsing symbol.
/// </summary> /// </summary>
public abstract TextRun Symbol { get; } public abstract TextRun Symbol { get; }
/// <summary> /// <summary>
/// Gets the style of collapsing /// Collapses given text line.
/// </summary> /// </summary>
public abstract TextCollapsingStyle Style { get; } /// <param name="textLine">Text line to collapse.</param>
public abstract IReadOnlyList<TextRun>? Collapse(TextLine textLine);
} }
} }

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

@ -1,18 +0,0 @@
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Text collapsing style
/// </summary>
public enum TextCollapsingStyle
{
/// <summary>
/// Collapse trailing characters
/// </summary>
TrailingCharacter,
/// <summary>
/// Collapse trailing words
/// </summary>
TrailingWord,
}
}

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

@ -0,0 +1,95 @@
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
namespace Avalonia.Media.TextFormatting
{
internal class TextEllipsisHelper
{
public static List<ShapedTextCharacters>? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
{
return null;
}
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);
}
var availableWidth = properties.Width - shapedSymbol.Size.Width;
while (runIndex < shapedTextRuns.Count)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth)
{
if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
{
if (isWordEllipsis && measuredLength < textRange.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition >= measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
collapsedLength += measuredLength;
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, collapsedLength);
shapedTextCharacters.AddRange(splitResult.First);
TextLineImpl.SortRuns(shapedTextCharacters);
}
shapedTextCharacters.Add(shapedSymbol);
return shapedTextCharacters;
}
availableWidth -= currentRun.Size.Width;
collapsedLength += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
return null;
}
}
}

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

@ -41,7 +41,7 @@ namespace Avalonia.Media.TextFormatting
IBrush? foreground, IBrush? foreground,
TextAlignment textAlignment = TextAlignment.Left, TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap, TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming textTrimming = TextTrimming.None, TextTrimming? textTrimming = null,
TextDecorationCollection? textDecorations = null, TextDecorationCollection? textDecorations = null,
FlowDirection flowDirection = FlowDirection.LeftToRight, FlowDirection flowDirection = FlowDirection.LeftToRight,
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
@ -58,7 +58,7 @@ namespace Avalonia.Media.TextFormatting
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, flowDirection, lineHeight); textDecorations, flowDirection, lineHeight);
_textTrimming = textTrimming; _textTrimming = textTrimming ?? TextTrimming.None;
_textStyleOverrides = textStyleOverrides; _textStyleOverrides = textStyleOverrides;
@ -641,14 +641,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The <see cref="TextCollapsingProperties"/>.</returns> /// <returns>The <see cref="TextCollapsingProperties"/>.</returns>
private TextCollapsingProperties GetCollapsingProperties(double width) private TextCollapsingProperties GetCollapsingProperties(double width)
{ {
return _textTrimming switch return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
{
TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width,
_paragraphProperties.DefaultTextRunProperties),
TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width,
_paragraphProperties.DefaultTextRunProperties),
_ => throw new ArgumentOutOfRangeException(),
};
} }
} }
} }

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

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Ellipsis based on a fixed length leading prefix and suffix growing from the end at character granularity.
/// </summary>
public sealed class TextLeadingPrefixCharacterEllipsis : TextCollapsingProperties
{
private readonly int _prefixLength;
/// <summary>
/// Construct a text trailing word ellipsis collapsing properties.
/// </summary>
/// <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>
public TextLeadingPrefixCharacterEllipsis(
ReadOnlySlice<char> ellipsis,
int prefixLength,
double width,
TextRunProperties textRunProperties)
{
if (_prefixLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(prefixLength));
}
_prefixLength = prefixLength;
Width = width;
Symbol = new TextCharacters(ellipsis, textRunProperties);
}
/// <inheritdoc/>
public sealed override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
{
return null;
}
var runIndex = 0;
var currentWidth = 0.0;
var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
if (Width < shapedSymbol.GlyphRun.Size.Width)
{
return new List<ShapedTextCharacters>(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)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth)
{
currentRun.TryMeasureCharacters(availableWidth, out var measuredLength);
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
if (measuredLength > 0)
{
List<ShapedTextCharacters>? preSplitRuns = null;
List<ShapedTextCharacters>? postSplitRuns = null;
if (_prefixLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength));
shapedTextCharacters.AddRange(splitResult.First);
TextLineImpl.SortRuns(shapedTextCharacters);
preSplitRuns = splitResult.First;
postSplitRuns = splitResult.Second;
}
else
{
postSplitRuns = shapedTextRuns;
}
shapedTextCharacters.Add(shapedSymbol);
if (measuredLength > _prefixLength && postSplitRuns is not null)
{
var availableSuffixWidth = availableWidth;
if (preSplitRuns is not null)
{
foreach (var run in preSplitRuns)
{
availableSuffixWidth -= run.Size.Width;
}
}
for (int i = postSplitRuns.Count - 1; i >= 0; i--)
{
var run = postSplitRuns[i];
if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth))
{
availableSuffixWidth -= suffixWidth;
if (suffixCount > 0)
{
var splitSuffix = run.Split(run.TextSourceLength - suffixCount);
shapedTextCharacters.Add(splitSuffix.Second!);
}
}
}
}
}
else
{
shapedTextCharacters.Add(shapedSymbol);
}
return shapedTextCharacters;
}
availableWidth -= currentRun.Size.Width;
runIndex++;
}
return null;
}
}
}

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

@ -106,85 +106,18 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0]; var collapsingProperties = collapsingPropertiesList[0];
var runIndex = 0; var collapsedRuns = collapsingProperties.Collapse(this);
var currentWidth = 0.0;
var textRange = TextRange;
var collapsedLength = 0;
var shapedSymbol = TextFormatterImpl.CreateSymbol(collapsingProperties.Symbol, _paragraphProperties.FlowDirection); if (collapsedRuns is List<ShapedTextCharacters> shapedRuns)
if (collapsingProperties.Width < shapedSymbol.GlyphRun.Size.Width)
{ {
return new TextLineImpl(new List<ShapedTextCharacters>(0), textRange, _paragraphWidth, _paragraphProperties, var collapsedLine = new TextLineImpl(shapedRuns, TextRange, _paragraphWidth, _paragraphProperties, _flowDirection, TextLineBreak, true);
_flowDirection, TextLineBreak, true);
}
var availableWidth = collapsingProperties.Width - shapedSymbol.GlyphRun.Size.Width;
while (runIndex < _textRuns.Count)
{
var currentRun = _textRuns[runIndex];
currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth) if (shapedRuns.Count > 0)
{ {
if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength)) collapsedLine.FinalizeLine();
{
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord &&
measuredLength < textRange.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition >= measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
collapsedLength += measuredLength;
var shapedTextCharacters = new List<ShapedTextCharacters>(_textRuns.Count);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(_textRuns, collapsedLength);
shapedTextCharacters.AddRange(splitResult.First);
SortRuns(shapedTextCharacters);
}
shapedTextCharacters.Add(shapedSymbol);
var textLine = new TextLineImpl(shapedTextCharacters, textRange, _paragraphWidth, _paragraphProperties,
_flowDirection, TextLineBreak, true);
return textLine.FinalizeLine();
} }
availableWidth -= currentRun.Size.Width; return collapsedLine;
collapsedLength += currentRun.GlyphRun.Characters.Length;
runIndex++;
} }
return this; return this;

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

@ -1,24 +1,24 @@
using Avalonia.Utilities; using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// a collapsing properties to collapse whole line toward the end /// A collapsing properties to collapse whole line toward the end
/// at character granularity and with ellipsis being the collapsing symbol /// at character granularity.
/// </summary> /// </summary>
public class TextTrailingCharacterEllipsis : TextCollapsingProperties public sealed class TextTrailingCharacterEllipsis : TextCollapsingProperties
{ {
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <summary> /// <summary>
/// Construct a text trailing character ellipsis collapsing properties /// Construct a text trailing character ellipsis collapsing properties
/// </summary> /// </summary>
/// <param name="width">width in which collapsing is constrained to</param> /// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param> /// <param name="width">Width in which collapsing is constrained to.</param>
public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties) /// <param name="textRunProperties">Text run properties of ellispis symbol.</param>
public TextTrailingCharacterEllipsis(ReadOnlySlice<char> ellipsis, double width, TextRunProperties textRunProperties)
{ {
Width = width; Width = width;
Symbol = new TextCharacters(s_ellipsis, textRunProperties); Symbol = new TextCharacters(ellipsis, textRunProperties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -27,7 +27,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public sealed override TextRun Symbol { get; } public sealed override TextRun Symbol { get; }
/// <inheritdoc/> public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter; {
return TextEllipsisHelper.Collapse(textLine, this, false);
}
} }
} }

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

@ -1,37 +1,39 @@
using Avalonia.Utilities; using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// a collapsing properties to collapse whole line toward the end /// a collapsing properties to collapse whole line toward the end
/// at word granularity and with ellipsis being the collapsing symbol /// at word granularity.
/// </summary> /// </summary>
public class TextTrailingWordEllipsis : TextCollapsingProperties public sealed class TextTrailingWordEllipsis : TextCollapsingProperties
{ {
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <summary> /// <summary>
/// Construct a text trailing word ellipsis collapsing properties /// Construct a text trailing word ellipsis collapsing properties.
/// </summary> /// </summary>
/// <param name="width">width in which collapsing is constrained to</param> /// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param> /// <param name="width">width in which collapsing is constrained to.</param>
/// <param name="textRunProperties">text run properties of ellispis symbol.</param>
public TextTrailingWordEllipsis( public TextTrailingWordEllipsis(
ReadOnlySlice<char> ellipsis,
double width, double width,
TextRunProperties textRunProperties TextRunProperties textRunProperties
) )
{ {
Width = width; Width = width;
Symbol = new TextCharacters(s_ellipsis, textRunProperties); Symbol = new TextCharacters(ellipsis, textRunProperties);
} }
/// <inheritdoc/> /// <inheritdoc/>
public sealed override double Width { get; } public sealed override double Width { get; }
/// <inheritdoc/> /// <inheritdoc/>
public sealed override TextRun Symbol { get; } public sealed override TextRun Symbol { get; }
/// <inheritdoc/> public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord; {
return TextEllipsisHelper.Collapse(textLine, this, true);
}
} }
} }

31
src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs

@ -0,0 +1,31 @@
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Media
{
public sealed class TextLeadingPrefixTrimming : TextTrimming
{
private readonly ReadOnlySlice<char> _ellipsis;
private readonly int _prefixLength;
public TextLeadingPrefixTrimming(char ellipsis, int prefixLength) : this(new[] { ellipsis }, prefixLength)
{
}
public TextLeadingPrefixTrimming(char[] ellipsis, int prefixLength)
{
_prefixLength = prefixLength;
_ellipsis = new ReadOnlySlice<char>(ellipsis);
}
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
return new TextLeadingPrefixCharacterEllipsis(_ellipsis, _prefixLength, createInfo.Width, createInfo.TextRunProperties);
}
public override string ToString()
{
return nameof(PrefixCharacterEllipsis);
}
}
}

18
src/Avalonia.Visuals/Media/TextNoneTrimming.cs

@ -0,0 +1,18 @@
using System;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
internal sealed class TextNoneTrimming : TextTrimming
{
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
throw new NotSupportedException();
}
public override string ToString()
{
return nameof(None);
}
}
}

36
src/Avalonia.Visuals/Media/TextTrailingTrimming.cs

@ -0,0 +1,36 @@
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Media
{
public sealed class TextTrailingTrimming : TextTrimming
{
private readonly ReadOnlySlice<char> _ellipsis;
private readonly bool _isWordBased;
public TextTrailingTrimming(char ellipsis, bool isWordBased) : this(new[] {ellipsis}, isWordBased)
{
}
public TextTrailingTrimming(char[] ellipsis, bool isWordBased)
{
_isWordBased = isWordBased;
_ellipsis = new ReadOnlySlice<char>(ellipsis);
}
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
if (_isWordBased)
{
return new TextTrailingWordEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties);
}
return new TextTrailingCharacterEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties);
}
public override string ToString()
{
return _isWordBased ? nameof(WordEllipsis) : nameof(CharacterEllipsis);
}
}
}

63
src/Avalonia.Visuals/Media/TextTrimming.cs

@ -1,23 +1,76 @@
namespace Avalonia.Media using System;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{ {
/// <summary> /// <summary>
/// Describes how text is trimmed when it overflows. /// Describes how text is trimmed when it overflows.
/// </summary> /// </summary>
public enum TextTrimming public abstract class TextTrimming
{ {
public static char s_defaultEllipsisChar = '\u2026';
/// <summary> /// <summary>
/// Text is not trimmed. /// Text is not trimmed.
/// </summary> /// </summary>
None, public static TextTrimming None { get; } = new TextNoneTrimming();
/// <summary> /// <summary>
/// Text is trimmed at a character boundary. An ellipsis (...) is drawn in place of remaining text. /// Text is trimmed at a character boundary. An ellipsis (...) is drawn in place of remaining text.
/// </summary> /// </summary>
CharacterEllipsis, public static TextTrimming CharacterEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, false);
/// <summary> /// <summary>
/// Text is trimmed at a word boundary. An ellipsis (...) is drawn in place of remaining text. /// Text is trimmed at a word boundary. An ellipsis (...) is drawn in place of remaining text.
/// </summary> /// </summary>
WordEllipsis public static TextTrimming WordEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, true);
/// <summary>
/// Text is trimmed after a given prefix length. An ellipsis (...) is drawn in between prefix and suffix and represents remaining text.
/// </summary>
public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8);
/// <summary>
/// Text is trimmed at a character boundary starting from the beginning. An ellipsis (...) is drawn in place of remaining text.
/// </summary>
public static TextTrimming LeadingCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 0);
/// <summary>
/// Creates properties that will be used for collapsing lines of text.
/// </summary>
/// <param name="createInfo">Contextual info about text that will be collapsed.</param>
public abstract TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo);
/// <summary>
/// Parses a text trimming string. Names must match static properties defined in this class.
/// </summary>
/// <param name="s">The text trimming string.</param>
/// <returns>The <see cref="TextTrimming"/>.</returns>
public static TextTrimming Parse(string s)
{
bool Matches(string name)
{
return name.Equals(s, StringComparison.OrdinalIgnoreCase);
}
if (Matches(nameof(None)))
{
return None;
}
if (Matches(nameof(CharacterEllipsis)))
{
return CharacterEllipsis;
}
else if (Matches(nameof(WordEllipsis)))
{
return WordEllipsis;
}
else if (Matches(nameof(PrefixCharacterEllipsis)))
{
return PrefixCharacterEllipsis;
}
throw new FormatException($"Invalid text trimming string: '{s}'.");
}
} }
} }

1
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -11,6 +11,7 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
public interface IPlatformRenderInterface public interface IPlatformRenderInterface
{ {
/// <summary>
/// Creates an ellipse geometry implementation. /// Creates an ellipse geometry implementation.
/// </summary> /// </summary>
/// <param name="rect">The bounds of the ellipse.</param> /// <param name="rect">The bounds of the ellipse.</param>

26
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -221,6 +221,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
} }
} }
if (type.Equals(types.TextTrimming))
{
foreach (var property in types.TextTrimming.Properties)
{
if (property.PropertyType == types.TextTrimming && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase))
{
result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty<IXamlAstValueNode>());
return true;
}
}
}
if (type.Equals(types.TextDecorationCollection))
{
foreach (var property in types.TextDecorations.Properties)
{
if (property.PropertyType == types.TextDecorationCollection && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase))
{
result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty<IXamlAstValueNode>());
return true;
}
}
}
result = null; result = null;
return false; return false;
} }

10
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -117,7 +117,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return parentDataContextNode.DataContextType; return parentDataContextNode.DataContextType;
}; };
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType()); var selfType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType();
// When using self bindings with setters we need to change target type to resolved selector type.
if (context.GetAvaloniaTypes().ISetter.IsAssignableFrom(selfType))
{
selfType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().First().TargetType.GetClrType();
}
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, selfType);
} }
return node; return node;

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -88,6 +88,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ImmutableSolidColorBrush { get; } public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public IXamlType TypeUtilities { get; } public IXamlType TypeUtilities { get; }
public IXamlType TextDecorationCollection { get; }
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -193,6 +197,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt }); ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities"); TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities");
TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection");
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
} }
} }

31
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -1064,6 +1064,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
} }
} }
[Fact]
public void Binds_To_Self_In_Style()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='IsVisible' Value='{CompiledBinding $self.IsEnabled}' />
</Style>
</Window.Styles>
<Button Name='button' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(button.IsVisible);
button.IsEnabled = false;
Assert.False(button.IsVisible);
}
}
[Fact] [Fact]
public void SupportsMethodBindingAsDelegate() public void SupportsMethodBindingAsDelegate()
{ {

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@ -183,6 +183,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
} }
} }
[Fact]
public void Binding_To_Self_In_Style_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='IsVisible' Value='{Binding $self.IsEnabled}' />
</Style>
</Window.Styles>
<Button Name='button' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(button.IsVisible);
button.IsEnabled = false;
Assert.False(button.IsVisible);
}
}
[Fact] [Fact]
public void Stream_Binding_To_Observable_Works() public void Stream_Binding_To_Observable_Works()
{ {

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

@ -387,7 +387,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
if (textLine.Width > 300 || currentHeight + textLine.Height > 240) if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
{ {
textLine = textLine.Collapse(new TextTrailingWordEllipsis(300, defaultProperties)); textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice<char>(new[] {TextTrimming.s_defaultEllipsisChar}), 300, defaultProperties));
} }
currentHeight += textLine.Height; currentHeight += textLine.Height;

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

@ -360,12 +360,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
[InlineData("01234 01234", 58, TextCollapsingStyle.TrailingCharacter, "01234 0\u2026")] public static IEnumerable<object[]> CollapsingData
[InlineData("01234 01234", 58, TextCollapsingStyle.TrailingWord, "01234\u2026")] {
[InlineData("01234", 9, TextCollapsingStyle.TrailingCharacter, "\u2026")] get
[InlineData("01234", 2, TextCollapsingStyle.TrailingCharacter, "")] {
yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixCharacterEllipsis, "01234 01\u20264 01234");
yield return CreateData("01234 01234", 58, TextTrimming.CharacterEllipsis, "01234 0\u2026");
yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026");
yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026");
yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, "");
object[] CreateData(string text, double width, TextTrimming mode, string expected)
{
return new object[]
{
text, width, mode, expected
};
}
}
}
[MemberData(nameof(CollapsingData))]
[Theory] [Theory]
public void Should_Collapse_Line(string text, double width, TextCollapsingStyle style, string expected) public void Should_Collapse_Line(string text, double width, TextTrimming trimming, string expected)
{ {
using (Start()) using (Start())
{ {
@ -381,16 +398,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.False(textLine.HasCollapsed); Assert.False(textLine.HasCollapsed);
TextCollapsingProperties collapsingProperties; TextCollapsingProperties collapsingProperties = trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, defaultProperties));
if (style == TextCollapsingStyle.TrailingCharacter)
{
collapsingProperties = new TextTrailingCharacterEllipsis(width, defaultProperties);
}
else
{
collapsingProperties = new TextTrailingWordEllipsis(width, defaultProperties);
}
var collapsedLine = textLine.Collapse(collapsingProperties); var collapsedLine = textLine.Collapse(collapsingProperties);

Loading…
Cancel
Save