committed by
GitHub
77 changed files with 1997 additions and 511 deletions
@ -0,0 +1,22 @@ |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Controls; |
|||
|
|||
/// <summary>
|
|||
/// Resource provider with theme variant awareness.
|
|||
/// Can be used with <see cref="IResourceDictionary.ThemeDictionaries"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions.
|
|||
/// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code.
|
|||
/// This API might be removed in the future minor updates.
|
|||
/// </remarks>
|
|||
[Unstable] |
|||
public interface IThemeVariantProvider : IResourceProvider |
|||
{ |
|||
/// <summary>
|
|||
/// Key property set by the compiler.
|
|||
/// </summary>
|
|||
ThemeVariant? Key { get; set; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata; |
|||
|
|||
/// <summary>
|
|||
/// Defines how compiler should split avalonia list string value before parsing individual items.
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] |
|||
public sealed class AvaloniaListAttribute : Attribute |
|||
{ |
|||
/// <summary>
|
|||
/// Separator used to split input string.
|
|||
/// Default value is ','.
|
|||
/// </summary>
|
|||
public string[]? Separators { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Split options used to split input string.
|
|||
/// Default value is RemoveEmptyEntries with TrimEntries.
|
|||
/// </summary>
|
|||
// StringSplitOptions.TrimEntries = 2, but only on net6 target.
|
|||
public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2; |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform.Storage.FileIO; |
|||
|
|||
namespace Avalonia.Platform.Storage; |
|||
|
|||
internal class NoopStorageProvider : BclStorageProvider |
|||
{ |
|||
public override bool CanOpen => false; |
|||
public override Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options) |
|||
{ |
|||
return Task.FromResult<IReadOnlyList<IStorageFile>>(Array.Empty<IStorageFile>()); |
|||
} |
|||
|
|||
public override bool CanSave => false; |
|||
public override Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) |
|||
{ |
|||
return Task.FromResult<IStorageFile?>(null); |
|||
} |
|||
|
|||
public override bool CanPickFolder => false; |
|||
public override Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) |
|||
{ |
|||
return Task.FromResult<IReadOnlyList<IStorageFolder>>(Array.Empty<IStorageFolder>()); |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
|
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\build\ApiDiff.props" /> |
|||
<Import Project="..\..\build\DevAnalyzers.props" /> |
|||
<Import Project="..\..\build\TrimmingEnable.props" /> |
|||
</Project> |
|||
@ -1,12 +0,0 @@ |
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<!-- Accent Colours --> |
|||
<!-- TODO pull accents from system... algorithm to generate shades --> |
|||
<Color x:Key="SystemAccentColor">#FF0078D7</Color> |
|||
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color> |
|||
<Color x:Key="SystemAccentColorDark2">#FF004275</Color> |
|||
<Color x:Key="SystemAccentColorDark3">#FF002642</Color> |
|||
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color> |
|||
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color> |
|||
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color> |
|||
</ResourceDictionary> |
|||
@ -0,0 +1,69 @@ |
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN --> |
|||
<ResourceDictionary.ThemeDictionaries> |
|||
<ResourceDictionary x:Key="Default"> |
|||
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color> |
|||
<Color x:Key="SystemAltLowColor">#33FFFFFF</Color> |
|||
<Color x:Key="SystemAltMediumColor">#99FFFFFF</Color> |
|||
<Color x:Key="SystemAltMediumHighColor">#CCFFFFFF</Color> |
|||
<Color x:Key="SystemAltMediumLowColor">#66FFFFFF</Color> |
|||
<Color x:Key="SystemBaseHighColor">#FF000000</Color> |
|||
<Color x:Key="SystemBaseLowColor">#33000000</Color> |
|||
<Color x:Key="SystemBaseMediumColor">#99000000</Color> |
|||
<Color x:Key="SystemBaseMediumHighColor">#CC000000</Color> |
|||
<Color x:Key="SystemBaseMediumLowColor">#66000000</Color> |
|||
<Color x:Key="SystemChromeAltLowColor">#FF171717</Color> |
|||
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color> |
|||
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color> |
|||
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color> |
|||
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color> |
|||
<Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color> |
|||
<Color x:Key="SystemChromeDisabledLowColor">#FF7A7A7A</Color> |
|||
<Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color> |
|||
<Color x:Key="SystemChromeLowColor">#FFF2F2F2</Color> |
|||
<Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color> |
|||
<Color x:Key="SystemChromeMediumLowColor">#FFF2F2F2</Color> |
|||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> |
|||
<Color x:Key="SystemChromeGrayColor">#FF767676</Color> |
|||
<Color x:Key="SystemListLowColor">#19000000</Color> |
|||
<Color x:Key="SystemListMediumColor">#33000000</Color> |
|||
<Color x:Key="SystemErrorTextColor">#C50500</Color> |
|||
<Color x:Key="SystemRegionColor">#FFFFFFFF</Color> |
|||
<Color x:Key="SystemRevealListLowColor">#17000000</Color> |
|||
<Color x:Key="SystemRevealListMediumColor">#2E000000</Color> |
|||
</ResourceDictionary> |
|||
<ResourceDictionary x:Key="Dark"> |
|||
<Color x:Key="SystemAltHighColor">#FF000000</Color> |
|||
<Color x:Key="SystemAltLowColor">#33000000</Color> |
|||
<Color x:Key="SystemAltMediumColor">#99000000</Color> |
|||
<Color x:Key="SystemAltMediumHighColor">#CC000000</Color> |
|||
<Color x:Key="SystemAltMediumLowColor">#66000000</Color> |
|||
<Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color> |
|||
<Color x:Key="SystemBaseLowColor">#33FFFFFF</Color> |
|||
<Color x:Key="SystemBaseMediumColor">#99FFFFFF</Color> |
|||
<Color x:Key="SystemBaseMediumHighColor">#CCFFFFFF</Color> |
|||
<Color x:Key="SystemBaseMediumLowColor">#66FFFFFF</Color> |
|||
<Color x:Key="SystemChromeAltLowColor">#FFF2F2F2</Color> |
|||
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color> |
|||
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color> |
|||
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color> |
|||
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color> |
|||
<Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color> |
|||
<Color x:Key="SystemChromeDisabledLowColor">#FF858585</Color> |
|||
<Color x:Key="SystemChromeHighColor">#FF767676</Color> |
|||
<Color x:Key="SystemChromeLowColor">#FF171717</Color> |
|||
<Color x:Key="SystemChromeMediumColor">#FF1F1F1F</Color> |
|||
<Color x:Key="SystemChromeMediumLowColor">#FF2B2B2B</Color> |
|||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> |
|||
<Color x:Key="SystemChromeGrayColor">#FF767676</Color> |
|||
<Color x:Key="SystemListLowColor">#19FFFFFF</Color> |
|||
<Color x:Key="SystemListMediumColor">#33FFFFFF</Color> |
|||
<Color x:Key="SystemErrorTextColor">#FFF000</Color> |
|||
<Color x:Key="SystemRegionColor">#FF000000</Color> |
|||
<Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color> |
|||
<Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.ThemeDictionaries> |
|||
|
|||
</ResourceDictionary> |
|||
@ -0,0 +1,163 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Themes.Fluent.Accents; |
|||
|
|||
internal class SystemAccentColors : IResourceProvider |
|||
{ |
|||
public const string AccentKey = "SystemAccentColor"; |
|||
public const string AccentDark1Key = "SystemAccentColorDark1"; |
|||
public const string AccentDark2Key = "SystemAccentColorDark2"; |
|||
public const string AccentDark3Key = "SystemAccentColorDark3"; |
|||
public const string AccentLight1Key = "SystemAccentColorLight1"; |
|||
public const string AccentLight2Key = "SystemAccentColorLight2"; |
|||
public const string AccentLight3Key = "SystemAccentColorLight3"; |
|||
|
|||
private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); |
|||
private readonly IPlatformSettings? _platformSettings; |
|||
private bool _invalidateColors = true; |
|||
private Color _systemAccentColor; |
|||
private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3; |
|||
private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3; |
|||
|
|||
public SystemAccentColors() |
|||
{ |
|||
_platformSettings = AvaloniaLocator.Current.GetService<IPlatformSettings>(); |
|||
} |
|||
|
|||
public bool HasResources => true; |
|||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value) |
|||
{ |
|||
if (key is string strKey) |
|||
{ |
|||
if (strKey.Equals(AccentKey, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColor; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentDark1Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorDark1; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentDark2Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorDark2; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentDark3Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorDark3; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentLight1Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorLight1; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentLight2Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorLight2; |
|||
return true; |
|||
} |
|||
|
|||
if (strKey.Equals(AccentLight3Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
EnsureColors(); |
|||
value = _systemAccentColorLight3; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
value = null; |
|||
return false; |
|||
} |
|||
|
|||
public IResourceHost? Owner { get; private set; } |
|||
public event EventHandler? OwnerChanged; |
|||
public void AddOwner(IResourceHost owner) |
|||
{ |
|||
if (Owner != owner) |
|||
{ |
|||
Owner = owner; |
|||
OwnerChanged?.Invoke(this, EventArgs.Empty); |
|||
|
|||
if (_platformSettings is not null) |
|||
{ |
|||
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void RemoveOwner(IResourceHost owner) |
|||
{ |
|||
if (Owner == owner) |
|||
{ |
|||
Owner = null; |
|||
OwnerChanged?.Invoke(this, EventArgs.Empty); |
|||
|
|||
if (_platformSettings is not null) |
|||
{ |
|||
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void EnsureColors() |
|||
{ |
|||
if (_invalidateColors) |
|||
{ |
|||
_invalidateColors = false; |
|||
|
|||
_systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; |
|||
(_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3, |
|||
_systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor); |
|||
} |
|||
} |
|||
|
|||
public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor) |
|||
{ |
|||
// dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255
|
|||
const double dark1step = 28.5 / 255d; |
|||
const double dark2step = 49 / 255d; |
|||
const double dark3step = 74.5 / 255d; |
|||
// light1step = (SystemAccentColorLight1.L - hslAccent.L) * 255
|
|||
const double light1step = 39 / 255d; |
|||
const double light2step = 70 / 255d; |
|||
const double light3step = 103 / 255d; |
|||
|
|||
var hslAccent = accentColor.ToHsl(); |
|||
|
|||
return ( |
|||
// Darker shades
|
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark1step).ToRgb(), |
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark2step).ToRgb(), |
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark3step).ToRgb(), |
|||
|
|||
// Lighter shades
|
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light1step).ToRgb(), |
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light2step).ToRgb(), |
|||
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light3step).ToRgb() |
|||
); |
|||
} |
|||
|
|||
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e) |
|||
{ |
|||
_invalidateColors = true; |
|||
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Themes.Fluent; |
|||
|
|||
public partial class ColorPaletteResources |
|||
{ |
|||
private bool _hasAccentColor; |
|||
private Color _accentColor; |
|||
private Color _accentColorDark1, _accentColorDark2, _accentColorDark3; |
|||
private Color _accentColorLight1, _accentColorLight2, _accentColorLight3; |
|||
|
|||
public static readonly DirectProperty<ColorPaletteResources, Color> AccentProperty |
|||
= AvaloniaProperty.RegisterDirect<ColorPaletteResources, Color>(nameof(Accent), r => r.Accent, (r, v) => r.Accent = v); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Accent color value.
|
|||
/// </summary>
|
|||
public Color Accent |
|||
{ |
|||
get => _accentColor; |
|||
set => SetAndRaise(AccentProperty, ref _accentColor, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AltHigh color value.
|
|||
/// </summary>
|
|||
public Color AltHigh { get => GetColor("SystemAltHighColor"); set => SetColor("SystemAltHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AltLow color value.
|
|||
/// </summary>
|
|||
public Color AltLow { get => GetColor("SystemAltLowColor"); set => SetColor("SystemAltLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AltMedium color value.
|
|||
/// </summary>
|
|||
public Color AltMedium { get => GetColor("SystemAltMediumColor"); set => SetColor("SystemAltMediumColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AltMediumHigh color value.
|
|||
/// </summary>
|
|||
public Color AltMediumHigh { get => GetColor("SystemAltMediumHighColor"); set => SetColor("SystemAltMediumHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AltMediumLow color value.
|
|||
/// </summary>
|
|||
public Color AltMediumLow { get => GetColor("SystemAltMediumLowColor"); set => SetColor("SystemAltMediumLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the BaseHigh color value.
|
|||
/// </summary>
|
|||
public Color BaseHigh { get => GetColor("SystemBaseHighColor"); set => SetColor("SystemBaseHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the BaseLow color value.
|
|||
/// </summary>
|
|||
public Color BaseLow { get => GetColor("SystemBaseLowColor"); set => SetColor("SystemBaseLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the BaseMedium color value.
|
|||
/// </summary>
|
|||
public Color BaseMedium { get => GetColor("SystemBaseMediumColor"); set => SetColor("SystemBaseMediumColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the BaseMediumHigh color value.
|
|||
/// </summary>
|
|||
public Color BaseMediumHigh { get => GetColor("SystemBaseMediumHighColor"); set => SetColor("SystemBaseMediumHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the BaseMediumLow color value.
|
|||
/// </summary>
|
|||
public Color BaseMediumLow { get => GetColor("SystemBaseMediumLowColor"); set => SetColor("SystemBaseMediumLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeAltLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeAltLow { get => GetColor("SystemChromeAltLowColor"); set => SetColor("SystemChromeAltLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeBlackHigh color value.
|
|||
/// </summary>
|
|||
public Color ChromeBlackHigh { get => GetColor("SystemChromeBlackHighColor"); set => SetColor("SystemChromeBlackHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeBlackLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeBlackLow { get => GetColor("SystemChromeBlackLowColor"); set => SetColor("SystemChromeBlackLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeBlackMedium color value.
|
|||
/// </summary>
|
|||
public Color ChromeBlackMedium { get => GetColor("SystemChromeBlackMediumColor"); set => SetColor("SystemChromeBlackMediumColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeBlackMediumLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeBlackMediumLow { get => GetColor("SystemChromeBlackMediumLowColor"); set => SetColor("SystemChromeBlackMediumLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeDisabledHigh color value.
|
|||
/// </summary>
|
|||
public Color ChromeDisabledHigh { get => GetColor("SystemChromeDisabledHighColor"); set => SetColor("SystemChromeDisabledHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeDisabledLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeDisabledLow { get => GetColor("SystemChromeDisabledLowColor"); set => SetColor("SystemChromeDisabledLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeGray color value.
|
|||
/// </summary>
|
|||
public Color ChromeGray { get => GetColor("SystemChromeGrayColor"); set => SetColor("SystemChromeGrayColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeHigh color value.
|
|||
/// </summary>
|
|||
public Color ChromeHigh { get => GetColor("SystemChromeHighColor"); set => SetColor("SystemChromeHighColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeLow { get => GetColor("SystemChromeLowColor"); set => SetColor("SystemChromeLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeMedium color value.
|
|||
/// </summary>
|
|||
public Color ChromeMedium { get => GetColor("SystemChromeMediumColor"); set => SetColor("SystemChromeMediumColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeMediumLow color value.
|
|||
/// </summary>
|
|||
public Color ChromeMediumLow { get => GetColor("SystemChromeMediumLowColor"); set => SetColor("SystemChromeMediumLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ChromeWhite color value.
|
|||
/// </summary>
|
|||
public Color ChromeWhite { get => GetColor("SystemChromeWhiteColor"); set => SetColor("SystemChromeWhiteColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ErrorText color value.
|
|||
/// </summary>
|
|||
public Color ErrorText { get => GetColor("SystemErrorTextColor"); set => SetColor("SystemErrorTextColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ListLow color value.
|
|||
/// </summary>
|
|||
public Color ListLow { get => GetColor("SystemListLowColor"); set => SetColor("SystemListLowColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ListMedium color value.
|
|||
/// </summary>
|
|||
public Color ListMedium { get => GetColor("SystemListMediumColor"); set => SetColor("SystemListMediumColor", value); } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the RegionColor color value.
|
|||
/// </summary>
|
|||
public Color RegionColor { get => GetColor("SystemRegionColor"); set => SetColor("SystemRegionColor", value); } |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Avalonia.Themes.Fluent.Accents; |
|||
|
|||
namespace Avalonia.Themes.Fluent; |
|||
|
|||
/// <summary>
|
|||
/// Represents a specialized resource dictionary that contains color resources used by FluentTheme elements.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This class can only be used in <see cref="FluentTheme.Palettes"/>.
|
|||
/// </remarks>
|
|||
public partial class ColorPaletteResources : AvaloniaObject, IResourceNode |
|||
{ |
|||
private readonly Dictionary<string, Color> _colors = new(StringComparer.InvariantCulture); |
|||
|
|||
public bool HasResources => _hasAccentColor || _colors.Count > 0; |
|||
|
|||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value) |
|||
{ |
|||
if (key is string strKey) |
|||
{ |
|||
if (strKey.Equals(SystemAccentColors.AccentKey, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColor; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentDark1Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorDark1; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentDark2Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorDark2; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentDark3Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorDark3; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentLight1Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorLight1; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentLight2Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorLight2; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (strKey.Equals(SystemAccentColors.AccentLight3Key, StringComparison.InvariantCulture)) |
|||
{ |
|||
value = _accentColorLight3; |
|||
return _hasAccentColor; |
|||
} |
|||
|
|||
if (_colors.TryGetValue(strKey, out var color)) |
|||
{ |
|||
value = color; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
value = null; |
|||
return false; |
|||
} |
|||
|
|||
private Color GetColor(string key) |
|||
{ |
|||
if (_colors.TryGetValue(key, out var color)) |
|||
{ |
|||
return color; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
|
|||
private void SetColor(string key, Color value) |
|||
{ |
|||
if (value == default) |
|||
{ |
|||
_colors.Remove(key); |
|||
} |
|||
else |
|||
{ |
|||
_colors[key] = value; |
|||
} |
|||
} |
|||
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == AccentProperty) |
|||
{ |
|||
_hasAccentColor = _accentColor != default; |
|||
|
|||
if (_hasAccentColor) |
|||
{ |
|||
(_accentColorDark1, _accentColorDark2, _accentColorDark3, |
|||
_accentColorLight1, _accentColorLight2, _accentColorLight3) = |
|||
SystemAccentColors.CalculateAccentShades(_accentColor); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Themes.Fluent; |
|||
|
|||
internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant, ColorPaletteResources>, IResourceProvider |
|||
{ |
|||
public ColorPaletteResourcesCollection() : base(2) |
|||
{ |
|||
this.ForEachItem( |
|||
(_, x) => |
|||
{ |
|||
if (Owner is not null) |
|||
{ |
|||
x.PropertyChanged += Palette_PropertyChanged; |
|||
} |
|||
}, |
|||
(_, x) => |
|||
{ |
|||
if (Owner is not null) |
|||
{ |
|||
x.PropertyChanged -= Palette_PropertyChanged; |
|||
} |
|||
}, |
|||
() => throw new NotSupportedException("Dictionary reset not supported")); |
|||
} |
|||
|
|||
public bool HasResources => Count > 0; |
|||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value) |
|||
{ |
|||
theme ??= ThemeVariant.Default; |
|||
if (base.TryGetValue(theme, out var paletteResources) |
|||
&& paletteResources.TryGetResource(key, theme, out value)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
value = null; |
|||
return false; |
|||
} |
|||
|
|||
public IResourceHost? Owner { get; private set; } |
|||
public event EventHandler? OwnerChanged; |
|||
public void AddOwner(IResourceHost owner) |
|||
{ |
|||
Owner = owner; |
|||
OwnerChanged?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
public void RemoveOwner(IResourceHost owner) |
|||
{ |
|||
Owner = null; |
|||
OwnerChanged?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
private void Palette_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == ColorPaletteResources.AccentProperty) |
|||
{ |
|||
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="xunit.core" Version="2.4.2" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\..\build\ApiDiff.props" /> |
|||
<Import Project="..\..\..\build\DevAnalyzers.props" /> |
|||
<Import Project="..\..\..\build\NullableEnable.props" /> |
|||
</Project> |
|||
@ -0,0 +1,35 @@ |
|||
using System.Reflection; |
|||
using Xunit.Abstractions; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Avalonia.Headless.XUnit; |
|||
|
|||
internal class AvaloniaTestFramework<TAppBuilderEntry> : XunitTestFramework |
|||
{ |
|||
public AvaloniaTestFramework(IMessageSink messageSink) : base(messageSink) |
|||
{ |
|||
} |
|||
|
|||
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) |
|||
=> new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); |
|||
|
|||
|
|||
private class Executor : XunitTestFrameworkExecutor |
|||
{ |
|||
public Executor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, |
|||
IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider, |
|||
diagnosticMessageSink) |
|||
{ |
|||
} |
|||
|
|||
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, |
|||
IMessageSink executionMessageSink, |
|||
ITestFrameworkExecutionOptions executionOptions) |
|||
{ |
|||
executionOptions.SetValue("xunit.execution.DisableParallelization", false); |
|||
using (var assemblyRunner = new AvaloniaTestRunner<TAppBuilderEntry>( |
|||
TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, |
|||
executionOptions)) await assemblyRunner.RunAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Xunit.Abstractions; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Avalonia.Headless.XUnit; |
|||
|
|||
/// <summary>
|
|||
/// Sets up global avalonia test framework using avalonia application builder passed as a parameter.
|
|||
/// </summary>
|
|||
[TestFrameworkDiscoverer("Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer", "Avalonia.Headless.XUnit")] |
|||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] |
|||
public sealed class AvaloniaTestFrameworkAttribute : Attribute, ITestFrameworkAttribute |
|||
{ |
|||
/// <summary>
|
|||
/// Creates instance of <see cref="AvaloniaTestFrameworkAttribute"/>.
|
|||
/// </summary>
|
|||
/// <param name="appBuilderEntryPointType">
|
|||
/// Parameter from which <see cref="AppBuilder"/> should be created.
|
|||
/// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
|
|||
/// </param>
|
|||
public AvaloniaTestFrameworkAttribute( |
|||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] |
|||
Type appBuilderEntryPointType) { } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Discoverer implementation for the Avalonia testing framework.
|
|||
/// </summary>
|
|||
public class AvaloniaTestFrameworkTypeDiscoverer : ITestFrameworkTypeDiscoverer |
|||
{ |
|||
/// <summary>
|
|||
/// Creates instance of <see cref="AvaloniaTestFrameworkTypeDiscoverer"/>.
|
|||
/// </summary>
|
|||
public AvaloniaTestFrameworkTypeDiscoverer(IMessageSink _) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Type GetTestFrameworkType(IAttributeInfo attribute) |
|||
{ |
|||
var builderType = attribute.GetConstructorArguments().First() as Type |
|||
?? throw new InvalidOperationException("AppBuilderEntryPointType parameter must be defined on the AvaloniaTestFrameworkAttribute attribute."); |
|||
return typeof(AvaloniaTestFramework<>).MakeGenericType(builderType); |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using Avalonia.Threading; |
|||
using Xunit.Abstractions; |
|||
using Xunit.Sdk; |
|||
|
|||
namespace Avalonia.Headless.XUnit; |
|||
|
|||
internal class AvaloniaTestRunner<TAppBuilderEntry> : XunitTestAssemblyRunner |
|||
{ |
|||
private CancellationTokenSource? _cancellationTokenSource; |
|||
|
|||
public AvaloniaTestRunner(ITestAssembly testAssembly, IEnumerable<IXunitTestCase> testCases, |
|||
IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, |
|||
ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink, |
|||
executionMessageSink, executionOptions) |
|||
{ |
|||
} |
|||
|
|||
protected override void SetupSyncContext(int maxParallelThreads) |
|||
{ |
|||
_cancellationTokenSource?.Dispose(); |
|||
_cancellationTokenSource = new CancellationTokenSource(); |
|||
SynchronizationContext.SetSynchronizationContext(InitNewApplicationContext(_cancellationTokenSource.Token).Result); |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
_cancellationTokenSource?.Cancel(); |
|||
base.Dispose(); |
|||
} |
|||
|
|||
internal static Task<SynchronizationContext> InitNewApplicationContext(CancellationToken cancellationToken) |
|||
{ |
|||
var tcs = new TaskCompletionSource<SynchronizationContext>(); |
|||
|
|||
new Thread(() => |
|||
{ |
|||
try |
|||
{ |
|||
var appBuilder = AppBuilder.Configure(typeof(TAppBuilderEntry)); |
|||
|
|||
// If windowing subsystem wasn't initialized by user, force headless with default parameters.
|
|||
if (appBuilder.WindowingSubsystemName != "Headless") |
|||
{ |
|||
appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions()); |
|||
} |
|||
|
|||
appBuilder.SetupWithoutStarting(); |
|||
|
|||
tcs.SetResult(SynchronizationContext.Current!); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
tcs.SetException(e); |
|||
} |
|||
|
|||
Dispatcher.UIThread.MainLoop(cancellationToken); |
|||
}) { IsBackground = true }.Start(); |
|||
|
|||
return tcs.Task; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\..\build\ApiDiff.props" /> |
|||
<Import Project="..\..\..\build\DevAnalyzers.props" /> |
|||
<Import Project="..\..\..\build\TrimmingEnable.props" /> |
|||
<Import Project="..\..\..\build\NullableEnable.props" /> |
|||
|
|||
<ItemGroup Label="InternalsVisibleTo"> |
|||
<InternalsVisibleTo Include="Avalonia.Headless.Vnc, PublicKey=$(AvaloniaPublicKey)" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,101 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Headless; |
|||
|
|||
/// <summary>
|
|||
/// Set of extension methods to simplify usage of Avalonia.Headless platform.
|
|||
/// </summary>
|
|||
public static class HeadlessWindowExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Triggers a renderer timer tick and captures last rendered frame.
|
|||
/// </summary>
|
|||
/// <returns>Bitmap with last rendered frame. Null, if nothing was rendered.</returns>
|
|||
public static Bitmap? CaptureRenderedFrame(this TopLevel topLevel) |
|||
{ |
|||
Dispatcher.UIThread.RunJobs(); |
|||
AvaloniaHeadlessPlatform.ForceRenderTimerTick(); |
|||
return topLevel.GetLastRenderedFrame(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads last rendered frame.
|
|||
/// Note, in order to trigger rendering timer, call <see cref="AvaloniaHeadlessPlatform.ForceRenderTimerTick"/> method.
|
|||
/// </summary>
|
|||
/// <returns>Bitmap with last rendered frame. Null, if nothing was rendered.</returns>
|
|||
public static Bitmap? GetLastRenderedFrame(this TopLevel topLevel) |
|||
{ |
|||
if (AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is HeadlessPlatformRenderInterface) |
|||
{ |
|||
throw new NotSupportedException( |
|||
"To capture a rendered frame, make sure that headless application was initialized with '.UseSkia()' and disabled 'UseHeadlessDrawing' in the 'AvaloniaHeadlessPlatformOptions'."); |
|||
} |
|||
|
|||
return GetImpl(topLevel).GetLastRenderedFrame(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Simulates keyboard press on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers) => |
|||
RunJobsAndGetImpl(topLevel).KeyPress(key, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates keyboard release on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers) => |
|||
RunJobsAndGetImpl(topLevel).KeyRelease(key, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates mouse down on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void MouseDown(this TopLevel topLevel, Point point, MouseButton button, |
|||
RawInputModifiers modifiers = RawInputModifiers.None) => |
|||
RunJobsAndGetImpl(topLevel).MouseDown(point, button, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates mouse move on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void MouseMove(this TopLevel topLevel, Point point, |
|||
RawInputModifiers modifiers = RawInputModifiers.None) => |
|||
RunJobsAndGetImpl(topLevel).MouseMove(point, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates mouse up on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void MouseUp(this TopLevel topLevel, Point point, MouseButton button, |
|||
RawInputModifiers modifiers = RawInputModifiers.None) => |
|||
RunJobsAndGetImpl(topLevel).MouseUp(point, button, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates mouse wheel on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void MouseWheel(this TopLevel topLevel, Point point, Vector delta, |
|||
RawInputModifiers modifiers = RawInputModifiers.None) => |
|||
RunJobsAndGetImpl(topLevel).MouseWheel(point, delta, modifiers); |
|||
|
|||
/// <summary>
|
|||
/// Simulates drag'n'drop target on the headless window/toplevel.
|
|||
/// </summary>
|
|||
public static void DragDrop(this TopLevel topLevel, Point point, RawDragEventType type, IDataObject data, |
|||
DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) => |
|||
RunJobsAndGetImpl(topLevel).DragDrop(point, type, data, effects, modifiers); |
|||
|
|||
private static IHeadlessWindow RunJobsAndGetImpl(this TopLevel topLevel) |
|||
{ |
|||
Dispatcher.UIThread.RunJobs(); |
|||
return GetImpl(topLevel); |
|||
} |
|||
|
|||
private static IHeadlessWindow GetImpl(this TopLevel topLevel) |
|||
{ |
|||
return topLevel.PlatformImpl as IHeadlessWindow ?? |
|||
throw new InvalidOperationException("TopLevel must be a headless window."); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System.Linq; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Transform; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
|
|||
internal class AvaloniaXamlIlThemeVariantProviderTransformer : IXamlAstTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
var type = context.GetAvaloniaTypes().IThemeVariantProvider; |
|||
if (!(node is XamlAstObjectNode on |
|||
&& type.IsAssignableFrom(on.Type.GetClrType()))) |
|||
return node; |
|||
|
|||
var keyDirective = on.Children.FirstOrDefault(n => n is XamlAstXmlDirective d |
|||
&& d.Namespace == XamlNamespaces.Xaml2006 && |
|||
d.Name == "Key") as XamlAstXmlDirective; |
|||
if (keyDirective is null) |
|||
return node; |
|||
|
|||
var keyProp = type.Properties.First(p => p.Name == "Key"); |
|||
on.Children.Add(new XamlAstXamlPropertyValueNode(keyDirective, |
|||
new XamlAstClrProperty(keyDirective, keyProp, context.Configuration), |
|||
keyDirective.Values, true)); |
|||
|
|||
return node; |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 5d1025f30d0ed6d8f419d82959c148276301f393 |
|||
Subproject commit e5254eb1b2017f78a92acd466c8fa1e47401056b |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<IsTestProject>true</IsTestProject> |
|||
</PropertyGroup> |
|||
<Import Project="..\..\build\UnitTests.NetCore.targets" /> |
|||
<Import Project="..\..\build\UnitTests.NetFX.props" /> |
|||
<Import Project="..\..\build\Moq.props" /> |
|||
<Import Project="..\..\build\XUnit.props" /> |
|||
<Import Project="..\..\build\Rx.props" /> |
|||
<Import Project="..\..\build\SharedVersion.props" /> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" /> |
|||
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj" /> |
|||
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,36 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Threading; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Headless.UnitTests; |
|||
|
|||
public class InputTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Click_Button_On_Window() |
|||
{ |
|||
var buttonClicked = false; |
|||
var button = new Button |
|||
{ |
|||
HorizontalAlignment = HorizontalAlignment.Stretch, |
|||
VerticalAlignment = VerticalAlignment.Stretch |
|||
}; |
|||
|
|||
button.Click += (_, _) => buttonClicked = true; |
|||
|
|||
var window = new Window |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Content = button |
|||
}; |
|||
window.Show(); |
|||
|
|||
window.MouseDown(new Point(50, 50), MouseButton.Left); |
|||
window.MouseUp(new Point(50, 50), MouseButton.Left); |
|||
|
|||
Assert.True(buttonClicked); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Threading; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Headless.UnitTests; |
|||
|
|||
public class RenderingTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Render_Last_Frame_To_Bitmap() |
|||
{ |
|||
var window = new Window |
|||
{ |
|||
Content = new ContentControl |
|||
{ |
|||
HorizontalAlignment = HorizontalAlignment.Stretch, |
|||
VerticalAlignment = VerticalAlignment.Stretch, |
|||
Padding = new Thickness(4), |
|||
Content = new PathIcon |
|||
{ |
|||
Data = StreamGeometry.Parse("M0,9 L10,0 20,9 19,10 10,2 1,10 z") |
|||
} |
|||
}, |
|||
SizeToContent = SizeToContent.WidthAndHeight |
|||
}; |
|||
window.Show(); |
|||
|
|||
var frame = window.CaptureRenderedFrame(); |
|||
Assert.NotNull(frame); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Avalonia.Headless.UnitTests; |
|||
using Avalonia.Headless.XUnit; |
|||
using Avalonia.Themes.Simple; |
|||
using Xunit; |
|||
|
|||
[assembly: AvaloniaTestFramework(typeof(TestApplication))] |
|||
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|||
|
|||
namespace Avalonia.Headless.UnitTests; |
|||
|
|||
public class TestApplication : Application |
|||
{ |
|||
public TestApplication() |
|||
{ |
|||
Styles.Add(new SimpleTheme()); |
|||
} |
|||
|
|||
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<TestApplication>() |
|||
.UseSkia() |
|||
.UseHeadless(new AvaloniaHeadlessPlatformOptions |
|||
{ |
|||
UseHeadlessDrawing = false |
|||
}); |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Threading; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Headless.UnitTests; |
|||
|
|||
public class ThreadingTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Be_On_Dispatcher_Thread() |
|||
{ |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task DispatcherTimer_Works_On_The_Same_Thread() |
|||
{ |
|||
var currentThread = Thread.CurrentThread; |
|||
var tcs = new TaskCompletionSource(); |
|||
|
|||
DispatcherTimer.RunOnce(() => |
|||
{ |
|||
Assert.Equal(currentThread, Thread.CurrentThread); |
|||
|
|||
tcs.SetResult(); |
|||
}, TimeSpan.FromTicks(1)); |
|||
|
|||
await tcs.Task; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue