Skip to content

Commit

Permalink
Merge pull request #6953 from Gillibald/feature/textBlockInlines
Browse files Browse the repository at this point in the history
Add TextBlock inlines support
  • Loading branch information
Gillibald authored Feb 24, 2022
2 parents 781f9e5 + cce20bc commit 237a9a4
Show file tree
Hide file tree
Showing 22 changed files with 786 additions and 21 deletions.
11 changes: 11 additions & 0 deletions samples/ControlCatalog/Pages/TextBlockPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@
<TextBlock Text="👪 👨‍👩‍👧 👨‍👩‍👧‍👦" />
</StackPanel>
</Border>
<Border>
<TextBlock Margin="10" TextWrapping="Wrap">
This <Span FontWeight="Bold">is</Span> a
<Span Background="Silver" Foreground="Maroon">TextBlock</Span>
with <Span TextDecorations="Underline">several</Span>
<Span FontStyle="Italic">Span</Span> elements,
<Span Foreground="Blue">
using a <Bold>variety</Bold> of <Italic>styles</Italic>
</Span>.
</TextBlock>
</Border>
</WrapPanel>
</StackPanel>
</UserControl>
10 changes: 10 additions & 0 deletions src/Avalonia.Base/Metadata/TrimSurroundingWhitespaceAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Avalonia.Metadata
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TrimSurroundingWhitespaceAttribute : Attribute
{

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Avalonia.Metadata
{
/// <summary>
/// Indicates that a collection type should be processed as being whitespace significant by a XAML processor.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class WhitespaceSignificantCollectionAttribute : Attribute
{
}
}
17 changes: 17 additions & 0 deletions src/Avalonia.Controls/Documents/Bold.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Avalonia.Media;

namespace Avalonia.Controls.Documents
{
/// <summary>
/// Bold element - markup helper for indicating bolded content.
/// Equivalent to a Span with FontWeight property set to FontWeights.Bold.
/// Can contain other inline elements.
/// </summary>
public sealed class Bold : Span
{
static Bold()
{
FontWeightProperty.OverrideDefaultValue<Bold>(FontWeight.Bold);
}
}
}
71 changes: 71 additions & 0 deletions src/Avalonia.Controls/Documents/Inline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Text;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;

namespace Avalonia.Controls.Documents
{
/// <summary>
/// Inline element.
/// </summary>
public abstract class Inline : TextElement
{
/// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary>
public static readonly StyledProperty<TextDecorationCollection> TextDecorationsProperty =
AvaloniaProperty.Register<Inline, TextDecorationCollection>(
nameof(TextDecorations));

/// <summary>
/// AvaloniaProperty for <see cref="BaselineAlignment" /> property.
/// </summary>
public static readonly StyledProperty<BaselineAlignment> BaselineAlignmentProperty =
AvaloniaProperty.Register<Inline, BaselineAlignment>(
nameof(BaselineAlignment),
BaselineAlignment.Baseline);

/// <summary>
/// The TextDecorations property specifies decorations that are added to the text of an element.
/// </summary>
public TextDecorationCollection TextDecorations
{
get { return GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
}

/// <summary>
/// Describes how the baseline for a text-based element is positioned on the vertical axis,
/// relative to the established baseline for text.
/// </summary>
public BaselineAlignment BaselineAlignment
{
get { return GetValue(BaselineAlignmentProperty); }
set { SetValue(BaselineAlignmentProperty, value); }
}

internal abstract int BuildRun(StringBuilder stringBuilder, IList<ValueSpan<TextRunProperties>> textStyleOverrides, int firstCharacterIndex);

internal abstract int AppendText(StringBuilder stringBuilder);

protected TextRunProperties CreateTextRunProperties()
{
return new GenericTextRunProperties(new Typeface(FontFamily, FontStyle, FontWeight), FontSize,
TextDecorations, Foreground, Background, BaselineAlignment);
}

protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

switch (change.Property.Name)
{
case nameof(TextDecorations):
case nameof(BaselineAlignment):
Invalidate();
break;
}
}
}
}
123 changes: 123 additions & 0 deletions src/Avalonia.Controls/Documents/InlineCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Text;
using Avalonia.Collections;
using Avalonia.LogicalTree;
using Avalonia.Metadata;

namespace Avalonia.Controls.Documents
{
/// <summary>
/// A collection of <see cref="Inline"/>s.
/// </summary>
[WhitespaceSignificantCollection]
public class InlineCollection : AvaloniaList<Inline>
{
private string? _text = string.Empty;

/// <summary>
/// Initializes a new instance of the <see cref="InlineCollection"/> class.
/// </summary>
public InlineCollection(ILogical parent) : base(0)
{
ResetBehavior = ResetBehavior.Remove;

this.ForEachItem(
x =>
{
((ISetLogicalParent)x).SetParent(parent);
x.Invalidated += Invalidate;
Invalidate();
},
x =>
{
((ISetLogicalParent)x).SetParent(null);
x.Invalidated -= Invalidate;
Invalidate();
},
() => throw new NotSupportedException());
}

public bool HasComplexContent => Count > 0;

/// <summary>
/// Gets or adds the text held by the inlines collection.
/// <remarks>
/// Can be null for complex content.
/// </remarks>
/// </summary>
public string? Text
{
get
{
if (!HasComplexContent)
{
return _text;
}

var builder = new StringBuilder();

foreach(var inline in this)
{
inline.AppendText(builder);
}

return builder.ToString();
}
set
{
if (HasComplexContent)
{
Add(new Run(value));
}
else
{
_text = value;
}
}
}

/// <summary>
/// Add a text segment to the collection.
/// <remarks>
/// For non complex content this appends the text to the end of currently held text.
/// For complex content this adds a <see cref="Run"/> to the collection.
/// </remarks>
/// </summary>
/// <param name="text"></param>
public void Add(string text)
{
if (HasComplexContent)
{
Add(new Run(text));
}
else
{
_text += text;
}
}

public override void Add(Inline item)
{
if (!HasComplexContent)
{
base.Add(new Run(_text));

_text = string.Empty;
}

base.Add(item);
}

/// <summary>
/// Raised when an inline in the collection changes.
/// </summary>
public event EventHandler? Invalidated;

/// <summary>
/// Raises the <see cref="Invalidated"/> event.
/// </summary>
protected void Invalidate() => Invalidated?.Invoke(this, EventArgs.Empty);

private void Invalidate(object? sender, EventArgs e) => Invalidate();
}
}
17 changes: 17 additions & 0 deletions src/Avalonia.Controls/Documents/Italic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Avalonia.Media;

namespace Avalonia.Controls.Documents
{
/// <summary>
/// Italic element - markup helper for indicating italicized content.
/// Equivalent to a Span with FontStyle property set to FontStyles.Italic.
/// Can contain other inline elements.
/// </summary>
public sealed class Italic : Span
{
static Italic()
{
FontStyleProperty.OverrideDefaultValue<Italic>(FontStyle.Italic);
}
}
}
44 changes: 44 additions & 0 deletions src/Avalonia.Controls/Documents/LineBreak.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Utilities;

namespace Avalonia.Controls.Documents
{
/// <summary>
/// LineBreak element that forces a line breaking.
/// </summary>
[TrimSurroundingWhitespace]
public class LineBreak : Inline
{
/// <summary>
/// Creates a new LineBreak instance.
/// </summary>
public LineBreak()
{
}

internal override int BuildRun(StringBuilder stringBuilder,
IList<ValueSpan<TextRunProperties>> textStyleOverrides, int firstCharacterIndex)
{
var length = AppendText(stringBuilder);

textStyleOverrides.Add(new ValueSpan<TextRunProperties>(firstCharacterIndex, length,
CreateTextRunProperties()));

return length;
}

internal override int AppendText(StringBuilder stringBuilder)
{
var text = Environment.NewLine;

stringBuilder.Append(text);

return text.Length;
}
}
}

Loading

0 comments on commit 237a9a4

Please sign in to comment.