committed by
GitHub
175 changed files with 3777 additions and 1852 deletions
@ -1,41 +1,48 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:integrationTestApp="clr-namespace:IntegrationTestApp" |
|||
x:Class="IntegrationTestApp.ShowWindowTest" |
|||
Name="SecondaryWindow" |
|||
x:DataType="Window" |
|||
Title="Show Window Test"> |
|||
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> |
|||
<Label Grid.Column="0" Grid.Row="1">Client Size</Label> |
|||
<TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True" |
|||
Text="{Binding ClientSize, Mode=OneWay}"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="2">Frame Size</Label> |
|||
<TextBox Name="FrameSize" Grid.Column="1" Grid.Row="2" IsReadOnly="True" |
|||
Text="{Binding FrameSize, Mode=OneWay}"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="3">Position</Label> |
|||
<TextBox Name="Position" Grid.Column="1" Grid.Row="3" IsReadOnly="True"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="4">Owner Rect</Label> |
|||
<TextBox Name="OwnerRect" Grid.Column="1" Grid.Row="4" IsReadOnly="True"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="5">Screen Rect</Label> |
|||
<TextBox Name="ScreenRect" Grid.Column="1" Grid.Row="5" IsReadOnly="True"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="6">Scaling</Label> |
|||
<TextBox Name="Scaling" Grid.Column="1" Grid.Row="6" IsReadOnly="True"/> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="7">WindowState</Label> |
|||
<ComboBox Name="WindowState" Grid.Column="1" Grid.Row="7" SelectedIndex="{Binding WindowState}"> |
|||
<ComboBoxItem Name="WindowStateNormal">Normal</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateMinimized">Minimized</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateMaximized">Maximized</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label> |
|||
<TextBox Name="Order" Grid.Column="1" Grid.Row="8" IsReadOnly="True"/> |
|||
|
|||
<Button Name="HideButton" Grid.Row="9" Command="{Binding $parent[Window].Hide}">Hide</Button> |
|||
</Grid> |
|||
<integrationTestApp:MeasureBorder Name="MyBorder"> |
|||
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> |
|||
<Label Grid.Column="0" Grid.Row="1">Client Size</Label> |
|||
<TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True" |
|||
Text="{Binding ClientSize, Mode=OneWay}" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="2">Frame Size</Label> |
|||
<TextBox Name="CurrentFrameSize" Grid.Column="1" Grid.Row="2" IsReadOnly="True" |
|||
Text="{Binding FrameSize, Mode=OneWay}" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="3">Position</Label> |
|||
<TextBox Name="CurrentPosition" Grid.Column="1" Grid.Row="3" IsReadOnly="True" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="4">Owner Rect</Label> |
|||
<TextBox Name="CurrentOwnerRect" Grid.Column="1" Grid.Row="4" IsReadOnly="True" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="5">Screen Rect</Label> |
|||
<TextBox Name="CurrentScreenRect" Grid.Column="1" Grid.Row="5" IsReadOnly="True" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="6">Scaling</Label> |
|||
<TextBox Name="CurrentScaling" Grid.Column="1" Grid.Row="6" IsReadOnly="True" /> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="7">WindowState</Label> |
|||
<ComboBox Name="CurrentWindowState" Grid.Column="1" Grid.Row="7" SelectedIndex="{Binding WindowState}"> |
|||
<ComboBoxItem Name="WindowStateNormal">Normal</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateMinimized">Minimized</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateMaximized">Maximized</ComboBoxItem> |
|||
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label> |
|||
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" /> |
|||
|
|||
<Label Grid.Row="9" Content="MeasuredWith:" /> |
|||
<TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" /> |
|||
|
|||
<Button Name="HideButton" Grid.Row="10" Command="{Binding $parent[Window].Hide}">Hide</Button> |
|||
|
|||
</Grid> |
|||
</integrationTestApp:MeasureBorder> |
|||
</Window> |
|||
|
|||
@ -1,5 +1,12 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) |
|||
dotnet restore -r osx-arm64 |
|||
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false |
|||
|
|||
arch="x64" |
|||
|
|||
if [[ $(uname -m) == 'arm64' ]]; then |
|||
arch="arm64" |
|||
fi |
|||
|
|||
dotnet restore -r osx-$arch |
|||
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-$arch -p:_AvaloniaUseExternalMSBuild=false |
|||
@ -0,0 +1,235 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Android.OS; |
|||
using Android.Views; |
|||
using AndroidX.AppCompat.App; |
|||
using AndroidX.Core.View; |
|||
using Avalonia.Android.Platform.SkiaPlatform; |
|||
using Avalonia.Controls.Platform; |
|||
using static Avalonia.Controls.Platform.IInsetsManager; |
|||
|
|||
namespace Avalonia.Android.Platform |
|||
{ |
|||
internal class AndroidInsetsManager : Java.Lang.Object, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener |
|||
{ |
|||
private readonly AvaloniaMainActivity _activity; |
|||
private readonly TopLevelImpl _topLevel; |
|||
private readonly InsetsAnimationCallback _callback; |
|||
private bool _displayEdgeToEdge; |
|||
private bool _usesLegacyLayouts; |
|||
private bool? _systemUiVisibility; |
|||
private SystemBarTheme? _statusBarTheme; |
|||
private bool? _isDefaultSystemBarLightTheme; |
|||
|
|||
public event EventHandler<SafeAreaChangedArgs> SafeAreaChanged; |
|||
|
|||
public bool DisplayEdgeToEdge |
|||
{ |
|||
get => _displayEdgeToEdge; |
|||
set |
|||
{ |
|||
_displayEdgeToEdge = value; |
|||
|
|||
if(Build.VERSION.SdkInt >= BuildVersionCodes.P) |
|||
{ |
|||
_activity.Window.Attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default; |
|||
} |
|||
|
|||
WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value); |
|||
} |
|||
} |
|||
|
|||
public AndroidInsetsManager(AvaloniaMainActivity activity, TopLevelImpl topLevel) |
|||
{ |
|||
_activity = activity; |
|||
_topLevel = topLevel; |
|||
_callback = new InsetsAnimationCallback(WindowInsetsAnimationCompat.Callback.DispatchModeStop); |
|||
|
|||
_callback.InsetsManager = this; |
|||
|
|||
ViewCompat.SetOnApplyWindowInsetsListener(_activity.Window.DecorView, this); |
|||
|
|||
ViewCompat.SetWindowInsetsAnimationCallback(_activity.Window.DecorView, _callback); |
|||
|
|||
if(Build.VERSION.SdkInt < BuildVersionCodes.R) |
|||
{ |
|||
_usesLegacyLayouts = true; |
|||
_activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this); |
|||
} |
|||
|
|||
DisplayEdgeToEdge = false; |
|||
} |
|||
|
|||
public Thickness SafeAreaPadding |
|||
{ |
|||
get |
|||
{ |
|||
var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView); |
|||
|
|||
if (insets != null) |
|||
{ |
|||
var renderScaling = _topLevel.RenderScaling; |
|||
|
|||
var inset = insets.GetInsets( |
|||
(DisplayEdgeToEdge ? |
|||
WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() | |
|||
WindowInsetsCompat.Type.DisplayCutout() : |
|||
0) | WindowInsetsCompat.Type.Ime()); |
|||
var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars()); |
|||
var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime()); |
|||
|
|||
return new Thickness(inset.Left / renderScaling, |
|||
inset.Top / renderScaling, |
|||
inset.Right / renderScaling, |
|||
(imeInset.Bottom > 0 && ((_usesLegacyLayouts && !DisplayEdgeToEdge) || !_usesLegacyLayouts) ? |
|||
imeInset.Bottom - navBarInset.Bottom : |
|||
inset.Bottom) / renderScaling); |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets) |
|||
{ |
|||
NotifySafeAreaChanged(SafeAreaPadding); |
|||
return insets; |
|||
} |
|||
|
|||
private void NotifySafeAreaChanged(Thickness safeAreaPadding) |
|||
{ |
|||
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)); |
|||
} |
|||
|
|||
public void OnGlobalLayout() |
|||
{ |
|||
NotifySafeAreaChanged(SafeAreaPadding); |
|||
} |
|||
|
|||
public SystemBarTheme? SystemBarTheme |
|||
{ |
|||
get |
|||
{ |
|||
try |
|||
{ |
|||
var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View); |
|||
|
|||
return compat.AppearanceLightStatusBars ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark; |
|||
} |
|||
catch (Exception _) |
|||
{ |
|||
return Controls.Platform.SystemBarTheme.Light; |
|||
} |
|||
} |
|||
set |
|||
{ |
|||
_statusBarTheme = value; |
|||
|
|||
var isDefault = _statusBarTheme == null; |
|||
|
|||
if (!_topLevel.View.IsShown) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View); |
|||
|
|||
if (_isDefaultSystemBarLightTheme == null) |
|||
{ |
|||
_isDefaultSystemBarLightTheme = compat.AppearanceLightStatusBars; |
|||
} |
|||
|
|||
if (value == null && _isDefaultSystemBarLightTheme != null) |
|||
{ |
|||
value = (bool)_isDefaultSystemBarLightTheme ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark; |
|||
} |
|||
|
|||
compat.AppearanceLightStatusBars = value == Controls.Platform.SystemBarTheme.Light; |
|||
compat.AppearanceLightNavigationBars = value == Controls.Platform.SystemBarTheme.Light; |
|||
|
|||
AppCompatDelegate.DefaultNightMode = isDefault ? AppCompatDelegate.ModeNightFollowSystem : compat.AppearanceLightStatusBars ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; |
|||
} |
|||
} |
|||
|
|||
public bool? IsSystemBarVisible |
|||
{ |
|||
get |
|||
{ |
|||
if(_activity.Window == null) |
|||
{ |
|||
return true; |
|||
} |
|||
var compat = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView); |
|||
|
|||
return compat?.IsVisible(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars()); |
|||
} |
|||
set |
|||
{ |
|||
_systemUiVisibility = value; |
|||
|
|||
if (!_topLevel.View.IsShown) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var compat = WindowCompat.GetInsetsController(_activity.Window, _topLevel.View); |
|||
|
|||
if (value == null || value.Value) |
|||
{ |
|||
compat?.Show(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars()); |
|||
} |
|||
else |
|||
{ |
|||
compat?.Hide(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars()); |
|||
|
|||
if (compat != null) |
|||
{ |
|||
compat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorShowTransientBarsBySwipe; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void ApplyStatusBarState() |
|||
{ |
|||
IsSystemBarVisible = _systemUiVisibility; |
|||
SystemBarTheme = _statusBarTheme; |
|||
} |
|||
|
|||
private class InsetsAnimationCallback : WindowInsetsAnimationCompat.Callback |
|||
{ |
|||
public InsetsAnimationCallback(int dispatchMode) : base(dispatchMode) |
|||
{ |
|||
} |
|||
|
|||
public AndroidInsetsManager InsetsManager { get; set; } |
|||
|
|||
public override WindowInsetsCompat OnProgress(WindowInsetsCompat insets, IList<WindowInsetsAnimationCompat> runningAnimations) |
|||
{ |
|||
foreach (var anim in runningAnimations) |
|||
{ |
|||
if ((anim.TypeMask & WindowInsetsCompat.Type.Ime()) != 0) |
|||
{ |
|||
var renderScaling = InsetsManager._topLevel.RenderScaling; |
|||
|
|||
var inset = insets.GetInsets((InsetsManager.DisplayEdgeToEdge ? WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() | WindowInsetsCompat.Type.DisplayCutout() : 0) | WindowInsetsCompat.Type.Ime()); |
|||
var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars()); |
|||
var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime()); |
|||
|
|||
|
|||
var bottomPadding = (imeInset.Bottom > 0 && !InsetsManager.DisplayEdgeToEdge ? imeInset.Bottom - navBarInset.Bottom : inset.Bottom); |
|||
bottomPadding = (int)(bottomPadding * anim.InterpolatedFraction); |
|||
|
|||
var padding = new Thickness(inset.Left / renderScaling, |
|||
inset.Top / renderScaling, |
|||
inset.Right / renderScaling, |
|||
bottomPadding / renderScaling); |
|||
InsetsManager?.NotifySafeAreaChanged(padding); |
|||
break; |
|||
} |
|||
} |
|||
return insets; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,290 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Globalization; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media.Fonts |
|||
{ |
|||
public class EmbeddedFontCollection : IFontCollection |
|||
{ |
|||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = new(); |
|||
|
|||
private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1); |
|||
|
|||
private readonly Uri _key; |
|||
|
|||
private readonly Uri _source; |
|||
|
|||
public EmbeddedFontCollection(Uri key, Uri source) |
|||
{ |
|||
_key = key; |
|||
|
|||
_source = source; |
|||
} |
|||
|
|||
public Uri Key => _key; |
|||
|
|||
public FontFamily this[int index] => _fontFamilies[index]; |
|||
|
|||
public int Count => _fontFamilies.Count; |
|||
|
|||
public void Initialize(IFontManagerImpl fontManager) |
|||
{ |
|||
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); |
|||
|
|||
var fontAssets = FontFamilyLoader.LoadFontAssets(_source); |
|||
|
|||
foreach (var fontAsset in fontAssets) |
|||
{ |
|||
var stream = assetLoader.Open(fontAsset); |
|||
|
|||
if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface)) |
|||
{ |
|||
if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces)) |
|||
{ |
|||
glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>(); |
|||
|
|||
if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces)) |
|||
{ |
|||
_fontFamilies.Add(new FontFamily(_key, glyphTypeface.FamilyName)); |
|||
} |
|||
} |
|||
|
|||
var key = new FontCollectionKey( |
|||
glyphTypeface.Style, |
|||
glyphTypeface.Weight, |
|||
glyphTypeface.Stretch); |
|||
|
|||
glyphTypefaces.TryAdd(key, glyphTypeface); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
foreach (var fontFamily in _fontFamilies) |
|||
{ |
|||
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out var glyphTypefaces)) |
|||
{ |
|||
foreach (var glyphTypeface in glyphTypefaces.Values) |
|||
{ |
|||
glyphTypeface.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
public IEnumerator<FontFamily> GetEnumerator() => _fontFamilies.GetEnumerator(); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
|
|||
public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
|||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
|||
{ |
|||
var key = new FontCollectionKey(style, weight, stretch); |
|||
|
|||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) |
|||
{ |
|||
if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//Try to find a partially matching font
|
|||
for (var i = 0; i < Count; i++) |
|||
{ |
|||
var fontFamily = _fontFamilies[i]; |
|||
|
|||
if (fontFamily.Name.ToLower(CultureInfo.InvariantCulture).StartsWith(familyName.ToLower(CultureInfo.InvariantCulture))) |
|||
{ |
|||
if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) && |
|||
TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
glyphTypeface = null; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static bool TryGetNearestMatch( |
|||
ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
|||
FontCollectionKey key, |
|||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (key.Style != FontStyle.Normal) |
|||
{ |
|||
key = key with { Style = FontStyle.Normal }; |
|||
} |
|||
|
|||
if (key.Stretch != FontStretch.Normal) |
|||
{ |
|||
if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (key.Weight != FontWeight.Normal) |
|||
{ |
|||
if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
key = key with { Stretch = FontStretch.Normal }; |
|||
} |
|||
|
|||
if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
//Take the first glyph typeface we can find.
|
|||
foreach (var typeface in glyphTypefaces.Values) |
|||
{ |
|||
glyphTypeface = typeface; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static bool TryFindStretchFallback( |
|||
ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
|||
FontCollectionKey key, |
|||
[NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
|||
{ |
|||
glyphTypeface = null; |
|||
|
|||
var stretch = (int)key.Stretch; |
|||
|
|||
if (stretch < 5) |
|||
{ |
|||
for (var i = 0; stretch + i < 9; i++) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (var i = 0; stretch - i > 1; i++) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private static bool TryFindWeightFallback( |
|||
ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces, |
|||
FontCollectionKey key, |
|||
[NotNullWhen(true)] out IGlyphTypeface? typeface) |
|||
{ |
|||
typeface = null; |
|||
var weight = (int)key.Weight; |
|||
|
|||
//If the target weight given is between 400 and 500 inclusive
|
|||
if (weight >= 400 && weight <= 500) |
|||
{ |
|||
//Look for available weights between the target and 500, in ascending order.
|
|||
for (var i = 0; weight + i <= 500; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//If no match is found, look for available weights less than the target, in descending order.
|
|||
for (var i = 0; weight - i >= 100; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//If no match is found, look for available weights greater than 500, in ascending order.
|
|||
for (var i = 0; weight + i <= 900; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
//If a weight less than 400 is given, look for available weights less than the target, in descending order.
|
|||
if (weight < 400) |
|||
{ |
|||
for (var i = 0; weight - i >= 100; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//If no match is found, look for available weights less than the target, in descending order.
|
|||
for (var i = 0; weight + i <= 900; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
//If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
|
|||
if (weight > 500) |
|||
{ |
|||
for (var i = 0; weight + i <= 900; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
//If no match is found, look for available weights less than the target, in descending order.
|
|||
for (var i = 0; weight - i >= 100; i += 50) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
namespace Avalonia.Media.Fonts |
|||
{ |
|||
public readonly record struct FontCollectionKey(FontStyle Style, FontWeight Weight, FontStretch Stretch); |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media.Fonts |
|||
{ |
|||
public interface IFontCollection : IReadOnlyList<FontFamily>, IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Get the font collection's key.
|
|||
/// </summary>
|
|||
Uri Key { get; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes the font collection.
|
|||
/// </summary>
|
|||
/// <param name="fontManager">The font manager the collection is registered with.</param>
|
|||
void Initialize(IFontManagerImpl fontManager); |
|||
|
|||
/// <summary>
|
|||
/// Try to get a glyph typeface for given parameters.
|
|||
/// </summary>
|
|||
/// <param name="familyName">The family name.</param>
|
|||
/// <param name="style">The font style.</param>
|
|||
/// <param name="weight">The font weight.</param>
|
|||
/// <param name="stretch">The font stretch.</param>
|
|||
/// <param name="glyphTypeface">The glyph typeface.</param>
|
|||
/// <returns>Returns <c>true</c> if a glyph typface can be found; otherwise, <c>false</c></returns>
|
|||
bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
|||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media.Fonts |
|||
{ |
|||
internal class SystemFontCollection : IFontCollection |
|||
{ |
|||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = new(); |
|||
|
|||
private readonly FontManager _fontManager; |
|||
private readonly string[] _familyNames; |
|||
|
|||
public SystemFontCollection(FontManager fontManager) |
|||
{ |
|||
_fontManager = fontManager; |
|||
_familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames(); |
|||
} |
|||
|
|||
public Uri Key => FontManager.SystemFontsKey; |
|||
|
|||
public FontFamily this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
var familyName = _familyNames[index]; |
|||
|
|||
return new FontFamily(familyName); |
|||
} |
|||
} |
|||
|
|||
public int Count => _familyNames.Length; |
|||
|
|||
public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, |
|||
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) |
|||
{ |
|||
if (familyName == FontFamily.DefaultFontFamilyName) |
|||
{ |
|||
familyName = _fontManager.DefaultFontFamilyName; |
|||
} |
|||
|
|||
var key = new FontCollectionKey(style, weight, stretch); |
|||
|
|||
if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces)) |
|||
{ |
|||
if (glyphTypefaces.TryGetValue(key, out glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) && |
|||
glyphTypefaces.TryAdd(key, glyphTypeface)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface)) |
|||
{ |
|||
glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>(); |
|||
|
|||
if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Initialize(IFontManagerImpl fontManager) |
|||
{ |
|||
//We initialize the system font collection during construction.
|
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return GetEnumerator(); |
|||
} |
|||
|
|||
public IEnumerator<FontFamily> GetEnumerator() |
|||
{ |
|||
foreach (var familyName in _familyNames) |
|||
{ |
|||
yield return new FontFamily(familyName); |
|||
} |
|||
} |
|||
|
|||
void IDisposable.Dispose() |
|||
{ |
|||
foreach (var glyphTypefaces in _glyphTypefaceCache.Values) |
|||
{ |
|||
foreach (var pair in glyphTypefaces) |
|||
{ |
|||
pair.Value.Dispose(); |
|||
} |
|||
} |
|||
|
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using Avalonia.Automation.Peers; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
namespace Avalonia.Controls.Automation.Peers |
|||
{ |
|||
public class LabelAutomationPeer : ControlAutomationPeer |
|||
{ |
|||
public LabelAutomationPeer(Label owner) : base(owner) |
|||
{ |
|||
} |
|||
|
|||
override protected string GetClassNameCore() |
|||
{ |
|||
return "Text"; |
|||
} |
|||
|
|||
override protected AutomationControlType GetAutomationControlTypeCore() |
|||
{ |
|||
return AutomationControlType.Text; |
|||
} |
|||
|
|||
override protected string? GetNameCore() |
|||
{ |
|||
var content = ((Label)Owner).Content as string; |
|||
|
|||
if (string.IsNullOrEmpty(content)) |
|||
{ |
|||
return base.GetNameCore(); |
|||
} |
|||
|
|||
return AccessText.RemoveAccessKeyMarker(content) ?? string.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using Avalonia.Metadata; |
|||
|
|||
#nullable enable |
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
[Unstable] |
|||
[NotClientImplementable] |
|||
public interface IInsetsManager |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets whether the system bars are visible.
|
|||
/// </summary>
|
|||
bool? IsSystemBarVisible { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the window draws edge to edge. behind any visibile system bars.
|
|||
/// </summary>
|
|||
bool DisplayEdgeToEdge { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the current safe area padding.
|
|||
/// </summary>
|
|||
Thickness SafeAreaPadding { get; } |
|||
|
|||
/// <summary>
|
|||
/// Occurs when safe area for the current window changes.
|
|||
/// </summary>
|
|||
event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged; |
|||
} |
|||
|
|||
public class SafeAreaChangedArgs : EventArgs |
|||
{ |
|||
public SafeAreaChangedArgs(Thickness safeArePadding) |
|||
{ |
|||
SafeAreaPadding = safeArePadding; |
|||
} |
|||
|
|||
/// <inheritdoc cref="IInsetsManager.GetSafeAreaPadding"/>
|
|||
public Thickness SafeAreaPadding { get; } |
|||
} |
|||
|
|||
public enum SystemBarTheme |
|||
{ |
|||
/// <summary>
|
|||
/// Light system bar theme, with light background and a dark foreground
|
|||
/// </summary>
|
|||
Light, |
|||
|
|||
/// <summary>
|
|||
/// Bark system bar theme, with dark background and a light foreground
|
|||
/// </summary>
|
|||
Dark |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue