141 changed files with 2607 additions and 814 deletions
@ -1,5 +1,5 @@ |
|||
<Project> |
|||
<PropertyGroup Condition="$(NETCoreSdkVersion.StartsWith('7.0'))"> |
|||
<PropertyGroup Condition="$([MSBuild]::VersionGreaterThanOrEquals($(NETCoreSdkVersion), '7.0'))"> |
|||
<DefineConstants>$(DefineConstants);NET7SDK</DefineConstants> |
|||
</PropertyGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.IO; |
|||
using Nuke.Common.Utilities; |
|||
|
|||
class Helpers |
|||
{ |
|||
public static IDisposable UseTempDir(out string dir) |
|||
{ |
|||
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); |
|||
Directory.CreateDirectory(path); |
|||
dir = path; |
|||
return DelegateDisposable.CreateBracket(null, () => |
|||
{ |
|||
try |
|||
{ |
|||
Directory.Delete(path, true); |
|||
} |
|||
catch |
|||
{ |
|||
// ignore
|
|||
} |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Linq; |
|||
using ILRepacking; |
|||
using Mono.Cecil; |
|||
using Mono.Cecil.Cil; |
|||
|
|||
public class RefAssemblyGenerator |
|||
{ |
|||
class Resolver : DefaultAssemblyResolver, IAssemblyResolver |
|||
{ |
|||
private readonly string _dir; |
|||
Dictionary<string, AssemblyDefinition> _cache = new(); |
|||
|
|||
public Resolver(string dir) |
|||
{ |
|||
_dir = dir; |
|||
} |
|||
|
|||
public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) |
|||
{ |
|||
if (_cache.TryGetValue(name.Name, out var asm)) |
|||
return asm; |
|||
var path = Path.Combine(_dir, name.Name + ".dll"); |
|||
if (File.Exists(path)) |
|||
return _cache[name.Name] = AssemblyDefinition.ReadAssembly(path, parameters); |
|||
return base.Resolve(name, parameters); |
|||
} |
|||
} |
|||
|
|||
public static void PatchRefAssembly(string file) |
|||
{ |
|||
var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk"); |
|||
var snk = new byte[reader.Length]; |
|||
reader.Read(snk, 0, snk.Length); |
|||
|
|||
var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters |
|||
{ |
|||
ReadWrite = true, |
|||
InMemory = true, |
|||
ReadSymbols = true, |
|||
SymbolReaderProvider = new DefaultSymbolReaderProvider(false), |
|||
AssemblyResolver = new Resolver(Path.GetDirectoryName(file)) |
|||
}); |
|||
|
|||
var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule, |
|||
def.MainModule.TypeSystem.CoreLibrary)); |
|||
var obsoleteCtor = def.MainModule.ImportReference(new MethodReference(".ctor", |
|||
def.MainModule.TypeSystem.Void, obsoleteAttribute) |
|||
{ |
|||
Parameters = { new ParameterDefinition(def.MainModule.TypeSystem.String) } |
|||
}); |
|||
|
|||
foreach(var t in def.MainModule.Types) |
|||
ProcessType(t, obsoleteCtor); |
|||
def.Write(file, new WriterParameters() |
|||
{ |
|||
StrongNameKeyBlob = snk, |
|||
WriteSymbols = def.MainModule.HasSymbols, |
|||
SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(), |
|||
DeterministicMvid = def.MainModule.HasSymbols |
|||
}); |
|||
} |
|||
|
|||
static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor) |
|||
{ |
|||
foreach (var nested in type.NestedTypes) |
|||
ProcessType(nested, obsoleteCtor); |
|||
if (type.IsInterface) |
|||
{ |
|||
var hideMethods = type.Name.EndsWith("Impl") |
|||
|| (type.HasCustomAttributes && type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute")); |
|||
|
|||
var injectMethod = hideMethods |
|||
|| type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute"); |
|||
|
|||
if (hideMethods) |
|||
{ |
|||
foreach (var m in type.Methods) |
|||
{ |
|||
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem | |
|||
MethodAttributes.FamANDAssem | MethodAttributes.Assembly; |
|||
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly; |
|||
} |
|||
} |
|||
|
|||
if(injectMethod) |
|||
{ |
|||
type.Methods.Add(new MethodDefinition("NotClientImplementable", |
|||
MethodAttributes.Assembly |
|||
| MethodAttributes.Abstract |
|||
| MethodAttributes.NewSlot |
|||
| MethodAttributes.HideBySig, type.Module.TypeSystem.Void)); |
|||
} |
|||
|
|||
var forceUnstable = type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute"); |
|||
|
|||
foreach (var m in type.Methods) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
foreach (var m in type.Properties) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
foreach (var m in type.Events) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
|
|||
} |
|||
} |
|||
|
|||
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) |
|||
{ |
|||
if (!force && ( |
|||
def.HasCustomAttributes == false |
|||
|| def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute"))) |
|||
return; |
|||
|
|||
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) |
|||
return; |
|||
|
|||
def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor) |
|||
{ |
|||
ConstructorArguments = |
|||
{ |
|||
new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, |
|||
"This is a part of unstable API and can be changed in minor releases. You have been warned") |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void GenerateRefAsmsInPackage(string packagePath) |
|||
{ |
|||
using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), |
|||
ZipArchiveMode.Update)) |
|||
{ |
|||
foreach (var entry in archive.Entries.ToList()) |
|||
{ |
|||
if (entry.FullName.StartsWith("ref/")) |
|||
entry.Delete(); |
|||
} |
|||
|
|||
foreach (var entry in archive.Entries.ToList()) |
|||
{ |
|||
if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml")) |
|||
{ |
|||
var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4), |
|||
CompressionLevel.Optimal); |
|||
using (var src = entry.Open()) |
|||
using (var dst = newEntry.Open()) |
|||
src.CopyTo(dst); |
|||
} |
|||
} |
|||
|
|||
var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll")) |
|||
.Select((e => new { s = e.FullName.Split('/'), e = e })) |
|||
.Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e }) |
|||
.GroupBy(x => x.Tfm); |
|||
foreach(var tfm in libs) |
|||
using (Helpers.UseTempDir(out var temp)) |
|||
{ |
|||
foreach (var l in tfm) |
|||
l.Entry.ExtractToFile(Path.Combine(temp, l.Name)); |
|||
foreach (var l in tfm) |
|||
PatchRefAssembly(Path.Combine(temp, l.Name)); |
|||
foreach (var l in tfm) |
|||
archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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,9 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata; |
|||
|
|||
[AttributeUsage(AttributeTargets.Interface)] |
|||
public sealed class PrivateApiAttribute : Attribute |
|||
{ |
|||
|
|||
} |
|||
@ -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,6 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public sealed class Points : AvaloniaList<Point> { } |
|||
public sealed class Points : AvaloniaList<Point> |
|||
{ |
|||
public Points() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Points(IEnumerable<Point> points) : base(points) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,12 @@ |
|||
using Avalonia.Automation.Peers; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
namespace Avalonia.Controls.Automation.Peers |
|||
{ |
|||
public class ThumbAutomationPeer : ControlAutomationPeer |
|||
{ |
|||
public ThumbAutomationPeer(Thumb owner) : base(owner) { } |
|||
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Thumb; |
|||
protected override bool IsContentElementCore() => false; |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System; |
|||
using Avalonia.Layout; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Describes the reason for a <see cref="WindowBase.Resized"/> event.
|
|||
/// </summary>
|
|||
public enum WindowResizeReason |
|||
{ |
|||
/// <summary>
|
|||
/// The resize reason is unknown or unspecified.
|
|||
/// </summary>
|
|||
Unspecified, |
|||
|
|||
/// <summary>
|
|||
/// The resize was due to the user resizing the window, for example by dragging the
|
|||
/// window frame.
|
|||
/// </summary>
|
|||
User, |
|||
|
|||
/// <summary>
|
|||
/// The resize was initiated by the application, for example by setting one of the sizing-
|
|||
/// related properties on <see cref="Window"/> such as <see cref="Layoutable.Width"/> or
|
|||
/// <see cref="Layoutable.Height"/>.
|
|||
/// </summary>
|
|||
Application, |
|||
|
|||
/// <summary>
|
|||
/// The resize was initiated by the layout system.
|
|||
/// </summary>
|
|||
Layout, |
|||
|
|||
/// <summary>
|
|||
/// The resize was due to a change in DPI.
|
|||
/// </summary>
|
|||
DpiChange, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides data for the <see cref="WindowBase.Resized"/> event.
|
|||
/// </summary>
|
|||
public class WindowResizedEventArgs : EventArgs |
|||
{ |
|||
internal WindowResizedEventArgs(Size clientSize, WindowResizeReason reason) |
|||
{ |
|||
ClientSize = clientSize; |
|||
Reason = reason; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the new client size of the window in device-independent pixels.
|
|||
/// </summary>
|
|||
public Size ClientSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reason for the resize.
|
|||
/// </summary>
|
|||
public WindowResizeReason Reason { get; } |
|||
} |
|||
} |
|||
@ -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,71 @@ |
|||
using System.Collections.Generic; |
|||
using System.Reflection.Emit; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Emit; |
|||
using XamlX.IL; |
|||
using XamlX.Transform; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes |
|||
{ |
|||
class AvaloniaXamlIlArrayConstantAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode |
|||
{ |
|||
private readonly IXamlType _elementType; |
|||
private readonly IReadOnlyList<IXamlAstValueNode> _values; |
|||
|
|||
public AvaloniaXamlIlArrayConstantAstNode(IXamlLineInfo lineInfo, IXamlType arrayType, IXamlType elementType, IReadOnlyList<IXamlAstValueNode> values) : base(lineInfo) |
|||
{ |
|||
_elementType = elementType; |
|||
_values = values; |
|||
|
|||
Type = new XamlAstClrTypeReference(lineInfo, arrayType, false); |
|||
|
|||
foreach (var element in values) |
|||
{ |
|||
if (!elementType.IsAssignableFrom(element.Type.GetClrType())) |
|||
{ |
|||
throw new XamlParseException("x:Array element is not assignable to the array element type!", lineInfo); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IXamlAstTypeReference Type { get; } |
|||
|
|||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
|||
{ |
|||
codeGen.Ldc_I4(_values.Count) |
|||
.Newarr(_elementType); |
|||
|
|||
for (var index = 0; index < _values.Count; index++) |
|||
{ |
|||
var value = _values[index]; |
|||
|
|||
codeGen |
|||
.Dup() |
|||
.Ldc_I4(index); |
|||
|
|||
context.Emit(value, codeGen, _elementType); |
|||
|
|||
if (value.Type.GetClrType() is { IsValueType: true } valTypeInObjArr) |
|||
{ |
|||
if (!_elementType.IsValueType) |
|||
{ |
|||
codeGen.Box(valTypeInObjArr); |
|||
} |
|||
// It seems like ASM codegen for "stelem valuetype" and "stelem.i4" is identical,
|
|||
// so we don't need to try to optimize it here.
|
|||
codeGen.Emit(OpCodes.Stelem, valTypeInObjArr); |
|||
} |
|||
else |
|||
{ |
|||
codeGen.Stelem_ref(); |
|||
} |
|||
} |
|||
|
|||
return XamlILNodeEmitResult.Type(0, Type.GetClrType()); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue