Skip to content

Commit 17e334b

Browse files
authored
Merge pull request #4893 from Gillibald/feature/FormattedTextPort
Port WPF's FormattedText to Avalonia and rework TextPresenter
2 parents 765f204 + 79298e3 commit 17e334b

File tree

106 files changed

+12040
-3906
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+12040
-3906
lines changed

samples/ControlCatalog/Pages/ScreenPage.cs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Globalization;
23
using Avalonia;
34
using Avalonia.Controls;
45
using Avalonia.Markup.Xaml;
@@ -49,25 +50,33 @@ public override void Render(DrawingContext context)
4950
context.DrawRectangle(p, boundsRect);
5051
context.DrawRectangle(p, workingAreaRect);
5152

52-
var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 };
5353

54-
text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}";
55-
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text);
56-
57-
text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
58-
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
54+
var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
55+
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
5956

60-
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
61-
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
62-
63-
text.Text = $"Primary: {screen.Primary}";
64-
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
65-
66-
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
67-
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
57+
formattedText =
58+
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
59+
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
60+
61+
formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%");
62+
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
63+
64+
formattedText = CreateFormattedText($"Primary: {screen.Primary}");
65+
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
66+
67+
formattedText =
68+
CreateFormattedText(
69+
$"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}");
70+
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
6871
}
6972

7073
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
7174
}
75+
76+
private FormattedText CreateFormattedText(string textToFormat)
77+
{
78+
return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
79+
Typeface.Default, 12, Brushes.Green);
80+
}
7281
}
7382
}

samples/RenderDemo/MainWindow.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
<TabItem Header="GlyphRun">
5858
<pages:GlyphRunPage />
5959
</TabItem>
60+
<TabItem Header="FormattedText">
61+
<pages:FormattedTextPage />
62+
</TabItem>
6063
<TabItem Header="LineBounds">
6164
<pages:LineBoundsPage />
6265
</TabItem>

samples/RenderDemo/Pages/CustomSkiaPage.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Globalization;
34
using Avalonia;
45
using Avalonia.Controls;
56
using Avalonia.Media;
@@ -41,7 +42,10 @@ public void Render(IDrawingContextImpl context)
4142
{
4243
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
4344
if (canvas == null)
44-
context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
45+
using (var c = new DrawingContext(context, false))
46+
{
47+
c.DrawText(_noSkia, new Point());
48+
}
4549
else
4650
{
4751
canvas.Save();
@@ -108,10 +112,9 @@ static int Animate(int d, int from, int to)
108112

109113
public override void Render(DrawingContext context)
110114
{
111-
var noSkia = new FormattedText()
112-
{
113-
Text = "Current rendering API is not Skia"
114-
};
115+
var noSkia = new FormattedText("Current rendering API is not Skia", CultureInfo.CurrentCulture,
116+
FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Black);
117+
115118
context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
116119
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
117120
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
6+
x:Class="RenderDemo.Pages.FormattedTextPage">
7+
</UserControl>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Globalization;
2+
using Avalonia;
3+
using Avalonia.Controls;
4+
using Avalonia.Markup.Xaml;
5+
using Avalonia.Media;
6+
7+
namespace RenderDemo.Pages
8+
{
9+
public class FormattedTextPage : UserControl
10+
{
11+
public FormattedTextPage()
12+
{
13+
this.InitializeComponent();
14+
}
15+
16+
private void InitializeComponent()
17+
{
18+
AvaloniaXamlLoader.Load(this);
19+
}
20+
21+
public override void Render(DrawingContext context)
22+
{
23+
const string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";
24+
25+
// Create the initial formatted text string.
26+
var formattedText = new FormattedText(
27+
testString,
28+
CultureInfo.GetCultureInfo("en-us"),
29+
FlowDirection.LeftToRight,
30+
new Typeface("Verdana"),
31+
32,
32+
Brushes.Black) { MaxTextWidth = 300, MaxTextHeight = 240 };
33+
34+
// Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
35+
36+
// Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters.
37+
// The font size is calculated in terms of points -- not as device-independent pixels.
38+
formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5);
39+
40+
// Use a Bold font weight beginning at the 6th character and continuing for 11 characters.
41+
formattedText.SetFontWeight(FontWeight.Bold, 6, 11);
42+
43+
var gradient = new LinearGradientBrush
44+
{
45+
GradientStops =
46+
new GradientStops { new GradientStop(Colors.Orange, 0), new GradientStop(Colors.Teal, 1) },
47+
StartPoint = new RelativePoint(0,0, RelativeUnit.Relative),
48+
EndPoint = new RelativePoint(0,1, RelativeUnit.Relative)
49+
};
50+
51+
// Use a linear gradient brush beginning at the 6th character and continuing for 11 characters.
52+
formattedText.SetForegroundBrush(gradient, 6, 11);
53+
54+
// Use an Italic font style beginning at the 28th character and continuing for 28 characters.
55+
formattedText.SetFontStyle(FontStyle.Italic, 28, 28);
56+
57+
context.DrawText(formattedText, new Point(10, 0));
58+
}
59+
}
60+
}

samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class GlyphRunPage : UserControl
1313
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
1414
private readonly Random _rand = new Random();
1515
private ushort[] _glyphIndices = new ushort[1];
16+
private char[] _characters = new char[1];
1617
private float _fontSize = 20;
1718
private int _direction = 10;
1819

@@ -38,7 +39,7 @@ private void InitializeComponent()
3839

3940
private void UpdateGlyphRun()
4041
{
41-
var c = (uint)_rand.Next(65, 90);
42+
var c = (char)_rand.Next(65, 90);
4243

4344
if (_fontSize + _direction > 200)
4445
{
@@ -54,14 +55,16 @@ private void UpdateGlyphRun()
5455

5556
_glyphIndices[0] = _glyphTypeface.GetGlyph(c);
5657

58+
_characters[0] = c;
59+
5760
var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
5861

5962
var drawingGroup = new DrawingGroup();
6063

6164
var glyphRunDrawing = new GlyphRunDrawing
6265
{
6366
Foreground = Brushes.Black,
64-
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
67+
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
6568
};
6669

6770
drawingGroup.Children.Add(glyphRunDrawing);

src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Avalonia.Utilities
55
{
6-
public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator, IEnumerator<T>
6+
public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator<T>
77
{
88
private readonly IReadOnlyList<T> _readOnlyList;
99
private int _pos;

src/Avalonia.Controls/ApiCompatBaseline.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.Off
4343
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
4444
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
4545
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
46+
MembersMustExist : Member 'protected Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.CreateFormattedText()' does not exist in the implementation but it does exist in the contract.
47+
MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.FormattedText.get()' does not exist in the implementation but it does exist in the contract.
48+
MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
49+
MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract.
4650
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
4751
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
4852
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
@@ -63,4 +67,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
6367
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
6468
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
6569
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
66-
Total Issues: 64
70+
Total Issues: 68

src/Avalonia.Controls/Control.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
22
using System.ComponentModel;
3+
using System.Runtime.CompilerServices;
34
using Avalonia.Controls.Primitives;
45
using Avalonia.Controls.Templates;
56
using Avalonia.Input;
67
using Avalonia.Input.Platform;
78
using Avalonia.Interactivity;
9+
using Avalonia.Media;
810
using Avalonia.Rendering;
911
using Avalonia.Styling;
1012
using Avalonia.VisualTree;
@@ -60,7 +62,13 @@ public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, I
6062
public static readonly RoutedEvent<ContextRequestedEventArgs> ContextRequestedEvent =
6163
RoutedEvent.Register<Control, ContextRequestedEventArgs>(nameof(ContextRequested),
6264
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
63-
65+
66+
/// <summary>
67+
/// Defines the <see cref="FlowDirection"/> property.
68+
/// </summary>
69+
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
70+
AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
71+
6472
private DataTemplates? _dataTemplates;
6573
private IControl? _focusAdorner;
6674

@@ -108,6 +116,15 @@ public object? Tag
108116
get => GetValue(TagProperty);
109117
set => SetValue(TagProperty, value);
110118
}
119+
120+
/// <summary>
121+
/// Gets or sets the text flow direction.
122+
/// </summary>
123+
public FlowDirection FlowDirection
124+
{
125+
get => GetValue(FlowDirectionProperty);
126+
set => SetValue(FlowDirectionProperty, value);
127+
}
111128

112129
/// <summary>
113130
/// Occurs when the user has completed a context input gesture, such as a right-click.

0 commit comments

Comments
 (0)