Skip to content

Commit 887ee05

Browse files
committed
Port WPF's FormattedText to Avalonia and rework TextPresenter
1 parent 9dbcc98 commit 887ee05

File tree

49 files changed

+4946
-1807
lines changed

Some content is hidden

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

49 files changed

+4946
-1807
lines changed

samples/ControlCatalog/Pages/ScreenPage.cs

Lines changed: 24 additions & 15 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;
@@ -24,7 +25,7 @@ public override void Render(DrawingContext context)
2425
base.Render(context);
2526
if (!(VisualRoot is Window w))
2627
{
27-
return;
28+
return;
2829
}
2930
var screens = w.Screens.All;
3031
var scaling = ((IRenderRoot)w).RenderScaling;
@@ -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: 21 additions & 19 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;
@@ -17,7 +18,7 @@ public CustomSkiaPage()
1718
{
1819
ClipToBounds = true;
1920
}
20-
21+
2122
class CustomDrawOp : ICustomDrawOperation
2223
{
2324
private readonly FormattedText _noSkia;
@@ -27,7 +28,7 @@ public CustomDrawOp(Rect bounds, FormattedText noSkia)
2728
_noSkia = noSkia;
2829
Bounds = bounds;
2930
}
30-
31+
3132
public void Dispose()
3233
{
3334
// No-op
@@ -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();
@@ -60,24 +64,24 @@ public void Render(IDrawingContextImpl context)
6064
(float)(Bounds.Height / 2 + Math.Sin(St.Elapsed.TotalSeconds) * Bounds.Height / 4));
6165
using (var sweep =
6266
SKShader.CreateSweepGradient(new SKPoint((int)Bounds.Width / 2, (int)Bounds.Height / 2), colors,
63-
null))
64-
using(var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0))
65-
using(var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop))
66-
using(var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15)))
67+
null))
68+
using (var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0))
69+
using (var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop))
70+
using (var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15)))
6771
using (var paint = new SKPaint
6872
{
6973
Shader = shader,
7074
ImageFilter = blur
7175
})
7276
canvas.DrawPaint(paint);
73-
77+
7478
using (var pseudoLight = SKShader.CreateRadialGradient(
7579
lightPosition,
76-
(float) (Bounds.Width/3),
77-
new [] {
78-
new SKColor(255, 200, 200, 100),
80+
(float)(Bounds.Width / 3),
81+
new[] {
82+
new SKColor(255, 200, 200, 100),
7983
SKColors.Transparent,
80-
new SKColor(40,40,40, 220),
84+
new SKColor(40,40,40, 220),
8185
new SKColor(20,20,20, (byte)Animate(100, 200,220)) },
8286
new float[] { 0.3f, 0.3f, 0.8f, 1 },
8387
SKShaderTileMode.Clamp))
@@ -88,7 +92,7 @@ public void Render(IDrawingContextImpl context)
8892
canvas.DrawPaint(paint);
8993
canvas.Restore();
9094
}
91-
}
95+
}
9296
static int Animate(int d, int from, int to)
9397
{
9498
var ms = (int)(St.ElapsedMilliseconds / d);
@@ -104,15 +108,13 @@ static int Animate(int d, int from, int to)
104108
}
105109
}
106110

107-
108-
109111
public override void Render(DrawingContext context)
110112
{
111-
var noSkia = new FormattedText()
112-
{
113-
Text = "Current rendering API is not Skia"
114-
};
113+
var noSkia = new FormattedText("Current rendering API is not Skia", CultureInfo.CurrentCulture,
114+
FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Black);
115+
115116
context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
117+
116118
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
117119
}
118120
}
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/Sandbox/MainWindow.axaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<Window xmlns="https://github.com/avaloniaui"
22
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
33
x:Class="Sandbox.MainWindow">
4+
<Border HorizontalAlignment="Center" VerticalAlignment="Center" Width="100">
5+
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="200" Height="125"
6+
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor"/>
7+
</Border>
48
</Window>

src/Avalonia.Controls/ApiCompatBaseline.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
3737
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
3838
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.
3939
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.
40+
MembersMustExist : Member 'protected Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.CreateFormattedText()' does not exist in the implementation but it does exist in the contract.
41+
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.
4042
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
4143
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
4244
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
@@ -55,4 +57,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
5557
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation.
5658
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
5759
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
58-
Total Issues: 56
60+
Total Issues: 58

0 commit comments

Comments
 (0)