committed by
GitHub
18 changed files with 376 additions and 73 deletions
@ -0,0 +1,16 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia; |
|||
|
|||
/// <summary>
|
|||
/// Represents an object which can algoritmically scale text.
|
|||
/// </summary>
|
|||
public interface ITextScaler |
|||
{ |
|||
double GetScaledFontSize(Visual target, double baseFontSize); |
|||
|
|||
/// <summary>
|
|||
/// Raised when the text scaling algorithm has changed. Indicates that all text should be rescaled.
|
|||
/// </summary>
|
|||
event EventHandler<EventArgs>? TextScalingChanged; |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Avalonia.Controls; |
|||
|
|||
/// <summary>
|
|||
/// Represents an object which particpates in <see cref="TextScaling"/>.
|
|||
/// </summary>
|
|||
/// <seealso cref="ITextScaler"/>
|
|||
public interface ITextScaleable |
|||
{ |
|||
/// <summary>
|
|||
/// Called when the active text scaling algorithm for this object changes.
|
|||
/// </summary>
|
|||
void OnTextScalingChanged(); |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Controls.Documents; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Controls; |
|||
|
|||
/// <summary>
|
|||
/// Configures and computes text scaling. This is an accessibility feature which allows the user to request that text be
|
|||
/// drawn at a different size than normal, without altering other UI elements. The default scaling algorithm is determined
|
|||
/// by the platform and may make text smaller as well as larger.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Text scaling is applied when text is measured. It does not modify the value of <see cref="TextElement.FontSize"/>.
|
|||
/// </remarks>
|
|||
/// <seealso cref="ITextScaleable"/>
|
|||
/// <seealso cref="ITextScaler"/>
|
|||
public static class TextScaling |
|||
{ |
|||
private static readonly ConditionalWeakTable<ITextScaler, HashSet<Visual>> s_customTextScalerSubscribers = []; |
|||
|
|||
/// <summary>
|
|||
/// Determines whether <see cref="TextElement.FontSize"/> (along with <see cref="TextElement.LetterSpacing"/>, <see cref="TextBlock.LineHeight"/>, and
|
|||
/// <see cref="TextBlock.LineSpacing"/>) should be scalled by calling <see cref="GetScaledFontSize"/> when measuring text content. The value is inherited.
|
|||
/// </summary>
|
|||
/// <remarks>Text scaling is typically not uniform. Smaller text scales up faster than larger text.</remarks>
|
|||
public static readonly AttachedProperty<bool> IsEnabledProperty = |
|||
AvaloniaProperty.RegisterAttached<Visual, bool>("IsEnabled", typeof(TextScaling), inherits: true); |
|||
|
|||
/// <summary>
|
|||
/// Determines the minimum size (in em units) to which text will be scaled by <see cref="GetScaledFontSize"/>. The value is inherited.
|
|||
/// </summary>
|
|||
/// <remarks>This value is used only when <see cref="IsEnabledProperty"/> is true.</remarks>
|
|||
public static readonly AttachedProperty<double> MinFontSizeProperty = |
|||
AvaloniaProperty.RegisterAttached<Visual, double>("MinFontSize", typeof(TextScaling), inherits: true, validate: size => size >= 0); |
|||
|
|||
/// <summary>
|
|||
/// Determines the maximum size (in em units) to which text will be scaled by <see cref="GetScaledFontSize"/>. The value is inherited.
|
|||
/// </summary>
|
|||
/// <remarks>This value is used only when <see cref="IsEnabledProperty"/> is true.</remarks>
|
|||
public static readonly AttachedProperty<double> MaxFontSizeProperty = |
|||
AvaloniaProperty.RegisterAttached<Visual, double>("MaxFontSize", typeof(TextScaling), defaultValue: double.PositiveInfinity, inherits: true, |
|||
validate: size => size > 0); |
|||
|
|||
/// <summary>
|
|||
/// Determines a user-defined text scaling algorithm, which overrides platform text scaling in <see cref="GetScaledFontSize"/>. The value is inherited.
|
|||
/// </summary>
|
|||
/// <remarks>This value is used only when <see cref="IsEnabledProperty"/> is true.</remarks>
|
|||
public static readonly AttachedProperty<ITextScaler?> CustomTextScalerProperty = |
|||
AvaloniaProperty.RegisterAttached<Visual, ITextScaler?>("CustomTextScaler", typeof(TextScaling), inherits: true); |
|||
|
|||
/// <inheritdoc cref="IsEnabledProperty"/> <see cref="IsEnabledProperty"/>
|
|||
public static bool GetIsEnabled(Visual visual) => visual.GetValue(IsEnabledProperty); |
|||
/// <inheritdoc cref="IsEnabledProperty"/> <see cref="IsEnabledProperty"/>
|
|||
public static void SetIsEnabled(Visual visual, bool value) => visual.SetValue(IsEnabledProperty, value); |
|||
/// <inheritdoc cref="MinFontSizeProperty"/> <see cref="MinFontSizeProperty"/>
|
|||
public static double GetMinFontSize(Visual visual) => visual.GetValue(MinFontSizeProperty); |
|||
/// <inheritdoc cref="MinFontSizeProperty"/> <see cref="MinFontSizeProperty"/>
|
|||
public static void SetMinFontSize(Visual visual, double value) => visual.SetValue(MinFontSizeProperty, value); |
|||
/// <inheritdoc cref="MaxFontSizeProperty"/> <see cref="MaxFontSizeProperty"/>
|
|||
public static double GetMaxFontSize(Visual visual) => visual.GetValue(MaxFontSizeProperty); |
|||
/// <inheritdoc cref="MaxFontSizeProperty"/> <see cref="MaxFontSizeProperty"/>
|
|||
public static void SetMaxFontSize(Visual visual, double value) => visual.SetValue(MaxFontSizeProperty, value); |
|||
/// <inheritdoc cref="CustomTextScalerProperty"/> <see cref="CustomTextScalerProperty"/>
|
|||
public static ITextScaler? GetCustomTextScaler(Visual visual) => visual.GetValue(CustomTextScalerProperty); |
|||
/// <inheritdoc cref="CustomTextScalerProperty"/> <see cref="CustomTextScalerProperty"/>
|
|||
public static void SetCustomTextScaler(Visual visual, ITextScaler? value) => visual.SetValue(CustomTextScalerProperty, value); |
|||
|
|||
static TextScaling() |
|||
{ |
|||
CustomTextScalerProperty.Changed.AddClassHandler<Visual>(OnCustomTextScalerChanged); |
|||
} |
|||
|
|||
private static void OnCustomTextScalerChanged(Visual visual, AvaloniaPropertyChangedEventArgs args) |
|||
{ |
|||
if (visual is not ITextScaleable) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var (oldScaler, newScaler) = args.GetOldAndNewValue<ITextScaler?>(); |
|||
|
|||
if (oldScaler != null && s_customTextScalerSubscribers.TryGetValue(oldScaler, out var oldSubscribers)) |
|||
{ |
|||
oldSubscribers.Remove(visual); |
|||
} |
|||
|
|||
if (newScaler != null) |
|||
{ |
|||
if (s_customTextScalerSubscribers.TryGetValue(newScaler, out var newSubscribers)) |
|||
{ |
|||
newSubscribers.Add(visual); |
|||
} |
|||
else |
|||
{ |
|||
s_customTextScalerSubscribers.Add(newScaler, [visual]); |
|||
newScaler.TextScalingChanged += OnCustomTextScalingChanged; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void OnCustomTextScalingChanged(object? sender, EventArgs e) |
|||
{ |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
if (sender is ITextScaler scaler && s_customTextScalerSubscribers.TryGetValue(scaler, out var subscribers)) |
|||
{ |
|||
foreach (var visual in subscribers) |
|||
{ |
|||
if (GetIsEnabled(visual)) |
|||
{ |
|||
((ITextScaleable)visual).OnTextScalingChanged(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales <paramref name="baseFontSize"/> according to either the current platform text scaling rules, or the rules
|
|||
/// defined by the object assigned to <see cref="CustomTextScalerProperty"/> for <paramref name="visual"/>.
|
|||
/// </summary>
|
|||
/// <remarks>The values of <see cref="IsEnabledProperty"/>, <see cref="MinFontSizeProperty"/>, and <see cref="MaxFontSizeProperty"/>
|
|||
/// are enforced, even when <see cref="CustomTextScalerProperty"/> is in use.</remarks>
|
|||
public static double GetScaledFontSize(Visual visual, double baseFontSize) |
|||
{ |
|||
if (double.IsNaN(baseFontSize) || baseFontSize <= 0 || !GetIsEnabled(visual) || |
|||
(GetCustomTextScaler(visual) ?? TopLevel.GetTopLevel(visual)?.PlatformSettings) is not { } scaler) |
|||
{ |
|||
return baseFontSize; |
|||
} |
|||
|
|||
// Extend min/max to encompass the base size. Conveniently, this means that min > max is not possible.
|
|||
var min = Math.Min(GetMinFontSize(visual), baseFontSize); |
|||
var max = Math.Max(GetMaxFontSize(visual), baseFontSize); |
|||
|
|||
return Math.Clamp(scaler.GetScaledFontSize(visual, baseFontSize), min, max); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue