Skip to content

Commit 7cc5c10

Browse files
Added support for platform text scaling, disabled by default
Only TextBlock, Inline, and TextPresenter scale text by themselves No change to low-level text rendering or FontSize values Implemented for iOS and Windows Windows scaling is currently uniform regardless of base font size, which is incorrect
1 parent f46758f commit 7cc5c10

File tree

15 files changed

+204
-27
lines changed

15 files changed

+204
-27
lines changed

samples/ControlCatalog/MainView.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
xmlns:models="using:ControlCatalog.Models"
77
xmlns:pages="using:ControlCatalog.Pages"
88
xmlns:viewModels="using:ControlCatalog.ViewModels"
9-
x:DataType="viewModels:MainWindowViewModel">
9+
x:DataType="viewModels:MainWindowViewModel"
10+
TextElement.IsPlatformTextScalingEnabled="True">
1011
<Grid>
1112
<Grid.Styles>
1213
<Style Selector="TextBlock.h2">

src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
22
using Avalonia.Input;
33
using Avalonia.Input.Platform;
4-
using Avalonia.Media;
54
using Avalonia.Metadata;
65
using Avalonia.Threading;
7-
using Avalonia.VisualTree;
86

97
namespace Avalonia.Platform
108
{
@@ -47,12 +45,18 @@ public virtual PlatformColorValues GetColorValues()
4745
};
4846
}
4947

50-
public virtual event EventHandler<PlatformColorValues>? ColorValuesChanged;
48+
public event EventHandler<PlatformColorValues>? ColorValuesChanged;
5149

52-
protected void OnColorValuesChanged(PlatformColorValues colorValues)
50+
protected virtual void OnColorValuesChanged(PlatformColorValues colorValues)
5351
{
5452
Dispatcher.UIThread.Send(
5553
_ => ColorValuesChanged?.Invoke(this, colorValues));
5654
}
55+
56+
public event EventHandler<EventArgs>? TextScalingChanged;
57+
58+
protected virtual void OnTextScaleChanged() => Dispatcher.UIThread.Send(_ => TextScalingChanged?.Invoke(this, EventArgs.Empty));
59+
60+
public virtual double GetScaledFontSize(double baseFontSize) => baseFontSize;
5761
}
5862
}

src/Avalonia.Base/Platform/IPlatformSettings.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public interface IPlatformSettings
4242
/// Get a configuration for platform-specific hotkeys in an Avalonia application.
4343
/// </summary>
4444
PlatformHotkeyConfiguration HotkeyConfiguration { get; }
45-
45+
4646
/// <summary>
4747
/// Gets current system color values including dark mode and accent colors.
4848
/// </summary>
@@ -52,5 +52,15 @@ public interface IPlatformSettings
5252
/// Raises when current system color values are changed. Including changing of a dark mode and accent colors.
5353
/// </summary>
5454
event EventHandler<PlatformColorValues>? ColorValuesChanged;
55+
56+
/// <summary>
57+
/// Raised when the system text scaling changes.
58+
/// </summary>
59+
event EventHandler<EventArgs>? TextScalingChanged;
60+
61+
/// <summary>
62+
/// Scales a font size according to the current system text scaling rules.
63+
/// </summary>
64+
double GetScaledFontSize(double baseFontSize);
5565
}
5666
}

src/Avalonia.Controls/Documents/IInlineHost.cs

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

44
namespace Avalonia.Controls.Documents
55
{
6-
internal interface IInlineHost : ILogical
6+
internal interface IInlineHost : ILogical, IPlatformTextScaleable
77
{
88
void Invalidate();
99

src/Avalonia.Controls/Documents/Inline.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ protected TextRunProperties CreateTextRunProperties()
8383
return new GenericTextRunProperties(
8484
typeface,
8585
FontFeatures,
86-
FontSize,
86+
InlineHost?.GetScaledFontSize(FontSize) ?? FontSize,
8787
TextDecorations,
8888
Foreground,
8989
parentOrSelfBackground,

src/Avalonia.Controls/Documents/TextElement.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public abstract class TextElement : StyledElement
1414
public static readonly StyledProperty<IBrush?> BackgroundProperty =
1515
Border.BackgroundProperty.AddOwner<TextElement>();
1616

17+
/// <summary>
18+
/// Defines the <see cref="IsPlatformTextScalingEnabled"/> property.
19+
/// </summary>
20+
public static readonly AttachedProperty<bool> IsPlatformTextScalingEnabledProperty =
21+
AvaloniaProperty.RegisterAttached<TextElement, Control, bool>(
22+
nameof(IsPlatformTextScalingEnabled),
23+
defaultValue: false,
24+
inherits: true);
25+
1726
/// <summary>
1827
/// Defines the <see cref="FontFamily"/> property.
1928
/// </summary>
@@ -102,6 +111,16 @@ public IBrush? Background
102111
set => SetValue(BackgroundProperty, value);
103112
}
104113

114+
/// <summary>
115+
/// Gets or sets whether the <see cref="FontSize"/> should be scaled according to platform text scaling
116+
/// rules when measuring and rendering this control.
117+
/// </summary>
118+
public bool IsPlatformTextScalingEnabled
119+
{
120+
get => GetValue(IsPlatformTextScalingEnabledProperty);
121+
set => SetValue(IsPlatformTextScalingEnabledProperty, value);
122+
}
123+
105124
/// <summary>
106125
/// Gets or sets the font family.
107126
/// </summary>
@@ -174,6 +193,9 @@ public double LetterSpacing
174193
set => SetValue(LetterSpacingProperty, value);
175194
}
176195

196+
public static bool GetIsPlatformTextScalingEnabled(Control control) => control.GetValue(IsPlatformTextScalingEnabledProperty);
197+
public static void SetIsPlatformTextScalingEnabled(Control control, bool value) => control.SetValue(IsPlatformTextScalingEnabledProperty, value);
198+
177199
/// <summary>
178200
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
179201
/// </summary>
@@ -359,6 +381,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
359381
case nameof(Background):
360382
case nameof(FontFamily):
361383
case nameof(FontSize):
384+
case nameof(IsPlatformTextScalingEnabled):
362385
case nameof(FontStyle):
363386
case nameof(FontWeight):
364387
case nameof(FontStretch):
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Avalonia.Controls;
2+
3+
/// <summary>
4+
/// Represents an object which particpates in platform text scaling. This is an accessibility feature
5+
/// which allows the user to request that text be drawn larger (or on some platforms smaller) than normal,
6+
/// without altering other UI elements.
7+
/// </summary>
8+
public interface IPlatformTextScaleable
9+
{
10+
bool IsPlatformTextScalingEnabled { get; }
11+
void OnPlatformTextScalingChanged();
12+
13+
/// <summary>
14+
/// Scales a font size according to the current system text scaling rules and the value of <see cref="IsPlatformTextScalingEnabled"/>.
15+
/// </summary>
16+
double GetScaledFontSize(double baseFontSize);
17+
}

src/Avalonia.Controls/Presenters/TextPresenter.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace Avalonia.Controls.Presenters
1717
{
18-
public class TextPresenter : Control
18+
public class TextPresenter : Control, IPlatformTextScaleable
1919
{
2020
public static readonly StyledProperty<bool> ShowSelectionHighlightProperty =
2121
AvaloniaProperty.Register<TextPresenter, bool>(nameof(ShowSelectionHighlight), defaultValue: true);
@@ -47,6 +47,12 @@ public class TextPresenter : Control
4747
public static readonly StyledProperty<int> SelectionEndProperty =
4848
TextBox.SelectionEndProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
4949

50+
/// <summary>
51+
/// Defines the <see cref="IsPlatformTextScalingEnabled"/> property.
52+
/// </summary>
53+
public static readonly StyledProperty<bool> IsPlatformTextScalingEnabledProperty =
54+
TextElement.IsPlatformTextScalingEnabledProperty.AddOwner<TextPresenter>();
55+
5056
/// <summary>
5157
/// Defines the <see cref="Text"/> property.
5258
/// </summary>
@@ -124,6 +130,13 @@ public IBrush? Background
124130
set => SetValue(BackgroundProperty, value);
125131
}
126132

133+
/// <inheritdoc cref="TextElement.IsPlatformTextScalingEnabled"/>
134+
public bool IsPlatformTextScalingEnabled
135+
{
136+
get => GetValue(IsPlatformTextScalingEnabledProperty);
137+
set => SetValue(IsPlatformTextScalingEnabledProperty, value);
138+
}
139+
127140
/// <summary>
128141
/// Gets or sets a value that determines whether the TextPresenter shows a selection highlight.
129142
/// </summary>
@@ -181,6 +194,13 @@ public double FontSize
181194
get => TextElement.GetFontSize(this);
182195
set => TextElement.SetFontSize(this, value);
183196
}
197+
198+
double IPlatformTextScaleable.GetScaledFontSize(double baseFontSize) => IsPlatformTextScalingEnabled && TopLevel.GetTopLevel(this) is { PlatformSettings: { } platformSettings } ? platformSettings.GetScaledFontSize(baseFontSize) : baseFontSize;
199+
200+
/// <summary>
201+
/// Gets <see cref="FontSize"/> scaled according to the platform's current text scaling rules.
202+
/// </summary>
203+
protected double EffectiveFontSize => ((IPlatformTextScaleable)this).GetScaledFontSize(FontSize);
184204

185205
/// <summary>
186206
/// Gets or sets the font style.
@@ -335,6 +355,14 @@ public int SelectionEnd
335355

336356
internal TextSelectionHandleCanvas? TextSelectionHandleCanvas { get; set; }
337357

358+
void IPlatformTextScaleable.OnPlatformTextScalingChanged()
359+
{
360+
if (IsPlatformTextScalingEnabled)
361+
{
362+
InvalidateMeasure();
363+
}
364+
}
365+
338366
/// <summary>
339367
/// Creates the <see cref="TextLayout"/> used to render the text.
340368
/// </summary>
@@ -350,7 +378,7 @@ private TextLayout CreateTextLayoutInternal(Size constraint, string? text, Typef
350378
var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width;
351379
var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height;
352380

353-
var textLayout = new TextLayout(text, typeface, FontFeatures, FontSize, foreground, TextAlignment,
381+
var textLayout = new TextLayout(text, typeface, FontFeatures, EffectiveFontSize, foreground, TextAlignment,
354382
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
355383
flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing);
356384

@@ -553,7 +581,7 @@ protected virtual TextLayout CreateTextLayout()
553581
if (!string.IsNullOrEmpty(preeditText))
554582
{
555583
var preeditHighlight = new ValueSpan<TextRunProperties>(caretIndex, preeditText.Length,
556-
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
584+
new GenericTextRunProperties(typeface, FontFeatures, EffectiveFontSize,
557585
foregroundBrush: foreground,
558586
textDecorations: TextDecorations.Underline));
559587

@@ -569,7 +597,7 @@ protected virtual TextLayout CreateTextLayout()
569597
textStyleOverrides = new[]
570598
{
571599
new ValueSpan<TextRunProperties>(start, length,
572-
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
600+
new GenericTextRunProperties(typeface, FontFeatures, EffectiveFontSize,
573601
foregroundBrush: SelectionForegroundBrush))
574602
};
575603
}

src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private void DragSelectionHandle(TextSelectionHandle handle)
154154
}
155155

156156
var point = ToPresenter(handle.IndicatorPosition);
157-
point = point.WithY(point.Y - _presenter.FontSize / 2);
157+
point = point.WithY(point.Y - ((IPlatformTextScaleable)_presenter).GetScaledFontSize(_presenter.FontSize) / 2);
158158
var hit = _presenter.TextLayout.HitTestPoint(point);
159159
var position = hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength;
160160

src/Avalonia.Controls/SelectableTextBlock.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ protected override TextLayout CreateTextLayout(string? text)
187187
var defaultProperties = new GenericTextRunProperties(
188188
typeface,
189189
FontFeatures,
190-
FontSize,
190+
EffectiveFontSize,
191191
TextDecorations,
192192
Foreground);
193193

@@ -236,7 +236,7 @@ protected override TextLayout CreateTextLayout(string? text)
236236
new GenericTextRunProperties(
237237
textRun.Properties?.Typeface ?? typeface,
238238
textRun.Properties?.FontFeatures ?? FontFeatures,
239-
FontSize,
239+
EffectiveFontSize,
240240
foregroundBrush: SelectionForegroundBrush)));
241241

242242
accumulatedLength += runLength;
@@ -247,7 +247,7 @@ protected override TextLayout CreateTextLayout(string? text)
247247
textStyleOverrides =
248248
[
249249
new ValueSpan<TextRunProperties>(start, length,
250-
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
250+
new GenericTextRunProperties(typeface, FontFeatures, EffectiveFontSize,
251251
foregroundBrush: SelectionForegroundBrush))
252252
];
253253
}

0 commit comments

Comments
 (0)