csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
7.1 KiB
139 lines
7.1 KiB
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);
|
|
}
|
|
}
|
|
|