committed by
GitHub
273 changed files with 13602 additions and 4639 deletions
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Linq; |
|||
using ILRepacking; |
|||
using Mono.Cecil; |
|||
|
|||
public class BuildTasksPatcher |
|||
{ |
|||
public static void PatchBuildTasksInPackage(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.Name == "Avalonia.Build.Tasks.dll") |
|||
{ |
|||
var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll"); |
|||
var output = temp + ".output"; |
|||
var patched = new MemoryStream(); |
|||
try |
|||
{ |
|||
entry.ExtractToFile(temp, true); |
|||
var repack = new ILRepacking.ILRepack(new RepackOptions() |
|||
{ |
|||
Internalize = true, |
|||
InputAssemblies = new[] |
|||
{ |
|||
temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0] |
|||
.FullyQualifiedName |
|||
}, |
|||
SearchDirectories = new string[0], |
|||
OutputFile = output |
|||
}); |
|||
repack.Repack(); |
|||
|
|||
|
|||
// 'hurr-durr assembly with the same name is already loaded' prevention
|
|||
using (var asm = AssemblyDefinition.ReadAssembly(output, |
|||
new ReaderParameters { ReadWrite = true, InMemory = true, })) |
|||
{ |
|||
asm.Name = new AssemblyNameDefinition( |
|||
"Avalonia.Build.Tasks." |
|||
+ Guid.NewGuid().ToString().Replace("-", ""), |
|||
new Version(0, 0, 0)); |
|||
asm.Write(patched); |
|||
patched.Position = 0; |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
try |
|||
{ |
|||
if (File.Exists(temp)) |
|||
File.Delete(temp); |
|||
if (File.Exists(output)) |
|||
File.Delete(output); |
|||
} |
|||
catch |
|||
{ |
|||
//ignore
|
|||
} |
|||
} |
|||
|
|||
var fn = entry.FullName; |
|||
entry.Delete(); |
|||
var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal); |
|||
using (var s = newEntry.Open()) |
|||
patched.CopyTo(s); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.AcrylicPage"> |
|||
<Border Padding="20" HorizontalAlignment="Center"> |
|||
<StackPanel Spacing="20"> |
|||
|
|||
<ExperimentalAcrylicBorder Width="660" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
<StackPanel Spacing="5" Margin="40 10"> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<TextBlock Text="TintOpacity" Foreground="Black" /> |
|||
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" /> |
|||
<TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" /> |
|||
</StackPanel> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<TextBlock Text="MaterialOpacity" Foreground="Black" /> |
|||
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" /> |
|||
<TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" /> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#FF0000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#00FF00" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#000000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#0000FF" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#FFFF00" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#000000" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Orientation="Horizontal" Spacing="20"> |
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="#3c3c3c" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="White" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
|
|||
<ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5"> |
|||
<ExperimentalAcrylicBorder.Material> |
|||
<ExperimentalAcrylicMaterial |
|||
TintColor="Red" |
|||
TintOpacity="{Binding #TintOpacitySlider.Value}" |
|||
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}" |
|||
BackgroundSource="Digger" /> |
|||
</ExperimentalAcrylicBorder.Material> |
|||
</ExperimentalAcrylicBorder> |
|||
</StackPanel> |
|||
</Border> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class AcrylicPage : UserControl |
|||
{ |
|||
public AcrylicPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> |
|||
<StackPanel Spacing="10" Margin="25"> |
|||
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" /> |
|||
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" /> |
|||
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" /> |
|||
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" /> |
|||
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Transparent</ComboBoxItem> |
|||
<ComboBoxItem>Blur</ComboBoxItem> |
|||
<ComboBoxItem>AcrylicBlur</ComboBoxItem> |
|||
</ComboBox> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class WindowCustomizationsPage : UserControl |
|||
{ |
|||
public WindowCustomizationsPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class ClrPropertyInfo : IPropertyInfo |
|||
{ |
|||
private readonly Func<object, object> _getter; |
|||
private readonly Action<object, object> _setter; |
|||
|
|||
public ClrPropertyInfo(string name, Func<object, object> getter, Action<object, object> setter, Type propertyType) |
|||
{ |
|||
_getter = getter; |
|||
_setter = setter; |
|||
PropertyType = propertyType; |
|||
Name = name; |
|||
} |
|||
|
|||
public string Name { get; } |
|||
public Type PropertyType { get; } |
|||
|
|||
public object Get(object target) |
|||
{ |
|||
if (_getter == null) |
|||
throw new NotSupportedException("Property " + Name + " doesn't have a getter"); |
|||
return _getter(target); |
|||
} |
|||
|
|||
public void Set(object target, object value) |
|||
{ |
|||
if (_setter == null) |
|||
throw new NotSupportedException("Property " + Name + " doesn't have a setter"); |
|||
_setter(target, value); |
|||
} |
|||
|
|||
public bool CanSet => _setter != null; |
|||
public bool CanGet => _getter != null; |
|||
} |
|||
|
|||
public class ReflectionClrPropertyInfo : ClrPropertyInfo |
|||
{ |
|||
static Action<object, object> CreateSetter(PropertyInfo info) |
|||
{ |
|||
if (info.SetMethod == null) |
|||
return null; |
|||
var target = Expression.Parameter(typeof(object), "target"); |
|||
var value = Expression.Parameter(typeof(object), "value"); |
|||
return Expression.Lambda<Action<object, object>>( |
|||
Expression.Call(Expression.Convert(target, info.DeclaringType), info.SetMethod, |
|||
Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)), |
|||
target, value) |
|||
.Compile(); |
|||
} |
|||
|
|||
static Func<object, object> CreateGetter(PropertyInfo info) |
|||
{ |
|||
if (info.GetMethod == null) |
|||
return null; |
|||
var target = Expression.Parameter(typeof(object), "target"); |
|||
return Expression.Lambda<Func<object, object>>( |
|||
Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType), info.GetMethod), |
|||
typeof(object))) |
|||
.Compile(); |
|||
} |
|||
|
|||
public ReflectionClrPropertyInfo(PropertyInfo info) : base(info.Name, |
|||
CreateGetter(info), CreateSetter(info), info.PropertyType) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public interface IPropertyInfo |
|||
{ |
|||
string Name { get; } |
|||
object Get(object target); |
|||
void Set(object target, object value); |
|||
bool CanSet { get; } |
|||
bool CanGet { get; } |
|||
Type PropertyType { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class PropertyPath |
|||
{ |
|||
public IReadOnlyList<IPropertyPathElement> Elements { get; } |
|||
|
|||
public PropertyPath(IEnumerable<IPropertyPathElement> elements) |
|||
{ |
|||
Elements = elements.ToList(); |
|||
} |
|||
} |
|||
|
|||
public class PropertyPathBuilder |
|||
{ |
|||
readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>(); |
|||
|
|||
public PropertyPathBuilder Property(IPropertyInfo property) |
|||
{ |
|||
_elements.Add(new PropertyPropertyPathElement(property)); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
public PropertyPathBuilder ChildTraversal() |
|||
{ |
|||
_elements.Add(new ChildTraversalPropertyPathElement()); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder EnsureType(Type type) |
|||
{ |
|||
_elements.Add(new EnsureTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder Cast(Type type) |
|||
{ |
|||
_elements.Add(new CastTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPath Build() |
|||
{ |
|||
return new PropertyPath(_elements); |
|||
} |
|||
} |
|||
|
|||
public interface IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class PropertyPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public IPropertyInfo Property { get; } |
|||
|
|||
public PropertyPropertyPathElement(IPropertyInfo property) |
|||
{ |
|||
Property = property; |
|||
} |
|||
} |
|||
|
|||
public class ChildTraversalPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class EnsureTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public Type Type { get; } |
|||
|
|||
public EnsureTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
} |
|||
|
|||
public class CastTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public CastTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
|
|||
public Type Type { get; } |
|||
} |
|||
} |
|||
@ -1,35 +1,54 @@ |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Data.Core.Plugins; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class StreamNode : ExpressionNode |
|||
{ |
|||
private IStreamPlugin _customPlugin = null; |
|||
private IDisposable _subscription; |
|||
|
|||
public override string Description => "^"; |
|||
|
|||
public StreamNode() { } |
|||
|
|||
public StreamNode(IStreamPlugin customPlugin) |
|||
{ |
|||
_customPlugin = customPlugin; |
|||
} |
|||
|
|||
protected override void StartListeningCore(WeakReference<object> reference) |
|||
{ |
|||
GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged); |
|||
} |
|||
|
|||
protected override void StopListeningCore() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
} |
|||
|
|||
private IStreamPlugin GetPlugin(WeakReference<object> reference) |
|||
{ |
|||
if (_customPlugin != null) |
|||
{ |
|||
return _customPlugin; |
|||
} |
|||
|
|||
foreach (var plugin in ExpressionObserver.StreamHandlers) |
|||
{ |
|||
if (plugin.Match(reference)) |
|||
{ |
|||
_subscription = plugin.Start(reference).Subscribe(ValueChanged); |
|||
return; |
|||
return plugin; |
|||
} |
|||
} |
|||
|
|||
// TODO: Improve error.
|
|||
// TODO: Improve error
|
|||
ValueChanged(new BindingNotification( |
|||
new MarkupBindingChainException("Stream operator applied to unsupported type", Description), |
|||
BindingErrorType.Error)); |
|||
} |
|||
|
|||
protected override void StopListeningCore() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
static class KeywordParser |
|||
{ |
|||
public static bool CheckKeyword(this ref CharacterReader r, string keyword) |
|||
{ |
|||
return (CheckKeywordInternal(ref r, keyword) >= 0); |
|||
} |
|||
|
|||
static int CheckKeywordInternal(this ref CharacterReader r, string keyword) |
|||
{ |
|||
var ws = r.PeekWhitespace(); |
|||
|
|||
var chars = r.TryPeek(ws.Length + keyword.Length); |
|||
if (chars.IsEmpty) |
|||
return -1; |
|||
if (SpanEquals(chars.Slice(ws.Length), keyword.AsSpan())) |
|||
return chars.Length; |
|||
return -1; |
|||
} |
|||
|
|||
static bool SpanEquals(ReadOnlySpan<char> left, ReadOnlySpan<char> right) |
|||
{ |
|||
if (left.Length != right.Length) |
|||
return false; |
|||
for(var c=0; c<left.Length;c++) |
|||
if (left[c] != right[c]) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
public static bool TakeIfKeyword(this ref CharacterReader r, string keyword) |
|||
{ |
|||
var l = CheckKeywordInternal(ref r, keyword); |
|||
if (l < 0) |
|||
return false; |
|||
r.Skip(l); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines compensation levels for the platform depending on the transparency level.
|
|||
/// It controls the base opacity level of the 'tracing paper' layer that compensates
|
|||
/// for low blur radius.
|
|||
/// </summary>
|
|||
public struct AcrylicPlatformCompensationLevels |
|||
{ |
|||
public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic) |
|||
{ |
|||
TransparentLevel = transparent; |
|||
BlurLevel = blurred; |
|||
AcrylicBlurLevel = acrylic; |
|||
} |
|||
|
|||
public double TransparentLevel { get; } |
|||
|
|||
public double BlurLevel { get; } |
|||
|
|||
public double AcrylicBlurLevel { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class CaptionButtons : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private Window? _hostWindow; |
|||
|
|||
public void Attach(Window hostWindow) |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
|
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton"); |
|||
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton"); |
|||
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); |
|||
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); |
|||
|
|||
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); |
|||
|
|||
restoreButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; |
|||
} |
|||
}; |
|||
|
|||
minimiseButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = WindowState.Minimized; |
|||
} |
|||
}; |
|||
|
|||
fullScreenButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws a titlebar when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class TitleBar : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private readonly Window? _hostWindow; |
|||
private CaptionButtons? _captionButtons; |
|||
|
|||
public TitleBar(Window hostWindow) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
} |
|||
|
|||
public TitleBar() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Attach() |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Add(this); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.OffScreenMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
|
|||
_captionButtons?.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
|
|||
private void UpdateSize() |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
Margin = new Thickness( |
|||
_hostWindow.OffScreenMargin.Left, |
|||
_hostWindow.OffScreenMargin.Top, |
|||
_hostWindow.OffScreenMargin.Right, |
|||
_hostWindow.OffScreenMargin.Bottom); |
|||
|
|||
if (_hostWindow.WindowState != WindowState.FullScreen) |
|||
{ |
|||
Height = _hostWindow.WindowDecorationMargin.Top; |
|||
|
|||
if (_captionButtons != null) |
|||
{ |
|||
_captionButtons.Height = Height; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
|
|||
_captionButtons?.Detach(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_captionButtons.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class ExperimentalAcrylicBorder : Decorator |
|||
{ |
|||
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty = |
|||
Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>(); |
|||
|
|||
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty = |
|||
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material)); |
|||
|
|||
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); |
|||
|
|||
private IDisposable _subscription; |
|||
|
|||
static ExperimentalAcrylicBorder() |
|||
{ |
|||
AffectsRender<ExperimentalAcrylicBorder>( |
|||
MaterialProperty, |
|||
CornerRadiusProperty); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the radius of the border rounded corners.
|
|||
/// </summary>
|
|||
public CornerRadius CornerRadius |
|||
{ |
|||
get { return GetValue(CornerRadiusProperty); } |
|||
set { SetValue(CornerRadiusProperty, value); } |
|||
} |
|||
|
|||
public ExperimentalAcrylicMaterial Material |
|||
{ |
|||
get => GetValue(MaterialProperty); |
|||
set => SetValue(MaterialProperty, value); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
var tl = (e.Root as TopLevel); |
|||
|
|||
_subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
switch (x) |
|||
{ |
|||
case WindowTransparencyLevel.Transparent: |
|||
case WindowTransparencyLevel.None: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; |
|||
break; |
|||
|
|||
case WindowTransparencyLevel.Blur: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; |
|||
break; |
|||
|
|||
case WindowTransparencyLevel.AcrylicBlur: |
|||
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel; |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
|
|||
_subscription?.Dispose(); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) |
|||
{ |
|||
var cornerRadius = CornerRadius; |
|||
|
|||
idc.DrawRectangle( |
|||
Material, |
|||
new RoundedRect( |
|||
new Rect(Bounds.Size), |
|||
cornerRadius.TopLeft, cornerRadius.TopRight, |
|||
cornerRadius.BottomRight, cornerRadius.BottomLeft)); |
|||
} |
|||
else |
|||
{ |
|||
_borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the control.
|
|||
/// </summary>
|
|||
/// <param name="availableSize">The available size.</param>
|
|||
/// <returns>The desired size of the control.</returns>
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
return LayoutHelper.MeasureChild(Child, availableSize, Padding); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Arranges the control's child.
|
|||
/// </summary>
|
|||
/// <param name="finalSize">The size allocated to the control.</param>
|
|||
/// <returns>The space taken.</returns>
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
return LayoutHelper.ArrangeChild(Child, finalSize, Padding); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Hint for Window Chrome when ClientArea is Extended.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum ExtendClientAreaChromeHints |
|||
{ |
|||
/// <summary>
|
|||
/// The will be no chrome at all.
|
|||
/// </summary>
|
|||
NoChrome, |
|||
|
|||
/// <summary>
|
|||
/// The default for the platform.
|
|||
/// </summary>
|
|||
Default = SystemChrome, |
|||
|
|||
/// <summary>
|
|||
/// Use SystemChrome
|
|||
/// </summary>
|
|||
SystemChrome = 0x01, |
|||
|
|||
/// <summary>
|
|||
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
|
|||
/// This is because Windows Chrome can not be shown ontop of user content.
|
|||
/// </summary>
|
|||
PreferSystemChrome = 0x02, |
|||
|
|||
/// <summary>
|
|||
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
|
|||
/// slightly lower than normal.
|
|||
/// </summary>
|
|||
OSXThickTitleBar = 0x08, |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System.Linq; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.VisualTree; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest |
|||
{ |
|||
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) |
|||
{ |
|||
foreach (var v in visual.GetVisualAncestors()) |
|||
if (v is VisualLayerManager vlm) |
|||
if (vlm.OverlayLayer != null) |
|||
return vlm.ChromeOverlayLayer; |
|||
|
|||
if (visual is TopLevel tl) |
|||
{ |
|||
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
|||
return layers?.ChromeOverlayLayer; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public bool HitTest(Point point) => Children.HitTestCustom(point); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" /> |
|||
<PackageReference Include="Quamotion.RemoteViewing" Version="1.1.21" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,96 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Threading; |
|||
using RemoteViewing.Vnc; |
|||
using RemoteViewing.Vnc.Server; |
|||
|
|||
namespace Avalonia.Headless.Vnc |
|||
{ |
|||
public class HeadlessVncFramebufferSource : IVncFramebufferSource |
|||
{ |
|||
public IHeadlessWindow Window { get; set; } |
|||
private object _lock = new object(); |
|||
public VncFramebuffer _framebuffer = new VncFramebuffer("Avalonia", 1, 1, VncPixelFormat.RGB32); |
|||
|
|||
private VncButton _previousButtons; |
|||
public HeadlessVncFramebufferSource(VncServerSession session, Window window) |
|||
{ |
|||
Window = (IHeadlessWindow)window.PlatformImpl; |
|||
session.PointerChanged += (_, args) => |
|||
{ |
|||
var pt = new Point(args.X, args.Y); |
|||
|
|||
var buttons = (VncButton)args.PressedButtons; |
|||
|
|||
int TranslateButton(VncButton vncButton) => |
|||
vncButton == VncButton.Left ? 0 : vncButton == VncButton.Right ? 1 : 2; |
|||
|
|||
var modifiers = (RawInputModifiers)(((int)buttons & 7) << 4); |
|||
|
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
Window?.MouseMove(pt); |
|||
foreach (var btn in CheckedButtons) |
|||
if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn)) |
|||
Window?.MouseUp(pt, TranslateButton(btn), modifiers); |
|||
|
|||
foreach (var btn in CheckedButtons) |
|||
if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn)) |
|||
Window?.MouseDown(pt, TranslateButton(btn), modifiers); |
|||
_previousButtons = buttons; |
|||
}, DispatcherPriority.Input); |
|||
}; |
|||
|
|||
} |
|||
|
|||
[Flags] |
|||
enum VncButton |
|||
{ |
|||
Left = 1, |
|||
Middle = 2, |
|||
Right = 4, |
|||
ScrollUp = 8, |
|||
ScrollDown = 16 |
|||
} |
|||
|
|||
|
|||
private static VncButton[] CheckedButtons = new[] {VncButton.Left, VncButton.Middle, VncButton.Right}; |
|||
|
|||
public VncFramebuffer Capture() |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
using (var bmpRef = Window.GetLastRenderedFrame()) |
|||
{ |
|||
if (bmpRef?.Item == null) |
|||
return _framebuffer; |
|||
var bmp = bmpRef.Item; |
|||
if (bmp.PixelSize.Width != _framebuffer.Width || bmp.PixelSize.Height != _framebuffer.Height) |
|||
{ |
|||
_framebuffer = new VncFramebuffer("Avalonia", bmp.PixelSize.Width, bmp.PixelSize.Height, |
|||
VncPixelFormat.RGB32); |
|||
} |
|||
|
|||
using (var fb = bmp.Lock()) |
|||
{ |
|||
var buf = _framebuffer.GetBuffer(); |
|||
if (_framebuffer.Stride == fb.RowBytes) |
|||
Marshal.Copy(fb.Address, buf, 0, buf.Length); |
|||
else |
|||
for (var y = 0; y < fb.Size.Height; y++) |
|||
{ |
|||
var sourceStart = fb.RowBytes * y; |
|||
var dstStart = _framebuffer.Stride * y; |
|||
var row = fb.Size.Width * 4; |
|||
Marshal.Copy(new IntPtr(sourceStart + fb.Address.ToInt64()), buf, dstStart, row); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return _framebuffer; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Headless; |
|||
using Avalonia.Headless.Vnc; |
|||
using RemoteViewing.Vnc; |
|||
using RemoteViewing.Vnc.Server; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class HeadlessVncPlatformExtensions |
|||
{ |
|||
public static int StartWithHeadlessVncPlatform<T>( |
|||
this T builder, |
|||
string host, int port, |
|||
string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
var tcpServer = new TcpListener(host == null ? IPAddress.Loopback : IPAddress.Parse(host), port); |
|||
tcpServer.Start(); |
|||
return builder |
|||
.UseHeadless(false) |
|||
.AfterSetup(_ => |
|||
{ |
|||
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime); |
|||
lt.Startup += async delegate |
|||
{ |
|||
while (true) |
|||
{ |
|||
var client = await tcpServer.AcceptTcpClientAsync(); |
|||
var options = new VncServerSessionOptions |
|||
{ |
|||
AuthenticationMethod = AuthenticationMethod.None |
|||
}; |
|||
var session = new VncServerSession(); |
|||
|
|||
session.SetFramebufferSource(new HeadlessVncFramebufferSource( |
|||
session, lt.MainWindow)); |
|||
session.Connect(client.GetStream(), options); |
|||
} |
|||
|
|||
}; |
|||
}) |
|||
.StartWithClassicDesktopLifetime(args, shutdownMode); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
public static class AvaloniaHeadlessPlatform |
|||
{ |
|||
class RenderTimer : DefaultRenderTimer |
|||
{ |
|||
private readonly int _framesPerSecond; |
|||
private Action _forceTick; |
|||
protected override IDisposable StartCore(Action<TimeSpan> tick) |
|||
{ |
|||
bool cancelled = false; |
|||
var st = Stopwatch.StartNew(); |
|||
_forceTick = () => tick(st.Elapsed); |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
if (cancelled) |
|||
return false; |
|||
tick(st.Elapsed); |
|||
return !cancelled; |
|||
}, TimeSpan.FromSeconds(1.0 / _framesPerSecond), DispatcherPriority.Render); |
|||
return Disposable.Create(() => |
|||
{ |
|||
_forceTick = null; |
|||
cancelled = true; |
|||
}); |
|||
} |
|||
|
|||
public RenderTimer(int framesPerSecond) : base(framesPerSecond) |
|||
{ |
|||
_framesPerSecond = framesPerSecond; |
|||
} |
|||
|
|||
public void ForceTick() => _forceTick?.Invoke(); |
|||
} |
|||
|
|||
class HeadlessWindowingPlatform : IWindowingPlatform |
|||
{ |
|||
public IWindowImpl CreateWindow() => new HeadlessWindowImpl(false); |
|||
|
|||
public IWindowImpl CreateEmbeddableWindow() => throw new PlatformNotSupportedException(); |
|||
|
|||
public IPopupImpl CreatePopup() => new HeadlessWindowImpl(true); |
|||
} |
|||
|
|||
internal static void Initialize() |
|||
{ |
|||
AvaloniaLocator.CurrentMutable |
|||
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface()) |
|||
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>() |
|||
.Bind<IStandardCursorFactory>().ToSingleton<HeadlessCursorFactoryStub>() |
|||
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub()) |
|||
.Bind<ISystemDialogImpl>().ToSingleton<HeadlessSystemDialogsStub>() |
|||
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>() |
|||
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice()) |
|||
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) |
|||
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60)) |
|||
.Bind<IFontManagerImpl>().ToSingleton<HeadlessFontManagerStub>() |
|||
.Bind<ITextShaperImpl>().ToSingleton<HeadlessTextShaperStub>() |
|||
.Bind<IWindowingPlatform>().ToConstant(new HeadlessWindowingPlatform()) |
|||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); |
|||
} |
|||
|
|||
|
|||
public static void ForceRenderTimerTick(int count = 1) |
|||
{ |
|||
var timer = AvaloniaLocator.Current.GetService<IRenderTimer>() as RenderTimer; |
|||
for (var c = 0; c < count; c++) |
|||
timer?.ForceTick(); |
|||
|
|||
} |
|||
} |
|||
|
|||
public static class AvaloniaHeadlessPlatformExtensions |
|||
{ |
|||
public static T UseHeadless<T>(this T builder, bool headlessDrawing = true) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
if (headlessDrawing) |
|||
builder.UseRenderingSubsystem(HeadlessPlatformRenderInterface.Initialize, "Headless"); |
|||
return builder.UseWindowingSubsystem(AvaloniaHeadlessPlatform.Initialize, "Headless"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,434 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Rendering.SceneGraph; |
|||
using Avalonia.Utilities; |
|||
using Avalonia.Visuals.Media.Imaging; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface |
|||
{ |
|||
public static void Initialize() |
|||
{ |
|||
AvaloniaLocator.CurrentMutable |
|||
.Bind<IPlatformRenderInterface>().ToConstant(new HeadlessPlatformRenderInterface()); |
|||
} |
|||
|
|||
public IEnumerable<string> InstalledFontNames { get; } = new[] { "Tahoma" }; |
|||
|
|||
public bool SupportsIndividualRoundRects => throw new NotImplementedException(); |
|||
|
|||
public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList<FormattedTextStyleSpan> spans) |
|||
{ |
|||
return new HeadlessFormattedTextStub(text, constraint); |
|||
} |
|||
|
|||
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect); |
|||
|
|||
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) |
|||
{ |
|||
var tl = new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)); |
|||
var br = new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)); |
|||
return new HeadlessGeometryStub(new Rect(tl, br)); |
|||
} |
|||
|
|||
public IGeometryImpl CreateRectangleGeometry(Rect rect) |
|||
{ |
|||
return new HeadlessGeometryStub(rect); |
|||
} |
|||
|
|||
public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub(); |
|||
|
|||
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget(); |
|||
|
|||
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) |
|||
{ |
|||
return new HeadlessBitmapStub(size, dpi); |
|||
} |
|||
|
|||
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) |
|||
{ |
|||
return new HeadlessBitmapStub(size, dpi); |
|||
} |
|||
|
|||
public IBitmapImpl LoadBitmap(string fileName) |
|||
{ |
|||
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); |
|||
} |
|||
|
|||
public IBitmapImpl LoadBitmap(Stream stream) |
|||
{ |
|||
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); |
|||
} |
|||
|
|||
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) |
|||
{ |
|||
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); |
|||
} |
|||
|
|||
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) |
|||
{ |
|||
return new HeadlessBitmapStub(new Size(width, width), new Vector(96, 96)); |
|||
} |
|||
|
|||
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) |
|||
{ |
|||
return new HeadlessBitmapStub(new Size(height, height), new Vector(96, 96)); |
|||
} |
|||
|
|||
public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) |
|||
{ |
|||
return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); |
|||
} |
|||
|
|||
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width) |
|||
{ |
|||
width = 100; |
|||
return new HeadlessGlyphRunStub(); |
|||
} |
|||
|
|||
class HeadlessGeometryStub : IGeometryImpl |
|||
{ |
|||
public HeadlessGeometryStub(Rect bounds) |
|||
{ |
|||
Bounds = bounds; |
|||
} |
|||
|
|||
public Rect Bounds { get; set; } |
|||
public virtual bool FillContains(Point point) => Bounds.Contains(point); |
|||
|
|||
public Rect GetRenderBounds(IPen pen) |
|||
{ |
|||
if(pen is null) |
|||
{ |
|||
return Bounds; |
|||
} |
|||
|
|||
return Bounds.Inflate(pen.Thickness / 2); |
|||
} |
|||
|
|||
public bool StrokeContains(IPen pen, Point point) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public IGeometryImpl Intersect(IGeometryImpl geometry) |
|||
=> new HeadlessGeometryStub(geometry.Bounds.Intersect(Bounds)); |
|||
|
|||
public ITransformedGeometryImpl WithTransform(Matrix transform) => |
|||
new HeadlessTransformedGeometryStub(this, transform); |
|||
} |
|||
|
|||
class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl |
|||
{ |
|||
public HeadlessTransformedGeometryStub(IGeometryImpl b, Matrix transform) : this(Fix(b, transform)) |
|||
{ |
|||
|
|||
} |
|||
|
|||
static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform) |
|||
{ |
|||
if (b is HeadlessTransformedGeometryStub transformed) |
|||
{ |
|||
b = transformed.SourceGeometry; |
|||
transform = transformed.Transform * transform; |
|||
} |
|||
|
|||
return (b, transform, b.Bounds.TransformToAABB(transform)); |
|||
} |
|||
|
|||
private HeadlessTransformedGeometryStub((IGeometryImpl b, Matrix transform, Rect bounds) fix) : base(fix.bounds) |
|||
{ |
|||
SourceGeometry = fix.b; |
|||
Transform = fix.transform; |
|||
} |
|||
|
|||
|
|||
public IGeometryImpl SourceGeometry { get; } |
|||
public Matrix Transform { get; } |
|||
} |
|||
|
|||
class HeadlessGlyphRunStub : IGlyphRunImpl |
|||
{ |
|||
public void Dispose() |
|||
{ |
|||
} |
|||
} |
|||
|
|||
class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl |
|||
{ |
|||
public HeadlessStreamingGeometryStub() : base(Rect.Empty) |
|||
{ |
|||
} |
|||
|
|||
public IStreamGeometryImpl Clone() |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
public IStreamGeometryContextImpl Open() |
|||
{ |
|||
return new HeadlessStreamingGeometryContextStub(this); |
|||
} |
|||
|
|||
class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl |
|||
{ |
|||
private readonly HeadlessStreamingGeometryStub _parent; |
|||
private double _x1, _y1, _x2, _y2; |
|||
public HeadlessStreamingGeometryContextStub(HeadlessStreamingGeometryStub parent) |
|||
{ |
|||
_parent = parent; |
|||
} |
|||
|
|||
void Track(Point pt) |
|||
{ |
|||
if (_x1 > pt.X) |
|||
_x1 = pt.X; |
|||
if (_x2 < pt.X) |
|||
_x2 = pt.X; |
|||
if (_y1 > pt.Y) |
|||
_y1 = pt.Y; |
|||
if (_y2 < pt.Y) |
|||
_y2 = pt.Y; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_parent.Bounds = new Rect(_x1, _y1, _x2 - _x1, _y2 - _y1); |
|||
} |
|||
|
|||
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) |
|||
=> Track(point); |
|||
|
|||
public void BeginFigure(Point startPoint, bool isFilled = true) => Track(startPoint); |
|||
|
|||
public void CubicBezierTo(Point point1, Point point2, Point point3) |
|||
{ |
|||
Track(point1); |
|||
Track(point2); |
|||
Track(point3); |
|||
} |
|||
|
|||
public void QuadraticBezierTo(Point control, Point endPoint) |
|||
{ |
|||
Track(control); |
|||
Track(endPoint); |
|||
} |
|||
|
|||
public void LineTo(Point point) => Track(point); |
|||
|
|||
public void EndFigure(bool isClosed) |
|||
{ |
|||
Dispose(); |
|||
} |
|||
|
|||
public void SetFillRule(FillRule fillRule) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
class HeadlessBitmapStub : IBitmapImpl, IRenderTargetBitmapImpl, IWriteableBitmapImpl |
|||
{ |
|||
public Size Size { get; } |
|||
|
|||
public HeadlessBitmapStub(Size size, Vector dpi) |
|||
{ |
|||
Size = size; |
|||
Dpi = dpi; |
|||
var pixel = Size * (Dpi / 96); |
|||
PixelSize = new PixelSize(Math.Max(1, (int)pixel.Width), Math.Max(1, (int)pixel.Height)); |
|||
} |
|||
|
|||
public HeadlessBitmapStub(PixelSize size, Vector dpi) |
|||
{ |
|||
PixelSize = size; |
|||
Dpi = dpi; |
|||
Size = PixelSize.ToSizeWithDpi(dpi); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
|||
{ |
|||
return new HeadlessDrawingContextStub(); |
|||
} |
|||
|
|||
public Vector Dpi { get; } |
|||
public PixelSize PixelSize { get; } |
|||
public int Version { get; set; } |
|||
public void Save(string fileName) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Save(Stream stream) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
Version++; |
|||
var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4); |
|||
return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888, |
|||
() => Marshal.FreeHGlobal(mem)); |
|||
} |
|||
} |
|||
|
|||
class HeadlessDrawingContextStub : IDrawingContextImpl |
|||
{ |
|||
public void Dispose() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Matrix Transform { get; set; } |
|||
public void Clear(Color color) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IRenderTargetBitmapImpl CreateLayer(Size size) |
|||
{ |
|||
return new HeadlessBitmapStub(size, new Vector(96, 96)); |
|||
} |
|||
|
|||
public void PushClip(Rect clip) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PopClip() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PushOpacity(double opacity) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PopOpacity() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PushOpacityMask(IBrush mask, Rect bounds) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PopOpacityMask() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PushGeometryClip(IGeometryImpl clip) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PopGeometryClip() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Custom(ICustomDrawOperation custom) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void DrawLine(IPen pen, Point p1, Point p2) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) |
|||
{ |
|||
} |
|||
|
|||
public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0) |
|||
{ |
|||
} |
|||
|
|||
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadow = default) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void PushClip(RoundedRect clip) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
|
|||
class HeadlessRenderTarget : IRenderTarget |
|||
{ |
|||
public void Dispose() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
|||
{ |
|||
return new HeadlessDrawingContextStub(); |
|||
} |
|||
} |
|||
|
|||
class HeadlessFormattedTextStub : IFormattedTextImpl |
|||
{ |
|||
public HeadlessFormattedTextStub(string text, Size constraint) |
|||
{ |
|||
Text = text; |
|||
Constraint = constraint; |
|||
Bounds = new Rect(Constraint.Constrain(new Size(50, 50))); |
|||
} |
|||
|
|||
public Size Constraint { get; } |
|||
public Rect Bounds { get; } |
|||
public string Text { get; } |
|||
|
|||
|
|||
public IEnumerable<FormattedTextLine> GetLines() |
|||
{ |
|||
return new[] { new FormattedTextLine(Text.Length, 10) }; |
|||
} |
|||
|
|||
public TextHitTestResult HitTestPoint(Point point) => new TextHitTestResult(); |
|||
|
|||
public Rect HitTestTextPosition(int index) => new Rect(); |
|||
|
|||
public IEnumerable<Rect> HitTestTextRange(int index, int length) => new Rect[length]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,201 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Fonts; |
|||
using Avalonia.Media.TextFormatting; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
class HeadlessClipboardStub : IClipboard |
|||
{ |
|||
private string _text; |
|||
private IDataObject _data; |
|||
|
|||
public Task<string> GetTextAsync() |
|||
{ |
|||
return Task.Run(() => _text); |
|||
} |
|||
|
|||
public Task SetTextAsync(string text) |
|||
{ |
|||
return Task.Run(() => _text = text); |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return Task.Run(() => _text = null); |
|||
} |
|||
|
|||
public Task SetDataObjectAsync(IDataObject data) |
|||
{ |
|||
return Task.Run(() => _data = data); |
|||
} |
|||
|
|||
public Task<string[]> GetFormatsAsync() |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public async Task<object> GetDataAsync(string format) |
|||
{ |
|||
return await Task.Run(() => _data); |
|||
} |
|||
} |
|||
|
|||
class HeadlessCursorFactoryStub : IStandardCursorFactory |
|||
{ |
|||
|
|||
public IPlatformHandle GetCursor(StandardCursorType cursorType) |
|||
{ |
|||
return new PlatformHandle(new IntPtr((int)cursorType), "STUB"); |
|||
} |
|||
} |
|||
|
|||
class HeadlessPlatformSettingsStub : IPlatformSettings |
|||
{ |
|||
public Size DoubleClickSize { get; } = new Size(2, 2); |
|||
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); |
|||
} |
|||
|
|||
class HeadlessSystemDialogsStub : ISystemDialogImpl |
|||
{ |
|||
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent) |
|||
{ |
|||
return Task.Run(() => (string[])null); |
|||
} |
|||
|
|||
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) |
|||
{ |
|||
return Task.Run(() => (string)null); |
|||
} |
|||
} |
|||
|
|||
class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl |
|||
{ |
|||
public short DesignEmHeight => 10; |
|||
|
|||
public int Ascent => 5; |
|||
|
|||
public int Descent => 5; |
|||
|
|||
public int LineGap => 2; |
|||
|
|||
public int UnderlinePosition => 5; |
|||
|
|||
public int UnderlineThickness => 5; |
|||
|
|||
public int StrikethroughPosition => 5; |
|||
|
|||
public int StrikethroughThickness => 2; |
|||
|
|||
public bool IsFixedPitch => true; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public ushort GetGlyph(uint codepoint) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
public int GetGlyphAdvance(ushort glyph) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
|||
{ |
|||
return glyphs.ToArray().Select(x => (int)x).ToArray(); |
|||
} |
|||
|
|||
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
|||
{ |
|||
return codepoints.ToArray().Select(x => (ushort)x).ToArray(); |
|||
} |
|||
} |
|||
|
|||
class HeadlessTextShaperStub : ITextShaperImpl |
|||
{ |
|||
public GlyphRun ShapeText(ReadOnlySlice<char> text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture) |
|||
{ |
|||
return new GlyphRun(new GlyphTypeface(typeface), 10, |
|||
new ReadOnlySlice<ushort>(new ushort[] { 1, 2, 3 }), |
|||
new ReadOnlySlice<double>(new double[] { 1, 2, 3 }), |
|||
new ReadOnlySlice<Vector>(new Vector[] { new Vector(1, 1), new Vector(2, 2), new Vector(3, 3) }), |
|||
text, |
|||
new ReadOnlySlice<ushort>(new ushort[] { 1, 2, 3 })); |
|||
} |
|||
} |
|||
|
|||
class HeadlessFontManagerStub : IFontManagerImpl |
|||
{ |
|||
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) |
|||
{ |
|||
return new HeadlessGlyphTypefaceImpl(); |
|||
} |
|||
|
|||
public string GetDefaultFontFamilyName() |
|||
{ |
|||
return "Arial"; |
|||
} |
|||
|
|||
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
|||
{ |
|||
return new List<string> { "Arial" }; |
|||
} |
|||
|
|||
public bool TryMatchCharacter(int codepoint, FontWeight fontWeight, FontStyle fontStyle, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) |
|||
{ |
|||
fontKey = new FontKey("Arial", fontWeight, fontStyle); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
class HeadlessIconLoaderStub : IPlatformIconLoader |
|||
{ |
|||
|
|||
class IconStub : IWindowIconImpl |
|||
{ |
|||
public void Save(Stream outputStream) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
public IWindowIconImpl LoadIcon(string fileName) |
|||
{ |
|||
return new IconStub(); |
|||
} |
|||
|
|||
public IWindowIconImpl LoadIcon(Stream stream) |
|||
{ |
|||
return new IconStub(); |
|||
} |
|||
|
|||
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) |
|||
{ |
|||
return new IconStub(); |
|||
} |
|||
} |
|||
|
|||
class HeadlessScreensStub : IScreenImpl |
|||
{ |
|||
public int ScreenCount { get; } = 1; |
|||
|
|||
public IReadOnlyList<Screen> AllScreens { get; } = new[] |
|||
{ |
|||
new Screen(1, new PixelRect(0, 0, 1920, 1280), |
|||
new PixelRect(0, 0, 1920, 1280), true), |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using System.Threading; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
class HeadlessPlatformThreadingInterface : IPlatformThreadingInterface |
|||
{ |
|||
public HeadlessPlatformThreadingInterface() |
|||
{ |
|||
_thread = Thread.CurrentThread; |
|||
} |
|||
|
|||
private AutoResetEvent _event = new AutoResetEvent(false); |
|||
private Thread _thread; |
|||
private object _lock = new object(); |
|||
private DispatcherPriority? _signaledPriority; |
|||
|
|||
public void RunLoop(CancellationToken cancellationToken) |
|||
{ |
|||
while (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
DispatcherPriority? signaled = null; |
|||
lock (_lock) |
|||
{ |
|||
signaled = _signaledPriority; |
|||
_signaledPriority = null; |
|||
} |
|||
if(signaled.HasValue) |
|||
Signaled?.Invoke(signaled); |
|||
WaitHandle.WaitAny(new[] {cancellationToken.WaitHandle, _event}, TimeSpan.FromMilliseconds(20)); |
|||
} |
|||
} |
|||
|
|||
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) |
|||
{ |
|||
var cancelled = false; |
|||
var enqueued = false; |
|||
var l = new object(); |
|||
var timer = new Timer(_ => |
|||
{ |
|||
lock (l) |
|||
{ |
|||
if (cancelled || enqueued) |
|||
return; |
|||
enqueued = true; |
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
lock (l) |
|||
{ |
|||
enqueued = false; |
|||
if (cancelled) |
|||
return; |
|||
tick(); |
|||
} |
|||
}, priority); |
|||
} |
|||
}, null, interval, interval); |
|||
return Disposable.Create(() => |
|||
{ |
|||
lock (l) |
|||
{ |
|||
timer.Dispose(); |
|||
cancelled = true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void Signal(DispatcherPriority priority) |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
if (_signaledPriority == null || _signaledPriority.Value > priority) |
|||
{ |
|||
_signaledPriority = priority; |
|||
} |
|||
_event.Set(); |
|||
} |
|||
} |
|||
|
|||
public bool CurrentThreadIsLoopThread => _thread == Thread.CurrentThread; |
|||
public event Action<DispatcherPriority?> Signaled; |
|||
} |
|||
} |
|||
@ -0,0 +1,340 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Controls.Primitives.PopupPositioning; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Threading; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow |
|||
{ |
|||
private IKeyboardDevice _keyboard; |
|||
private Stopwatch _st = Stopwatch.StartNew(); |
|||
private Pointer _mousePointer; |
|||
private WriteableBitmap _lastRenderedFrame; |
|||
private object _sync = new object(); |
|||
public bool IsPopup { get; } |
|||
|
|||
public HeadlessWindowImpl(bool isPopup) |
|||
{ |
|||
IsPopup = isPopup; |
|||
Surfaces = new object[] { this }; |
|||
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>(); |
|||
_mousePointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); |
|||
MouseDevice = new MouseDevice(_mousePointer); |
|||
ClientSize = new Size(1024, 768); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Closed?.Invoke(); |
|||
_lastRenderedFrame?.Dispose(); |
|||
_lastRenderedFrame = null; |
|||
} |
|||
|
|||
public Size ClientSize { get; set; } |
|||
public double Scaling { get; } = 1; |
|||
public IEnumerable<object> Surfaces { get; } |
|||
public Action<RawInputEventArgs> Input { get; set; } |
|||
public Action<Rect> Paint { get; set; } |
|||
public Action<Size> Resized { get; set; } |
|||
public Action<double> ScalingChanged { get; set; } |
|||
|
|||
public IRenderer CreateRenderer(IRenderRoot root) |
|||
=> new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()); |
|||
|
|||
public void Invalidate(Rect rect) |
|||
{ |
|||
} |
|||
|
|||
public void SetInputRoot(IInputRoot inputRoot) |
|||
{ |
|||
InputRoot = inputRoot; |
|||
} |
|||
|
|||
public IInputRoot InputRoot { get; set; } |
|||
|
|||
public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling); |
|||
|
|||
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling); |
|||
|
|||
public void SetCursor(IPlatformHandle cursor) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Action Closed { get; set; } |
|||
public IMouseDevice MouseDevice { get; } |
|||
|
|||
public void Show() |
|||
{ |
|||
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); |
|||
} |
|||
|
|||
public void Hide() |
|||
{ |
|||
Dispatcher.UIThread.Post(() => Deactivated?.Invoke(), DispatcherPriority.Input); |
|||
} |
|||
|
|||
public void BeginMoveDrag() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void BeginResizeDrag(WindowEdge edge) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public PixelPoint Position { get; set; } |
|||
public Action<PixelPoint> PositionChanged { get; set; } |
|||
public void Activate() |
|||
{ |
|||
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); |
|||
} |
|||
|
|||
public Action Deactivated { get; set; } |
|||
public Action Activated { get; set; } |
|||
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB"); |
|||
public Size MaxClientSize { get; } = new Size(1920, 1280); |
|||
public void Resize(Size clientSize) |
|||
{ |
|||
// Emulate X11 behavior here
|
|||
if (IsPopup) |
|||
DoResize(clientSize); |
|||
else |
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
DoResize(clientSize); |
|||
}); |
|||
} |
|||
|
|||
void DoResize(Size clientSize) |
|||
{ |
|||
// Uncomment this check and experience a weird bug in layout engine
|
|||
if (ClientSize != clientSize) |
|||
{ |
|||
ClientSize = clientSize; |
|||
Resized?.Invoke(clientSize); |
|||
} |
|||
} |
|||
|
|||
public void SetMinMaxSize(Size minSize, Size maxSize) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetTopmost(bool value) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IScreenImpl Screen { get; } = new HeadlessScreensStub(); |
|||
public WindowState WindowState { get; set; } |
|||
public Action<WindowState> WindowStateChanged { get; set; } |
|||
public void SetTitle(string title) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void ShowDialog(IWindowImpl parent) |
|||
{ |
|||
Show(); |
|||
} |
|||
|
|||
public void SetSystemDecorations(bool enabled) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetIcon(IWindowIconImpl icon) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void ShowTaskbarIcon(bool value) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void CanResize(bool value) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Func<bool> Closing { get; set; } |
|||
|
|||
class FramebufferProxy : ILockedFramebuffer |
|||
{ |
|||
private readonly ILockedFramebuffer _fb; |
|||
private readonly Action _onDispose; |
|||
private bool _disposed; |
|||
|
|||
public FramebufferProxy(ILockedFramebuffer fb, Action onDispose) |
|||
{ |
|||
_fb = fb; |
|||
_onDispose = onDispose; |
|||
} |
|||
public void Dispose() |
|||
{ |
|||
if (_disposed) |
|||
return; |
|||
_disposed = true; |
|||
_fb.Dispose(); |
|||
_onDispose(); |
|||
} |
|||
|
|||
public IntPtr Address => _fb.Address; |
|||
public PixelSize Size => _fb.Size; |
|||
public int RowBytes => _fb.RowBytes; |
|||
public Vector Dpi => _fb.Dpi; |
|||
public PixelFormat Format => _fb.Format; |
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling); |
|||
var fb = bmp.Lock(); |
|||
return new FramebufferProxy(fb, () => |
|||
{ |
|||
lock (_sync) |
|||
{ |
|||
_lastRenderedFrame?.Dispose(); |
|||
_lastRenderedFrame = bmp; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public IRef<IWriteableBitmapImpl> GetLastRenderedFrame() |
|||
{ |
|||
lock (_sync) |
|||
return _lastRenderedFrame?.PlatformImpl?.CloneAs<IWriteableBitmapImpl>(); |
|||
} |
|||
|
|||
private ulong Timestamp => (ulong)_st.ElapsedMilliseconds; |
|||
|
|||
// TODO: Hook recent Popup changes.
|
|||
IPopupPositioner IPopupImpl.PopupPositioner => null; |
|||
|
|||
public Size MaxAutoSizeHint => new Size(1920, 1080); |
|||
|
|||
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } |
|||
|
|||
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; |
|||
|
|||
public Action GotInputWhenDisabled { get; set; } |
|||
|
|||
public bool IsClientAreaExtendedToDecorations => false; |
|||
|
|||
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; } |
|||
|
|||
public bool NeedsManagedDecorations => false; |
|||
|
|||
public Thickness ExtendedMargins => new Thickness(); |
|||
|
|||
public Thickness OffScreenMargin => new Thickness(); |
|||
|
|||
public Action LostFocus { get; set; } |
|||
|
|||
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers) |
|||
{ |
|||
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyDown, key, modifiers)); |
|||
} |
|||
|
|||
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers) |
|||
{ |
|||
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyUp, key, modifiers)); |
|||
} |
|||
|
|||
void IHeadlessWindow.MouseDown(Point point, int button, RawInputModifiers modifiers) |
|||
{ |
|||
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot, |
|||
button == 0 ? RawPointerEventType.LeftButtonDown : |
|||
button == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.RightButtonDown, |
|||
point, modifiers)); |
|||
} |
|||
|
|||
void IHeadlessWindow.MouseMove(Point point, RawInputModifiers modifiers) |
|||
{ |
|||
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot, |
|||
RawPointerEventType.Move, point, modifiers)); |
|||
} |
|||
|
|||
void IHeadlessWindow.MouseUp(Point point, int button, RawInputModifiers modifiers) |
|||
{ |
|||
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot, |
|||
button == 0 ? RawPointerEventType.LeftButtonUp : |
|||
button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp, |
|||
point, modifiers)); |
|||
} |
|||
|
|||
void IWindowImpl.Move(PixelPoint point) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public IPopupImpl CreatePopup() |
|||
{ |
|||
// TODO: Hook recent Popup changes.
|
|||
return null; |
|||
} |
|||
|
|||
public void SetWindowManagerAddShadowHint(bool enabled) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetParent(IWindowImpl parent) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetEnabled(bool enable) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetSystemDecorations(SystemDecorations enabled) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void BeginMoveDrag(PointerPressedEventArgs e) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Avalonia.Input; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Headless |
|||
{ |
|||
public interface IHeadlessWindow |
|||
{ |
|||
IRef<IWriteableBitmapImpl> GetLastRenderedFrame(); |
|||
void KeyPress(Key key, RawInputModifiers modifiers); |
|||
void KeyRelease(Key key, RawInputModifiers modifiers); |
|||
void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None); |
|||
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None); |
|||
void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="CaptionButtons"> |
|||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> |
|||
<Setter Property="MaxHeight" Value="30" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Panel"> |
|||
<Setter Property="Width" Value="45" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="Panel:pointerover"> |
|||
<Setter Property="Background" Value="#7F7f7f7f" /> |
|||
</Style> |
|||
<Style Selector="Panel#PART_CloseButton:pointerover"> |
|||
<Setter Property="Background" Value="#7FFF0000" /> |
|||
</Style> |
|||
<Style Selector="Viewbox"> |
|||
<Setter Property="Width" Value="11" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Panel x:Name="PART_FullScreenButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_MinimiseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_RestoreButton"> |
|||
<Viewbox> |
|||
<Viewbox.RenderTransform> |
|||
<RotateTransform Angle="-90" /> |
|||
</Viewbox.RenderTransform> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_CloseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
</StackPanel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,53 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Design.PreviewWith> |
|||
<Border> |
|||
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
|||
</Border> |
|||
</Design.PreviewWith> |
|||
<Style Selector="TitleBar"> |
|||
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
<Setter Property="VerticalAlignment" Value="Top" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
|||
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
|||
<Panel x:Name="PART_Container"> |
|||
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
|||
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
|||
</Panel> |
|||
</Panel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen"> |
|||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
|||
</Transitions> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="none" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -1,9 +1,9 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseDark.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesDark.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.FluentTheme.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml" /> |
|||
</Styles> |
|||
|
|||
@ -1,9 +1,9 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseLight.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesLight.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Fluent.FluentTheme.xaml?assembly=Avalonia.Themes.Fluent" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml" /> |
|||
</Styles> |
|||
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue