committed by
GitHub
135 changed files with 4534 additions and 268 deletions
@ -1 +1 @@ |
|||||
Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5 |
Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 |
||||
@ -1,8 +1,18 @@ |
|||||
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
||||
|
xmlns:pages="clr-namespace:ControlCatalog.Pages" |
||||
Title="Avalonia Control Gallery" |
Title="Avalonia Control Gallery" |
||||
Icon="/Assets/test_icon.ico" |
Icon="/Assets/test_icon.ico" |
||||
xmlns:local="clr-namespace:ControlCatalog" |
xmlns:local="clr-namespace:ControlCatalog" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:vm="clr-namespace:ControlCatalog.ViewModels" |
||||
|
xmlns:v="clr-namespace:ControlCatalog.Views" |
||||
x:Class="ControlCatalog.MainWindow"> |
x:Class="ControlCatalog.MainWindow"> |
||||
<local:MainView/> |
<Window.DataTemplates> |
||||
|
<DataTemplate DataType="vm:NotificationViewModel"> |
||||
|
<v:CustomNotificationView /> |
||||
|
</DataTemplate> |
||||
|
</Window.DataTemplates> |
||||
|
<Panel> |
||||
|
<local:MainView/> |
||||
|
</Panel> |
||||
</Window> |
</Window> |
||||
|
|||||
@ -0,0 +1,10 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Pages.NotificationsPage"> |
||||
|
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left"> |
||||
|
<TextBlock Classes="h1">Notifications</TextBlock> |
||||
|
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" /> |
||||
|
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" /> |
||||
|
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotificationCommand}" /> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class NotificationsPage : UserControl |
||||
|
{ |
||||
|
public NotificationsPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System.Reactive; |
||||
|
using Avalonia.Controls.Notifications; |
||||
|
using Avalonia.Diagnostics.ViewModels; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
class MainWindowViewModel : ViewModelBase |
||||
|
{ |
||||
|
private IManagedNotificationManager _notificationManager; |
||||
|
|
||||
|
public MainWindowViewModel(IManagedNotificationManager notificationManager) |
||||
|
{ |
||||
|
_notificationManager = notificationManager; |
||||
|
|
||||
|
ShowCustomManagedNotificationCommand = ReactiveCommand.Create(() => |
||||
|
{ |
||||
|
NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" }); |
||||
|
}); |
||||
|
|
||||
|
ShowManagedNotificationCommand = ReactiveCommand.Create(() => |
||||
|
{ |
||||
|
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information)); |
||||
|
}); |
||||
|
|
||||
|
ShowNativeNotificationCommand = ReactiveCommand.Create(() => |
||||
|
{ |
||||
|
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public IManagedNotificationManager NotificationManager |
||||
|
{ |
||||
|
get { return _notificationManager; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _notificationManager, value); } |
||||
|
} |
||||
|
|
||||
|
public ReactiveCommand<Unit, Unit> ShowCustomManagedNotificationCommand { get; } |
||||
|
|
||||
|
public ReactiveCommand<Unit, Unit> ShowManagedNotificationCommand { get; } |
||||
|
|
||||
|
public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System.Reactive; |
||||
|
using Avalonia.Controls.Notifications; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class NotificationViewModel |
||||
|
{ |
||||
|
public NotificationViewModel(INotificationManager manager) |
||||
|
{ |
||||
|
YesCommand = ReactiveCommand.Create(() => |
||||
|
{ |
||||
|
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today.")); |
||||
|
}); |
||||
|
|
||||
|
NoCommand = ReactiveCommand.Create(() => |
||||
|
{ |
||||
|
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit...")); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public string Title { get; set; } |
||||
|
public string Message { get; set; } |
||||
|
|
||||
|
public ReactiveCommand<Unit, Unit> YesCommand { get; } |
||||
|
|
||||
|
public ReactiveCommand<Unit, Unit> NoCommand { get; } |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Views.CustomNotificationView"> |
||||
|
<Border Padding="12" MinHeight="20" Background="DodgerBlue"> |
||||
|
<Grid ColumnDefinitions="Auto,*"> |
||||
|
<Panel Margin="0,0,12,0" Width="25" Height="25" VerticalAlignment="Top"> |
||||
|
<TextBlock Text="" FontFamily="Segoe UI Symbol" FontSize="20" TextAlignment="Center" VerticalAlignment="Center"/> |
||||
|
</Panel> |
||||
|
<DockPanel Grid.Column="1"> |
||||
|
<TextBlock DockPanel.Dock="Top" Text="{Binding Title}" FontWeight="Medium" /> |
||||
|
<StackPanel Spacing="20" DockPanel.Dock="Bottom" Margin="0,8,0,0" Orientation="Horizontal"> |
||||
|
<Button Content="No" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding NoCommand}" Margin="0,0,8,0" /> |
||||
|
<Button Content="Yes" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding YesCommand}" /> |
||||
|
</StackPanel> |
||||
|
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" Opacity=".8" Margin="0,8,0,0"/> |
||||
|
</DockPanel> |
||||
|
</Grid> |
||||
|
</Border> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Views |
||||
|
{ |
||||
|
public class CustomNotificationView : UserControl |
||||
|
{ |
||||
|
public CustomNotificationView() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Metadata |
||||
|
{ |
||||
|
[AttributeUsage(AttributeTargets.Class)] |
||||
|
public class UsableDuringInitializationAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Threading; |
||||
|
using Microsoft.Build.Framework; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public class CompileAvaloniaXamlTask: ITask |
||||
|
{ |
||||
|
public bool Execute() |
||||
|
{ |
||||
|
OutputPath = OutputPath ?? AssemblyFile; |
||||
|
var outputPdb = GetPdbPath(OutputPath); |
||||
|
var input = AssemblyFile; |
||||
|
var inputPdb = GetPdbPath(input); |
||||
|
// Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
|
||||
|
if (OriginalCopyPath != null) |
||||
|
{ |
||||
|
File.Copy(AssemblyFile, OriginalCopyPath, true); |
||||
|
input = OriginalCopyPath; |
||||
|
File.Delete(AssemblyFile); |
||||
|
|
||||
|
if (File.Exists(inputPdb)) |
||||
|
{ |
||||
|
var copyPdb = GetPdbPath(OriginalCopyPath); |
||||
|
File.Copy(inputPdb, copyPdb, true); |
||||
|
File.Delete(inputPdb); |
||||
|
inputPdb = copyPdb; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, |
||||
|
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), |
||||
|
ProjectDirectory, OutputPath); |
||||
|
if (!res.Success) |
||||
|
return false; |
||||
|
if (!res.WrittenFile) |
||||
|
{ |
||||
|
File.Copy(input, OutputPath, true); |
||||
|
if(File.Exists(inputPdb)) |
||||
|
File.Copy(inputPdb, outputPdb, true); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
string GetPdbPath(string p) |
||||
|
{ |
||||
|
var d = Path.GetDirectoryName(p); |
||||
|
var f = Path.GetFileNameWithoutExtension(p); |
||||
|
var rv = f + ".pdb"; |
||||
|
if (d != null) |
||||
|
rv = Path.Combine(d, rv); |
||||
|
return rv; |
||||
|
} |
||||
|
|
||||
|
[Required] |
||||
|
public string AssemblyFile { get; set; } |
||||
|
[Required] |
||||
|
public string ReferencesFilePath { get; set; } |
||||
|
[Required] |
||||
|
public string OriginalCopyPath { get; set; } |
||||
|
[Required] |
||||
|
public string ProjectDirectory { get; set; } |
||||
|
|
||||
|
public string OutputPath { get; set; } |
||||
|
|
||||
|
public IBuildEngine BuildEngine { get; set; } |
||||
|
public ITaskHost HostObject { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.IO; |
||||
|
using Microsoft.Build.Framework; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public class Program |
||||
|
{ |
||||
|
static int Main(string[] args) |
||||
|
{ |
||||
|
if (args.Length != 3) |
||||
|
{ |
||||
|
Console.Error.WriteLine("input references output"); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
return new CompileAvaloniaXamlTask() |
||||
|
{ |
||||
|
AssemblyFile = args[0], |
||||
|
ReferencesFilePath = args[1], |
||||
|
OutputPath = args[2], |
||||
|
BuildEngine = new ConsoleBuildEngine(), |
||||
|
ProjectDirectory = Directory.GetCurrentDirectory() |
||||
|
}.Execute() ? |
||||
|
0 : |
||||
|
2; |
||||
|
} |
||||
|
|
||||
|
class ConsoleBuildEngine : IBuildEngine |
||||
|
{ |
||||
|
public void LogErrorEvent(BuildErrorEventArgs e) |
||||
|
{ |
||||
|
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); |
||||
|
} |
||||
|
|
||||
|
public void LogWarningEvent(BuildWarningEventArgs e) |
||||
|
{ |
||||
|
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); |
||||
|
} |
||||
|
|
||||
|
public void LogMessageEvent(BuildMessageEventArgs e) |
||||
|
{ |
||||
|
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); |
||||
|
} |
||||
|
|
||||
|
public void LogCustomEvent(CustomBuildEventArgs e) |
||||
|
{ |
||||
|
Console.WriteLine($"CUSTOM: {e.Message}"); |
||||
|
} |
||||
|
|
||||
|
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, |
||||
|
IDictionary targetOutputs) => throw new NotSupportedException(); |
||||
|
|
||||
|
public bool ContinueOnError { get; } |
||||
|
public int LineNumberOfTaskNode { get; } |
||||
|
public int ColumnNumberOfTaskNode { get; } |
||||
|
public string ProjectFileOfTaskNode { get; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
namespace System |
||||
|
{ |
||||
|
// This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory
|
||||
|
struct ReadOnlySpan<T> |
||||
|
{ |
||||
|
private string _s; |
||||
|
private int _start; |
||||
|
private int _length; |
||||
|
public int Length => _length; |
||||
|
|
||||
|
public ReadOnlySpan(string s) : this(s, 0, s.Length) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
public ReadOnlySpan(string s, int start, int len) |
||||
|
{ |
||||
|
_s = s; |
||||
|
_length = len; |
||||
|
_start = start; |
||||
|
if (_start > s.Length) |
||||
|
_length = 0; |
||||
|
else if (_start + _length > s.Length) |
||||
|
_length = s.Length - _start; |
||||
|
} |
||||
|
|
||||
|
public char this[int c] => _s[_start + c]; |
||||
|
|
||||
|
public bool IsEmpty => _length == 0; |
||||
|
|
||||
|
public ReadOnlySpan<char> Slice(int start, int len) |
||||
|
{ |
||||
|
return new ReadOnlySpan<char>(_s, _start + start, len); |
||||
|
} |
||||
|
|
||||
|
public static ReadOnlySpan<char> Empty => default; |
||||
|
|
||||
|
public ReadOnlySpan<char> Slice(int start) |
||||
|
{ |
||||
|
return new ReadOnlySpan<char>(_s, _start + start, _length - start); |
||||
|
} |
||||
|
|
||||
|
public bool SequenceEqual(ReadOnlySpan<char> other) |
||||
|
{ |
||||
|
if (_length != other.Length) |
||||
|
return false; |
||||
|
for(var c=0; c<_length;c++) |
||||
|
if (this[c] != other[c]) |
||||
|
return false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public ReadOnlySpan<char> TrimStart() |
||||
|
{ |
||||
|
int start = 0; |
||||
|
for (; start < Length; start++) |
||||
|
{ |
||||
|
if (!char.IsWhiteSpace(this[start])) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
return Slice(start); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); |
||||
|
} |
||||
|
|
||||
|
static class SpanCompatExtensions |
||||
|
{ |
||||
|
public static ReadOnlySpan<char> AsSpan(this string s) => new ReadOnlySpan<char>(s); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Utilities; |
||||
|
using Mono.Cecil; |
||||
|
using Mono.Cecil.Cil; |
||||
|
using XamlIl.TypeSystem; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public static partial class XamlCompilerTaskExecutor |
||||
|
{ |
||||
|
interface IResource : IFileSource |
||||
|
{ |
||||
|
string Uri { get; } |
||||
|
string Name { get; } |
||||
|
void Remove(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
interface IResourceGroup |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
IEnumerable<IResource> Resources { get; } |
||||
|
} |
||||
|
|
||||
|
class EmbeddedResources : IResourceGroup |
||||
|
{ |
||||
|
private readonly AssemblyDefinition _asm; |
||||
|
public string Name => "EmbeddedResource"; |
||||
|
|
||||
|
public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>() |
||||
|
.Select(r => new WrappedResource(_asm, r)).ToList(); |
||||
|
|
||||
|
public EmbeddedResources(AssemblyDefinition asm) |
||||
|
{ |
||||
|
_asm = asm; |
||||
|
} |
||||
|
class WrappedResource : IResource |
||||
|
{ |
||||
|
private readonly AssemblyDefinition _asm; |
||||
|
private readonly EmbeddedResource _res; |
||||
|
|
||||
|
public WrappedResource(AssemblyDefinition asm, EmbeddedResource res) |
||||
|
{ |
||||
|
_asm = asm; |
||||
|
_res = res; |
||||
|
} |
||||
|
|
||||
|
public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}"; |
||||
|
public string Name => _res.Name; |
||||
|
public string FilePath => Name; |
||||
|
public byte[] FileContents => _res.GetResourceData(); |
||||
|
|
||||
|
public void Remove() => _asm.MainModule.Resources.Remove(_res); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class AvaloniaResources : IResourceGroup |
||||
|
{ |
||||
|
private readonly AssemblyDefinition _asm; |
||||
|
Dictionary<string, AvaloniaResource> _resources = new Dictionary<string, AvaloniaResource>(); |
||||
|
private EmbeddedResource _embedded; |
||||
|
public AvaloniaResources(AssemblyDefinition asm, string projectDir) |
||||
|
{ |
||||
|
_asm = asm; |
||||
|
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r => |
||||
|
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources")); |
||||
|
if (_embedded == null) |
||||
|
return; |
||||
|
using (var stream = _embedded.GetResourceStream()) |
||||
|
{ |
||||
|
var br = new BinaryReader(stream); |
||||
|
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32()))); |
||||
|
var baseOffset = stream.Position; |
||||
|
foreach (var e in index) |
||||
|
{ |
||||
|
stream.Position = e.Offset + baseOffset; |
||||
|
_resources[e.Path] = new AvaloniaResource(this, projectDir, e.Path, br.ReadBytes(e.Size)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Save() |
||||
|
{ |
||||
|
if (_embedded != null) |
||||
|
{ |
||||
|
_asm.MainModule.Resources.Remove(_embedded); |
||||
|
_embedded = null; |
||||
|
} |
||||
|
|
||||
|
if (_resources.Count == 0) |
||||
|
return; |
||||
|
|
||||
|
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public, |
||||
|
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key, |
||||
|
x => x.Value.FileContents))); |
||||
|
_asm.MainModule.Resources.Add(_embedded); |
||||
|
} |
||||
|
|
||||
|
public string Name => "AvaloniaResources"; |
||||
|
public IEnumerable<IResource> Resources => _resources.Values.ToList(); |
||||
|
|
||||
|
class AvaloniaResource : IResource |
||||
|
{ |
||||
|
private readonly AvaloniaResources _grp; |
||||
|
private readonly byte[] _data; |
||||
|
|
||||
|
public AvaloniaResource(AvaloniaResources grp, |
||||
|
string projectDir, |
||||
|
string name, byte[] data) |
||||
|
{ |
||||
|
_grp = grp; |
||||
|
_data = data; |
||||
|
Name = name; |
||||
|
FilePath = Path.Combine(projectDir, name.TrimStart('/')); |
||||
|
Uri = $"avares://{grp._asm.Name.Name}/{name.TrimStart('/')}"; |
||||
|
} |
||||
|
public string Uri { get; } |
||||
|
public string Name { get; } |
||||
|
public string FilePath { get; } |
||||
|
public byte[] FileContents => _data; |
||||
|
|
||||
|
public void Remove() => _grp._resources.Remove(Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void CopyDebugDocument(MethodDefinition method, MethodDefinition copyFrom) |
||||
|
{ |
||||
|
if (!copyFrom.DebugInformation.HasSequencePoints) |
||||
|
return; |
||||
|
var dbg = method.DebugInformation; |
||||
|
|
||||
|
dbg.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.First()) |
||||
|
{ |
||||
|
End = new InstructionOffset(), |
||||
|
Import = new ImportDebugInformation() |
||||
|
}; |
||||
|
dbg.SequencePoints.Add(new SequencePoint(method.Body.Instructions.First(), |
||||
|
copyFrom.DebugInformation.SequencePoints.First().Document) |
||||
|
{ |
||||
|
StartLine = 0xfeefee, |
||||
|
EndLine = 0xfeefee |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,357 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Text; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
||||
|
using Microsoft.Build.Framework; |
||||
|
using Mono.Cecil; |
||||
|
using XamlIl.TypeSystem; |
||||
|
using Avalonia.Utilities; |
||||
|
using Mono.Cecil.Cil; |
||||
|
using Mono.Cecil.Rocks; |
||||
|
using XamlIl; |
||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Parsers; |
||||
|
using XamlIl.Transform; |
||||
|
using FieldAttributes = Mono.Cecil.FieldAttributes; |
||||
|
using MethodAttributes = Mono.Cecil.MethodAttributes; |
||||
|
using TypeAttributes = Mono.Cecil.TypeAttributes; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
|
||||
|
public static partial class XamlCompilerTaskExecutor |
||||
|
{ |
||||
|
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") |
||||
|
|| r.Name.ToLowerInvariant().EndsWith(".paml"); |
||||
|
|
||||
|
public class CompileResult |
||||
|
{ |
||||
|
public bool Success { get; set; } |
||||
|
public bool WrittenFile { get; } |
||||
|
|
||||
|
public CompileResult(bool success, bool writtenFile = false) |
||||
|
{ |
||||
|
Success = success; |
||||
|
WrittenFile = writtenFile; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, |
||||
|
string output) |
||||
|
{ |
||||
|
var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input); |
||||
|
var asm = typeSystem.TargetAssemblyDefinition; |
||||
|
var emres = new EmbeddedResources(asm); |
||||
|
var avares = new AvaloniaResources(asm, projectDirectory); |
||||
|
if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0) |
||||
|
// Nothing to do
|
||||
|
return new CompileResult(true); |
||||
|
|
||||
|
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem); |
||||
|
var compilerConfig = new XamlIlTransformerConfiguration(typeSystem, |
||||
|
typeSystem.TargetAssembly, |
||||
|
xamlLanguage, |
||||
|
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage), |
||||
|
AvaloniaXamlIlLanguage.CustomValueConverter); |
||||
|
|
||||
|
|
||||
|
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", |
||||
|
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); |
||||
|
asm.MainModule.Types.Add(contextDef); |
||||
|
|
||||
|
var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem, |
||||
|
xamlLanguage); |
||||
|
|
||||
|
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass); |
||||
|
|
||||
|
var editorBrowsableAttribute = typeSystem |
||||
|
.GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute")) |
||||
|
.Resolve(); |
||||
|
var editorBrowsableCtor = |
||||
|
asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors() |
||||
|
.First(c => c.Parameters.Count == 1)); |
||||
|
|
||||
|
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); |
||||
|
var rootServiceProviderField = asm.MainModule.ImportReference( |
||||
|
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Fields |
||||
|
.First(x => x.Name == "RootServiceProviderV1")); |
||||
|
|
||||
|
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader", |
||||
|
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); |
||||
|
|
||||
|
|
||||
|
loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) |
||||
|
{ |
||||
|
ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
var loaderDispatcherMethod = new MethodDefinition("TryLoad", |
||||
|
MethodAttributes.Static | MethodAttributes.Public, |
||||
|
asm.MainModule.TypeSystem.Object) |
||||
|
{ |
||||
|
Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)} |
||||
|
}; |
||||
|
loaderDispatcherDef.Methods.Add(loaderDispatcherMethod); |
||||
|
asm.MainModule.Types.Add(loaderDispatcherDef); |
||||
|
|
||||
|
var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First( |
||||
|
m => |
||||
|
m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 && |
||||
|
m.ReturnType.FullName == "System.Boolean" |
||||
|
&& m.Parameters[0].ParameterType.FullName == "System.String" |
||||
|
&& m.Parameters[1].ParameterType.FullName == "System.String")); |
||||
|
|
||||
|
bool CompileGroup(IResourceGroup group) |
||||
|
{ |
||||
|
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name, |
||||
|
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); |
||||
|
|
||||
|
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) |
||||
|
{ |
||||
|
ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)} |
||||
|
}); |
||||
|
asm.MainModule.Types.Add(typeDef); |
||||
|
var builder = typeSystem.CreateTypeBuilder(typeDef); |
||||
|
|
||||
|
foreach (var res in group.Resources.Where(CheckXamlName)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// StreamReader is needed here to handle BOM
|
||||
|
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd(); |
||||
|
var parsed = XDocumentXamlIlParser.Parse(xaml); |
||||
|
|
||||
|
var initialRoot = (XamlIlAstObjectNode)parsed.Root; |
||||
|
|
||||
|
|
||||
|
var precompileDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>() |
||||
|
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile"); |
||||
|
if (precompileDirective != null) |
||||
|
{ |
||||
|
var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim() |
||||
|
.ToLowerInvariant(); |
||||
|
if (precompileText == "false") |
||||
|
continue; |
||||
|
if (precompileText != "true") |
||||
|
throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective); |
||||
|
} |
||||
|
|
||||
|
var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>() |
||||
|
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); |
||||
|
IXamlIlType classType = null; |
||||
|
if (classDirective != null) |
||||
|
{ |
||||
|
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn)) |
||||
|
throw new XamlIlParseException("x:Class should have a string value", classDirective); |
||||
|
classType = typeSystem.TargetAssembly.FindType(tn.Text); |
||||
|
if (classType == null) |
||||
|
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective); |
||||
|
initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType, false); |
||||
|
initialRoot.Children.Remove(classDirective); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
compiler.Transform(parsed); |
||||
|
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate"; |
||||
|
var buildName = classType == null ? "Build:" + res.Name : null; |
||||
|
|
||||
|
var classTypeDefinition = |
||||
|
classType == null ? null : typeSystem.GetTypeReference(classType).Resolve(); |
||||
|
|
||||
|
|
||||
|
var populateBuilder = classTypeDefinition == null ? |
||||
|
builder : |
||||
|
typeSystem.CreateTypeBuilder(classTypeDefinition); |
||||
|
compiler.Compile(parsed, contextClass, |
||||
|
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, |
||||
|
classTypeDefinition == null), |
||||
|
buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true), |
||||
|
builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name, |
||||
|
true), |
||||
|
(closureName, closureBaseType) => |
||||
|
populateBuilder.DefineSubType(closureBaseType, closureName, false), |
||||
|
res.Uri, res |
||||
|
); |
||||
|
|
||||
|
|
||||
|
if (classTypeDefinition != null) |
||||
|
{ |
||||
|
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() |
||||
|
.Methods.First(m => m.Name == populateName); |
||||
|
|
||||
|
var designLoaderFieldType = typeSystem |
||||
|
.GetType("System.Action`1") |
||||
|
.MakeGenericType(typeSystem.GetType("System.Object")); |
||||
|
|
||||
|
var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType); |
||||
|
designLoaderFieldTypeReference.GenericArguments[0] = |
||||
|
asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]); |
||||
|
designLoaderFieldTypeReference = (GenericInstanceType) |
||||
|
asm.MainModule.ImportReference(designLoaderFieldTypeReference); |
||||
|
|
||||
|
var designLoaderLoad = |
||||
|
typeSystem.GetMethodReference( |
||||
|
designLoaderFieldType.Methods.First(m => m.Name == "Invoke")); |
||||
|
designLoaderLoad = |
||||
|
asm.MainModule.ImportReference(designLoaderLoad); |
||||
|
designLoaderLoad.DeclaringType = designLoaderFieldTypeReference; |
||||
|
|
||||
|
var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride", |
||||
|
FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference); |
||||
|
classTypeDefinition.Fields.Add(designLoaderField); |
||||
|
|
||||
|
const string TrampolineName = "!XamlIlPopulateTrampoline"; |
||||
|
var trampoline = new MethodDefinition(TrampolineName, |
||||
|
MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); |
||||
|
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); |
||||
|
classTypeDefinition.Methods.Add(trampoline); |
||||
|
|
||||
|
var regularStart = Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField); |
||||
|
|
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); |
||||
|
|
||||
|
trampoline.Body.Instructions.Add(regularStart); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); |
||||
|
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); |
||||
|
CopyDebugDocument(trampoline, compiledPopulateMethod); |
||||
|
|
||||
|
var foundXamlLoader = false; |
||||
|
// Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
|
||||
|
foreach (var method in classTypeDefinition.Methods |
||||
|
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) |
||||
|
{ |
||||
|
var i = method.Body.Instructions; |
||||
|
for (var c = 1; c < i.Count; c++) |
||||
|
{ |
||||
|
if (i[c - 1].OpCode == OpCodes.Ldarg_0 |
||||
|
&& i[c].OpCode == OpCodes.Call) |
||||
|
{ |
||||
|
var op = i[c].Operand as MethodReference; |
||||
|
|
||||
|
// TODO: Throw an error
|
||||
|
// This usually happens when same XAML resource was added twice for some weird reason
|
||||
|
// We currently support it for dual-named default theme resource
|
||||
|
if (op != null |
||||
|
&& op.Name == TrampolineName) |
||||
|
{ |
||||
|
foundXamlLoader = true; |
||||
|
break; |
||||
|
} |
||||
|
if (op != null |
||||
|
&& op.Name == "Load" |
||||
|
&& op.Parameters.Count == 1 |
||||
|
&& op.Parameters[0].ParameterType.FullName == "System.Object" |
||||
|
&& op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader") |
||||
|
{ |
||||
|
i[c].Operand = trampoline; |
||||
|
foundXamlLoader = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!foundXamlLoader) |
||||
|
{ |
||||
|
var ctors = classTypeDefinition.GetConstructors() |
||||
|
.Where(c => !c.IsStatic).ToList(); |
||||
|
// We can inject xaml loader into default constructor
|
||||
|
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3) |
||||
|
{ |
||||
|
var i = ctors[0].Body.Instructions; |
||||
|
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); |
||||
|
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); |
||||
|
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new InvalidProgramException( |
||||
|
$"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
if (buildName != null || classTypeDefinition != null) |
||||
|
{ |
||||
|
var compiledBuildMethod = buildName == null ? |
||||
|
null : |
||||
|
typeSystem.GetTypeReference(builder).Resolve() |
||||
|
.Methods.First(m => m.Name == buildName); |
||||
|
var parameterlessConstructor = compiledBuildMethod != null ? |
||||
|
null : |
||||
|
classTypeDefinition.GetConstructors().FirstOrDefault(c => |
||||
|
c.IsPublic && !c.IsStatic && !c.HasParameters); |
||||
|
|
||||
|
if (compiledBuildMethod != null || parameterlessConstructor != null) |
||||
|
{ |
||||
|
var i = loaderDispatcherMethod.Body.Instructions; |
||||
|
var nop = Instruction.Create(OpCodes.Nop); |
||||
|
i.Add(Instruction.Create(OpCodes.Ldarg_0)); |
||||
|
i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri)); |
||||
|
i.Add(Instruction.Create(OpCodes.Call, stringEquals)); |
||||
|
i.Add(Instruction.Create(OpCodes.Brfalse, nop)); |
||||
|
if (parameterlessConstructor != null) |
||||
|
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor)); |
||||
|
else |
||||
|
{ |
||||
|
i.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField)); |
||||
|
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod)); |
||||
|
} |
||||
|
|
||||
|
i.Add(Instruction.Create(OpCodes.Ret)); |
||||
|
i.Add(nop); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
int lineNumber = 0, linePosition = 0; |
||||
|
if (e is XamlIlParseException xe) |
||||
|
{ |
||||
|
lineNumber = xe.LineNumber; |
||||
|
linePosition = xe.LinePosition; |
||||
|
} |
||||
|
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath, |
||||
|
lineNumber, linePosition, lineNumber, linePosition, |
||||
|
e.Message, "", "Avalonia")); |
||||
|
return false; |
||||
|
} |
||||
|
res.Remove(); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (emres.Resources.Count(CheckXamlName) != 0) |
||||
|
if (!CompileGroup(emres)) |
||||
|
return new CompileResult(false); |
||||
|
if (avares.Resources.Count(CheckXamlName) != 0) |
||||
|
{ |
||||
|
if (!CompileGroup(avares)) |
||||
|
return new CompileResult(false); |
||||
|
avares.Save(); |
||||
|
} |
||||
|
|
||||
|
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull)); |
||||
|
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); |
||||
|
|
||||
|
asm.Write(output, new WriterParameters |
||||
|
{ |
||||
|
WriteSymbols = asm.MainModule.HasSymbols |
||||
|
}); |
||||
|
|
||||
|
return new CompileResult(true, true); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a notification manager that can show arbitrary content.
|
||||
|
/// Managed notification managers can show any content.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Because notification managers of this type are implemented purely in managed code, they
|
||||
|
/// can display arbitrary content, as opposed to notification managers which display notifications
|
||||
|
/// using the host operating system's notification mechanism.
|
||||
|
/// </remarks>
|
||||
|
public interface IManagedNotificationManager : INotificationManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Shows a notification.
|
||||
|
/// </summary>
|
||||
|
/// <param name="content">The content to be displayed.</param>
|
||||
|
void Show(object content); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a notification that can be shown in a window or by the host operating system.
|
||||
|
/// </summary>
|
||||
|
public interface INotification |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the Title of the notification.
|
||||
|
/// </summary>
|
||||
|
string Title { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the notification message.
|
||||
|
/// </summary>
|
||||
|
string Message { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="NotificationType"/> of the notification.
|
||||
|
/// </summary>
|
||||
|
NotificationType Type { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the expiration time of the notification after which it will automatically close.
|
||||
|
/// If the value is <see cref="TimeSpan.Zero"/> then the notification will remain open until the user closes it.
|
||||
|
/// </summary>
|
||||
|
TimeSpan Expiration { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an Action to be run when the notification is clicked.
|
||||
|
/// </summary>
|
||||
|
Action OnClick { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an Action to be run when the notification is closed.
|
||||
|
/// </summary>
|
||||
|
Action OnClose { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a notification manager that can be used to show notifications in a window or using
|
||||
|
/// the host operating system.
|
||||
|
/// </summary>
|
||||
|
public interface INotificationManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Show a notification.
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification">The notification to be displayed.</param>
|
||||
|
void Show(INotification notification); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A notification that can be shown in a window or by the host operating system.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This class represents a notification that can be displayed either in a window using
|
||||
|
/// <see cref="WindowNotificationManager"/> or by the host operating system (to be implemented).
|
||||
|
/// </remarks>
|
||||
|
public class Notification : INotification |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Notification"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="title">The title of the notification.</param>
|
||||
|
/// <param name="message">The message to be displayed in the notification.</param>
|
||||
|
/// <param name="type">The <see cref="NotificationType"/> of the notification.</param>
|
||||
|
/// <param name="expiration">The expiry time at which the notification will close.
|
||||
|
/// Use <see cref="TimeSpan.Zero"/> for notifications that will remain open.</param>
|
||||
|
/// <param name="onClick">An Action to call when the notification is clicked.</param>
|
||||
|
/// <param name="onClose">An Action to call when the notification is closed.</param>
|
||||
|
public Notification(string title, |
||||
|
string message, |
||||
|
NotificationType type = NotificationType.Information, |
||||
|
TimeSpan? expiration = null, |
||||
|
Action onClick = null, |
||||
|
Action onClose = null) |
||||
|
{ |
||||
|
Title = title; |
||||
|
Message = message; |
||||
|
Type = type; |
||||
|
Expiration = expiration.HasValue ? expiration.Value : TimeSpan.FromSeconds(5); |
||||
|
OnClick = onClick; |
||||
|
OnClose = onClose; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Title { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Message { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public NotificationType Type { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TimeSpan Expiration { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Action OnClick { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Action OnClose { get; private set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Linq; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.LogicalTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Control that represents and displays a notification.
|
||||
|
/// </summary>
|
||||
|
public class NotificationCard : ContentControl |
||||
|
{ |
||||
|
private bool _isClosed; |
||||
|
private bool _isClosing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="NotificationCard"/> class.
|
||||
|
/// </summary>
|
||||
|
public NotificationCard() |
||||
|
{ |
||||
|
this.GetObservable(IsClosedProperty) |
||||
|
.Subscribe(x => |
||||
|
{ |
||||
|
if (!IsClosing && !IsClosed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
RaiseEvent(new RoutedEventArgs(NotificationClosedEvent)); |
||||
|
}); |
||||
|
|
||||
|
this.GetObservable(ContentProperty) |
||||
|
.OfType<Notification>() |
||||
|
.Subscribe(x => |
||||
|
{ |
||||
|
switch (x.Type) |
||||
|
{ |
||||
|
case NotificationType.Error: |
||||
|
PseudoClasses.Add(":error"); |
||||
|
break; |
||||
|
|
||||
|
case NotificationType.Information: |
||||
|
PseudoClasses.Add(":information"); |
||||
|
break; |
||||
|
|
||||
|
case NotificationType.Success: |
||||
|
PseudoClasses.Add(":success"); |
||||
|
break; |
||||
|
|
||||
|
case NotificationType.Warning: |
||||
|
PseudoClasses.Add(":warning"); |
||||
|
break; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines if the notification is already closing.
|
||||
|
/// </summary>
|
||||
|
public bool IsClosing |
||||
|
{ |
||||
|
get { return _isClosing; } |
||||
|
private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="IsClosing"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<NotificationCard, bool> IsClosingProperty = |
||||
|
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosing), o => o.IsClosing); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines if the notification is closed.
|
||||
|
/// </summary>
|
||||
|
public bool IsClosed |
||||
|
{ |
||||
|
get { return _isClosed; } |
||||
|
set { SetAndRaise(IsClosedProperty, ref _isClosed, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="IsClosed"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<NotificationCard, bool> IsClosedProperty = |
||||
|
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="NotificationClosed"/> event.
|
||||
|
/// </summary>
|
||||
|
public static readonly RoutedEvent<RoutedEventArgs> NotificationClosedEvent = |
||||
|
RoutedEvent.Register<NotificationCard, RoutedEventArgs>(nameof(NotificationClosed), RoutingStrategies.Bubble); |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raised when the <see cref="NotificationCard"/> has closed.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<RoutedEventArgs> NotificationClosed |
||||
|
{ |
||||
|
add { AddHandler(NotificationClosedEvent, value); } |
||||
|
remove { RemoveHandler(NotificationClosedEvent, value); } |
||||
|
} |
||||
|
|
||||
|
public static bool GetCloseOnClick(Button obj) |
||||
|
{ |
||||
|
return (bool)obj.GetValue(CloseOnClickProperty); |
||||
|
} |
||||
|
|
||||
|
public static void SetCloseOnClick(Button obj, bool value) |
||||
|
{ |
||||
|
obj.SetValue(CloseOnClickProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the CloseOnClick property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty CloseOnClickProperty = |
||||
|
AvaloniaProperty.RegisterAttached<Button, bool>("CloseOnClick", typeof(NotificationCard), validate: CloseOnClickChanged); |
||||
|
|
||||
|
private static bool CloseOnClickChanged(Button button, bool value) |
||||
|
{ |
||||
|
if (value) |
||||
|
{ |
||||
|
button.Click += Button_Click; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
button.Click -= Button_Click; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when a button inside the Notification is clicked.
|
||||
|
/// </summary>
|
||||
|
private static void Button_Click(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
var btn = sender as ILogical; |
||||
|
var notification = btn.GetLogicalAncestors().OfType<NotificationCard>().FirstOrDefault(); |
||||
|
notification?.Close(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Closes the <see cref="NotificationCard"/>.
|
||||
|
/// </summary>
|
||||
|
public void Close() |
||||
|
{ |
||||
|
if (IsClosing) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
IsClosing = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Describes the possible positions for <see cref="Notification"/>s that are displayed by a
|
||||
|
/// <see cref="WindowNotificationManager"/>.
|
||||
|
/// </summary>
|
||||
|
public enum NotificationPosition |
||||
|
{ |
||||
|
TopLeft, |
||||
|
TopRight, |
||||
|
BottomLeft, |
||||
|
BottomRight |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration of types for <see cref="INotification"/>.
|
||||
|
/// </summary>
|
||||
|
public enum NotificationType |
||||
|
{ |
||||
|
Information, |
||||
|
Success, |
||||
|
Warning, |
||||
|
Error |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Layout; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements a <see cref="StackPanel"/> where the flow direction of its items can be reversed.
|
||||
|
/// </summary>
|
||||
|
public class ReversibleStackPanel : StackPanel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ReverseOrder"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<bool> ReverseOrderProperty = |
||||
|
AvaloniaProperty.Register<ReversibleStackPanel, bool>(nameof(ReverseOrder)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets if the child controls will be layed out in reverse order.
|
||||
|
/// </summary>
|
||||
|
public bool ReverseOrder |
||||
|
{ |
||||
|
get => GetValue(ReverseOrderProperty); |
||||
|
set => SetValue(ReverseOrderProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
var orientation = Orientation; |
||||
|
var spacing = Spacing; |
||||
|
var finalRect = new Rect(finalSize); |
||||
|
var pos = 0.0; |
||||
|
|
||||
|
var children = ReverseOrder ? Children.Reverse() : Children; |
||||
|
|
||||
|
foreach (Control child in children) |
||||
|
{ |
||||
|
double childWidth = child.DesiredSize.Width; |
||||
|
double childHeight = child.DesiredSize.Height; |
||||
|
|
||||
|
if (orientation == Orientation.Vertical) |
||||
|
{ |
||||
|
var rect = new Rect(0, pos, childWidth, childHeight) |
||||
|
.Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top); |
||||
|
ArrangeChild(child, rect, finalSize, orientation); |
||||
|
pos += childHeight + spacing; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var rect = new Rect(pos, 0, childWidth, childHeight) |
||||
|
.Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment); |
||||
|
ArrangeChild(child, rect, finalSize, orientation); |
||||
|
pos += childWidth + spacing; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return finalSize; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,163 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
|
||||
|
/// </summary>
|
||||
|
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager |
||||
|
{ |
||||
|
private IList _items; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Position"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<NotificationPosition> PositionProperty = |
||||
|
AvaloniaProperty.Register<WindowNotificationManager, NotificationPosition>(nameof(Position), NotificationPosition.TopRight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines which corner of the screen notifications can be displayed in.
|
||||
|
/// </summary>
|
||||
|
/// <seealso cref="NotificationPosition"/>
|
||||
|
public NotificationPosition Position |
||||
|
{ |
||||
|
get { return GetValue(PositionProperty); } |
||||
|
set { SetValue(PositionProperty, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxItems"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MaxItemsProperty = |
||||
|
AvaloniaProperty.Register<WindowNotificationManager, int>(nameof(MaxItems), 5); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the maximum number of notifications visible at once.
|
||||
|
/// </summary>
|
||||
|
public int MaxItems |
||||
|
{ |
||||
|
get { return GetValue(MaxItemsProperty); } |
||||
|
set { SetValue(MaxItemsProperty, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="WindowNotificationManager"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="host">The window that will host the control.</param>
|
||||
|
public WindowNotificationManager(Window host) |
||||
|
{ |
||||
|
if (VisualChildren.Count != 0) |
||||
|
{ |
||||
|
Install(host); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Observable.FromEventPattern<TemplateAppliedEventArgs>(host, nameof(host.TemplateApplied)).Take(1) |
||||
|
.Subscribe(_ => |
||||
|
{ |
||||
|
Install(host); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static WindowNotificationManager() |
||||
|
{ |
||||
|
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopLeft, ":topleft"); |
||||
|
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopRight, ":topright"); |
||||
|
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomLeft, ":bottomleft"); |
||||
|
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomRight, ":bottomright"); |
||||
|
|
||||
|
HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch); |
||||
|
VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnTemplateApplied(e); |
||||
|
|
||||
|
var itemsControl = e.NameScope.Find<Panel>("PART_Items"); |
||||
|
_items = itemsControl?.Children; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Show(INotification content) |
||||
|
{ |
||||
|
Show(content as object); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async void Show(object content) |
||||
|
{ |
||||
|
var notification = content as INotification; |
||||
|
|
||||
|
var notificationControl = new NotificationCard |
||||
|
{ |
||||
|
Content = content |
||||
|
}; |
||||
|
|
||||
|
if (notification != null) |
||||
|
{ |
||||
|
notificationControl.NotificationClosed += (sender, args) => |
||||
|
{ |
||||
|
notification.OnClose?.Invoke(); |
||||
|
|
||||
|
_items.Remove(sender); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
notificationControl.PointerPressed += (sender, args) => |
||||
|
{ |
||||
|
if (notification != null && notification.OnClick != null) |
||||
|
{ |
||||
|
notification.OnClick.Invoke(); |
||||
|
} |
||||
|
|
||||
|
(sender as NotificationCard)?.Close(); |
||||
|
}; |
||||
|
|
||||
|
_items.Add(notificationControl); |
||||
|
|
||||
|
if (_items.OfType<NotificationCard>().Count(i => !i.IsClosing) > MaxItems) |
||||
|
{ |
||||
|
_items.OfType<NotificationCard>().First(i => !i.IsClosing).Close(); |
||||
|
} |
||||
|
|
||||
|
if (notification != null && notification.Expiration == TimeSpan.Zero) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await Task.Delay(notification?.Expiration ?? TimeSpan.FromSeconds(5)); |
||||
|
|
||||
|
notificationControl.Close(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
|
||||
|
/// of the host <see cref="Window"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="host">The <see cref="Window"/> that will be the host.</param>
|
||||
|
private void Install(Window host) |
||||
|
{ |
||||
|
var adornerLayer = host.GetVisualDescendants() |
||||
|
.OfType<AdornerDecorator>() |
||||
|
.FirstOrDefault() |
||||
|
?.AdornerLayer; |
||||
|
|
||||
|
if (adornerLayer != null) |
||||
|
{ |
||||
|
adornerLayer.Children.Add(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Style Selector="NotificationCard"> |
||||
|
<Setter Property="UseLayoutRounding" Value="True"/> |
||||
|
<Setter Property="Width" Value="350"/> |
||||
|
<Setter Property="FontSize" Value="14"/> |
||||
|
<Setter Property="Foreground" Value="White"/> |
||||
|
<Setter Property="RenderTransformOrigin" Value="50%,75%"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True"> |
||||
|
<Border Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
Margin="8,8,0,0"> |
||||
|
<ContentControl MinHeight="150" Content="{TemplateBinding Content}" /> |
||||
|
</Border> |
||||
|
</LayoutTransformControl> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
|
||||
|
<Style.Animations> |
||||
|
<Animation Duration="0:0:0.45" Easing="QuadraticEaseIn" FillMode="Forward"> |
||||
|
<KeyFrame Cue="0%"> |
||||
|
<Setter Property="Opacity" Value="0"/> |
||||
|
<Setter Property="TranslateTransform.Y" Value="20"/> |
||||
|
<Setter Property="ScaleTransform.ScaleX" Value="0.85"/> |
||||
|
<Setter Property="ScaleTransform.ScaleY" Value="0.85"/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue="30%"> |
||||
|
<Setter Property="TranslateTransform.Y" Value="-20"/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue="100%"> |
||||
|
<Setter Property="Opacity" Value="1"/> |
||||
|
<Setter Property="TranslateTransform.Y" Value="0"/> |
||||
|
<Setter Property="ScaleTransform.ScaleX" Value="1"/> |
||||
|
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
||||
|
</KeyFrame> |
||||
|
</Animation> |
||||
|
</Style.Animations> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl"> |
||||
|
<Setter Property="RenderTransformOrigin" Value="50%,0%"/> |
||||
|
<Style.Animations> |
||||
|
<Animation Duration="0:0:0.75" Easing="QuadraticEaseOut" FillMode="Forward"> |
||||
|
<KeyFrame Cue="0%"> |
||||
|
<Setter Property="TranslateTransform.X" Value="0"/> |
||||
|
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue="70%"> |
||||
|
<Setter Property="TranslateTransform.X" Value="800"/> |
||||
|
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue="100%"> |
||||
|
<Setter Property="ScaleTransform.ScaleY" Value="0"/> |
||||
|
<Setter Property="TranslateTransform.X" Value="800"/> |
||||
|
</KeyFrame> |
||||
|
</Animation> |
||||
|
</Style.Animations> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="NotificationCard[IsClosing=true]"> |
||||
|
<Style.Animations> |
||||
|
<Animation Duration="0:0:1.25" Easing="QuadraticEaseOut" FillMode="Forward"> |
||||
|
<KeyFrame Cue="100%"> |
||||
|
<Setter Property="IsClosed" Value="True"/> |
||||
|
</KeyFrame> |
||||
|
</Animation> |
||||
|
</Style.Animations> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="NotificationCard"> |
||||
|
<Setter Property="Background" Value="{DynamicResource NotificationCardBackgroundBrush}"/> |
||||
|
</Style> |
||||
|
<Style Selector="NotificationCard:information"> |
||||
|
<Setter Property="Background" Value="{DynamicResource NotificationCardInformationBackgroundBrush}"/> |
||||
|
</Style> |
||||
|
<Style Selector="NotificationCard:success"> |
||||
|
<Setter Property="Background" Value="{DynamicResource NotificationCardSuccessBackgroundBrush}"/> |
||||
|
</Style> |
||||
|
<Style Selector="NotificationCard:warning"> |
||||
|
<Setter Property="Background" Value="{DynamicResource NotificationCardWarningBackgroundBrush}"/> |
||||
|
</Style> |
||||
|
<Style Selector="NotificationCard:error"> |
||||
|
<Setter Property="Background" Value="{DynamicResource NotificationCardErrorBackgroundBrush}"/> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -0,0 +1,45 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Style Selector="WindowNotificationManager"> |
||||
|
<Setter Property="Margin" Value="0 0 8 8"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<ReversibleStackPanel Name="PART_Items"> |
||||
|
<ReversibleStackPanel.DataTemplates> |
||||
|
<DataTemplate DataType="INotification"> |
||||
|
<StackPanel Spacing="8" Margin="12"> |
||||
|
<TextBlock Text="{Binding Title}" FontWeight="Medium" /> |
||||
|
<TextBlock MaxHeight="80" Text="{Binding Message}" TextWrapping="Wrap" Margin="0,0,12,0"/> |
||||
|
</StackPanel> |
||||
|
</DataTemplate> |
||||
|
<DataTemplate DataType="x:String"> |
||||
|
<TextBlock Text="{Binding }" Margin="12" /> |
||||
|
</DataTemplate> |
||||
|
</ReversibleStackPanel.DataTemplates> |
||||
|
</ReversibleStackPanel> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="WindowNotificationManager:topleft /template/ ReversibleStackPanel#PART_Items"> |
||||
|
<Setter Property="VerticalAlignment" Value="Top"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Left"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="WindowNotificationManager:topright /template/ ReversibleStackPanel#PART_Items"> |
||||
|
<Setter Property="VerticalAlignment" Value="Top"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Right"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="WindowNotificationManager:bottomleft /template/ ReversibleStackPanel#PART_Items"> |
||||
|
<Setter Property="ReverseOrder" Value="True"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Left"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="WindowNotificationManager:bottomright /template/ ReversibleStackPanel#PART_Items"> |
||||
|
<Setter Property="ReverseOrder" Value="True"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Right"/> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.Runtime; |
||||
|
using Portable.Xaml; |
||||
|
using Portable.Xaml.Markup; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml |
||||
|
{ |
||||
|
internal static class Extensions |
||||
|
{ |
||||
|
public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T)); |
||||
|
|
||||
|
|
||||
|
public static Uri GetContextBaseUri(this IServiceProvider ctx) |
||||
|
{ |
||||
|
var properService = ctx.GetService<IUriContext>(); |
||||
|
if (properService != null) |
||||
|
return properService.BaseUri; |
||||
|
// Ugly hack with casts
|
||||
|
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetBaseUri((ITypeDescriptorContext)ctx); |
||||
|
} |
||||
|
|
||||
|
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class |
||||
|
{ |
||||
|
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>(); |
||||
|
if (parentStack != null) |
||||
|
return parentStack.Parents.OfType<T>().FirstOrDefault(); |
||||
|
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetFirstAmbientValue<T>((ITypeDescriptorContext)ctx); |
||||
|
} |
||||
|
|
||||
|
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class |
||||
|
{ |
||||
|
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>(); |
||||
|
if (parentStack != null) |
||||
|
return parentStack.Parents.OfType<T>().LastOrDefault(); |
||||
|
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetLastOrDefaultAmbientValue<T>( |
||||
|
(ITypeDescriptorContext)ctx); |
||||
|
} |
||||
|
|
||||
|
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp) |
||||
|
{ |
||||
|
var stack = sp.GetService<IAvaloniaXamlIlParentStackProvider>(); |
||||
|
if (stack != null) |
||||
|
return stack.Parents.OfType<T>(); |
||||
|
|
||||
|
var context = (ITypeDescriptorContext)sp; |
||||
|
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext; |
||||
|
var ambientProvider = context.GetService<IAmbientProvider>(); |
||||
|
return ambientProvider.GetAllAmbientValues(schemaContext.GetXamlType(typeof(T))).OfType<T>(); |
||||
|
} |
||||
|
|
||||
|
public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type) |
||||
|
{ |
||||
|
var tr = ctx.GetService<IXamlTypeResolver>(); |
||||
|
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}"; |
||||
|
return tr?.Resolve(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 7452b23169e4948907fa10e2c115b672897d0e04 |
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975 |
||||
@ -0,0 +1,276 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Expressions; |
||||
|
using System.Reflection; |
||||
|
using System.Reflection.Emit; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.Runtime; |
||||
|
using Avalonia.Platform; |
||||
|
using XamlIl.Transform; |
||||
|
using XamlIl.TypeSystem; |
||||
|
#if RUNTIME_XAML_CECIL
|
||||
|
using TypeAttributes = Mono.Cecil.TypeAttributes; |
||||
|
using Mono.Cecil; |
||||
|
using XamlIl.Ast; |
||||
|
#endif
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl |
||||
|
{ |
||||
|
static class AvaloniaXamlIlRuntimeCompiler |
||||
|
{ |
||||
|
#if !RUNTIME_XAML_CECIL
|
||||
|
private static SreTypeSystem _sreTypeSystem; |
||||
|
private static ModuleBuilder _sreBuilder; |
||||
|
private static IXamlIlType _sreContextType; |
||||
|
private static XamlIlLanguageTypeMappings _sreMappings; |
||||
|
private static XamlIlXmlnsMappings _sreXmlns; |
||||
|
private static AssemblyBuilder _sreAsm; |
||||
|
private static bool _sreCanSave; |
||||
|
|
||||
|
public static void DumpRuntimeCompilationResults() |
||||
|
{ |
||||
|
if (_sreBuilder == null) |
||||
|
return; |
||||
|
var saveMethod = _sreAsm.GetType().GetMethods() |
||||
|
.FirstOrDefault(m => m.Name == "Save" && m.GetParameters().Length == 1); |
||||
|
if (saveMethod == null) |
||||
|
return; |
||||
|
try |
||||
|
{ |
||||
|
_sreBuilder.CreateGlobalFunctions(); |
||||
|
saveMethod.Invoke(_sreAsm, new Object[] {"XamlIlLoader.ildump"}); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void InitializeSre() |
||||
|
{ |
||||
|
if (_sreTypeSystem == null) |
||||
|
_sreTypeSystem = new SreTypeSystem(); |
||||
|
if (_sreBuilder == null) |
||||
|
{ |
||||
|
_sreCanSave = !(RuntimeInformation.FrameworkDescription.StartsWith(".NET Core")); |
||||
|
var name = new AssemblyName(Guid.NewGuid().ToString("N")); |
||||
|
if (_sreCanSave) |
||||
|
{ |
||||
|
var define = AppDomain.CurrentDomain.GetType().GetMethods() |
||||
|
.FirstOrDefault(m => m.Name == "DefineDynamicAssembly" |
||||
|
&& m.GetParameters().Length == 3 && |
||||
|
m.GetParameters()[2].ParameterType == typeof(string)); |
||||
|
if (define != null) |
||||
|
_sreAsm = (AssemblyBuilder)define.Invoke(AppDomain.CurrentDomain, new object[] |
||||
|
{ |
||||
|
name, (AssemblyBuilderAccess)3, |
||||
|
Path.GetDirectoryName(typeof(AvaloniaXamlIlRuntimeCompiler).Assembly.GetModules()[0] |
||||
|
.FullyQualifiedName) |
||||
|
}); |
||||
|
else |
||||
|
_sreCanSave = false; |
||||
|
} |
||||
|
|
||||
|
if(_sreAsm == null) |
||||
|
_sreAsm = AssemblyBuilder.DefineDynamicAssembly(name, |
||||
|
AssemblyBuilderAccess.RunAndCollect); |
||||
|
|
||||
|
_sreBuilder = _sreAsm.DefineDynamicModule("XamlIlLoader.ildump"); |
||||
|
} |
||||
|
|
||||
|
if (_sreMappings == null) |
||||
|
_sreMappings = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem); |
||||
|
if (_sreXmlns == null) |
||||
|
_sreXmlns = XamlIlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings); |
||||
|
if (_sreContextType == null) |
||||
|
_sreContextType = XamlIlContextDefinition.GenerateContextClass( |
||||
|
_sreTypeSystem.CreateTypeBuilder( |
||||
|
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode) |
||||
|
{ |
||||
|
var success = false; |
||||
|
try |
||||
|
{ |
||||
|
var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode); |
||||
|
success = true; |
||||
|
return rv; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if(!success && _sreCanSave) |
||||
|
DumpRuntimeCompilationResults(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode) |
||||
|
{ |
||||
|
|
||||
|
InitializeSre(); |
||||
|
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly); |
||||
|
|
||||
|
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm, |
||||
|
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter), |
||||
|
_sreContextType); |
||||
|
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); |
||||
|
|
||||
|
IXamlIlType overrideType = null; |
||||
|
if (rootInstance != null) |
||||
|
{ |
||||
|
overrideType = _sreTypeSystem.GetType(rootInstance.GetType()); |
||||
|
} |
||||
|
|
||||
|
compiler.IsDesignMode = isDesignMode; |
||||
|
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); |
||||
|
var created = tb.CreateTypeInfo(); |
||||
|
|
||||
|
return LoadOrPopulate(created, rootInstance); |
||||
|
} |
||||
|
#endif
|
||||
|
|
||||
|
static object LoadOrPopulate(Type created, object rootInstance) |
||||
|
{ |
||||
|
var isp = Expression.Parameter(typeof(IServiceProvider)); |
||||
|
|
||||
|
|
||||
|
var epar = Expression.Parameter(typeof(object)); |
||||
|
var populate = created.GetMethod(AvaloniaXamlIlCompiler.PopulateName); |
||||
|
isp = Expression.Parameter(typeof(IServiceProvider)); |
||||
|
var populateCb = Expression.Lambda<Action<IServiceProvider, object>>( |
||||
|
Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)), |
||||
|
isp, epar).Compile(); |
||||
|
|
||||
|
if (rootInstance == null) |
||||
|
{ |
||||
|
var targetType = populate.GetParameters()[1].ParameterType; |
||||
|
var overrideField = targetType.GetField("!XamlIlPopulateOverride", |
||||
|
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); |
||||
|
|
||||
|
if (overrideField != null) |
||||
|
{ |
||||
|
overrideField.SetValue(null, |
||||
|
new Action<object>( |
||||
|
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); })); |
||||
|
try |
||||
|
{ |
||||
|
return Activator.CreateInstance(targetType); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
overrideField.SetValue(null, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var createCb = Expression.Lambda<Func<IServiceProvider, object>>( |
||||
|
Expression.Convert(Expression.Call( |
||||
|
created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile(); |
||||
|
return createCb(XamlIlRuntimeHelpers.RootServiceProviderV1); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance); |
||||
|
return rootInstance; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri, |
||||
|
bool isDesignMode) |
||||
|
{ |
||||
|
string xaml; |
||||
|
using (var sr = new StreamReader(stream)) |
||||
|
xaml = sr.ReadToEnd(); |
||||
|
#if RUNTIME_XAML_CECIL
|
||||
|
return LoadCecil(xaml, localAssembly, rootInstance, uri); |
||||
|
#else
|
||||
|
return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
#if RUNTIME_XAML_CECIL
|
||||
|
private static Dictionary<string, (Action<IServiceProvider, object> populate, Func<IServiceProvider, object> |
||||
|
build)> |
||||
|
s_CecilCache = |
||||
|
new Dictionary<string, (Action<IServiceProvider, object> populate, Func<IServiceProvider, object> build) |
||||
|
>(); |
||||
|
|
||||
|
|
||||
|
private static string _cecilEmitDir; |
||||
|
private static CecilTypeSystem _cecilTypeSystem; |
||||
|
private static XamlIlLanguageTypeMappings _cecilMappings; |
||||
|
private static XamlIlXmlnsMappings _cecilXmlns; |
||||
|
private static bool _cecilInitialized; |
||||
|
|
||||
|
static void InitializeCecil() |
||||
|
{ |
||||
|
if(_cecilInitialized) |
||||
|
return; |
||||
|
var path = Assembly.GetEntryAssembly().GetModules()[0].FullyQualifiedName; |
||||
|
_cecilEmitDir = Path.Combine(Path.GetDirectoryName(path), "emit"); |
||||
|
Directory.CreateDirectory(_cecilEmitDir); |
||||
|
var refs = new[] {path}.Concat(File.ReadAllLines(path + ".refs")); |
||||
|
_cecilTypeSystem = new CecilTypeSystem(refs); |
||||
|
_cecilMappings = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem); |
||||
|
_cecilXmlns = XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings); |
||||
|
_cecilInitialized = true; |
||||
|
} |
||||
|
|
||||
|
private static Dictionary<string, Type> _cecilGeneratedCache = new Dictionary<string, Type>(); |
||||
|
static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri) |
||||
|
{ |
||||
|
if (uri == null) |
||||
|
throw new InvalidOperationException("Please, go away"); |
||||
|
InitializeCecil(); |
||||
|
IXamlIlType overrideType = null; |
||||
|
if (rootInstance != null) |
||||
|
{ |
||||
|
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
var safeUri = uri.ToString() |
||||
|
.Replace(":", "_") |
||||
|
.Replace("/", "_") |
||||
|
.Replace("?", "_") |
||||
|
.Replace("=", "_") |
||||
|
.Replace(".", "_"); |
||||
|
if (_cecilGeneratedCache.TryGetValue(safeUri, out var cached)) |
||||
|
return LoadOrPopulate(cached, rootInstance); |
||||
|
|
||||
|
|
||||
|
var asm = _cecilTypeSystem.CreateAndRegisterAssembly(safeUri, new Version(1, 0), |
||||
|
ModuleKind.Dll); |
||||
|
var def = new TypeDefinition("XamlIlLoader", safeUri, |
||||
|
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); |
||||
|
|
||||
|
var contextDef = new TypeDefinition("XamlIlLoader", safeUri + "_XamlIlContext", |
||||
|
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); |
||||
|
|
||||
|
asm.MainModule.Types.Add(def); |
||||
|
asm.MainModule.Types.Add(contextDef); |
||||
|
|
||||
|
var tb = _cecilTypeSystem.CreateTypeBuilder(def); |
||||
|
|
||||
|
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_cecilTypeSystem, |
||||
|
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name), |
||||
|
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings), |
||||
|
AvaloniaXamlIlLanguage.CustomValueConverter), |
||||
|
_cecilTypeSystem.CreateTypeBuilder(contextDef)); |
||||
|
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType); |
||||
|
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll"); |
||||
|
using(var f = File.Create(asmPath)) |
||||
|
asm.Write(f); |
||||
|
var loaded = Assembly.LoadFile(asmPath) |
||||
|
.GetTypes().First(x => x.Name == safeUri); |
||||
|
_cecilGeneratedCache[safeUri] = loaded; |
||||
|
return LoadOrPopulate(loaded, rootInstance); |
||||
|
} |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,124 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
||||
|
using XamlIl; |
||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Parsers; |
||||
|
using XamlIl.Transform; |
||||
|
using XamlIl.Transform.Transformers; |
||||
|
using XamlIl.TypeSystem; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions |
||||
|
{ |
||||
|
class AvaloniaXamlIlCompiler : XamlIlCompiler |
||||
|
{ |
||||
|
private readonly XamlIlTransformerConfiguration _configuration; |
||||
|
private readonly IXamlIlType _contextType; |
||||
|
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer; |
||||
|
|
||||
|
private AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration) : base(configuration, true) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
|
||||
|
void InsertAfter<T>(params IXamlIlAstTransformer[] t) |
||||
|
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t); |
||||
|
|
||||
|
void InsertBefore<T>(params IXamlIlAstTransformer[] t) |
||||
|
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t); |
||||
|
|
||||
|
|
||||
|
// Before everything else
|
||||
|
|
||||
|
Transformers.Insert(0, new XNameTransformer()); |
||||
|
Transformers.Insert(1, new IgnoredDirectivesTransformer()); |
||||
|
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer()); |
||||
|
Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer()); |
||||
|
|
||||
|
|
||||
|
// Targeted
|
||||
|
|
||||
|
InsertBefore<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlTransformInstanceAttachedProperties()); |
||||
|
InsertAfter<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver()); |
||||
|
|
||||
|
|
||||
|
|
||||
|
InsertBefore<XamlIlContentConvertTransformer>( |
||||
|
new AvaloniaXamlIlSelectorTransformer(), |
||||
|
new AvaloniaXamlIlSetterTransformer(), |
||||
|
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), |
||||
|
new AvaloniaXamlIlConstructorServiceProviderTransformer(), |
||||
|
new AvaloniaXamlIlTransitionsTypeMetadataTransformer() |
||||
|
); |
||||
|
|
||||
|
// After everything else
|
||||
|
|
||||
|
Transformers.Add(new AddNameScopeRegistration()); |
||||
|
Transformers.Add(new AvaloniaXamlIlMetadataRemover()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration, |
||||
|
IXamlIlTypeBuilder contextTypeBuilder) : this(configuration) |
||||
|
{ |
||||
|
_contextType = CreateContextType(contextTypeBuilder); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration, |
||||
|
IXamlIlType contextType) : this(configuration) |
||||
|
{ |
||||
|
_contextType = contextType; |
||||
|
} |
||||
|
|
||||
|
public const string PopulateName = "__AvaloniaXamlIlPopulate"; |
||||
|
public const string BuildName = "__AvaloniaXamlIlBuild"; |
||||
|
|
||||
|
public bool IsDesignMode |
||||
|
{ |
||||
|
get => _designTransformer.IsDesignMode; |
||||
|
set => _designTransformer.IsDesignMode = value; |
||||
|
} |
||||
|
|
||||
|
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType) |
||||
|
{ |
||||
|
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string> |
||||
|
{ |
||||
|
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} |
||||
|
}); |
||||
|
|
||||
|
var rootObject = (XamlIlAstObjectNode)parsed.Root; |
||||
|
|
||||
|
var classDirective = rootObject.Children |
||||
|
.OfType<XamlIlAstXmlDirective>().FirstOrDefault(x => |
||||
|
x.Namespace == XamlNamespaces.Xaml2006 |
||||
|
&& x.Name == "Class"); |
||||
|
|
||||
|
var rootType = |
||||
|
classDirective != null ? |
||||
|
new XamlIlAstClrTypeReference(classDirective, |
||||
|
_configuration.TypeSystem.GetType(((XamlIlAstTextNode)classDirective.Values[0]).Text), |
||||
|
false) : |
||||
|
XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true), |
||||
|
(XamlIlAstXmlTypeReference)rootObject.Type, true); |
||||
|
|
||||
|
|
||||
|
if (overrideRootType != null) |
||||
|
{ |
||||
|
|
||||
|
|
||||
|
if (!rootType.Type.IsAssignableFrom(overrideRootType)) |
||||
|
throw new XamlIlLoadException( |
||||
|
$"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject); |
||||
|
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false); |
||||
|
} |
||||
|
|
||||
|
rootObject.Type = rootType; |
||||
|
|
||||
|
Transform(parsed); |
||||
|
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,176 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
||||
|
using XamlIl; |
||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Transform; |
||||
|
using XamlIl.TypeSystem; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions |
||||
|
{ |
||||
|
/* |
||||
|
This file is used in the build task. |
||||
|
ONLY use types from netstandard and XamlIl. NO dependencies on Avalonia are allowed. Only strings. |
||||
|
No, nameof isn't welcome here either |
||||
|
*/ |
||||
|
|
||||
|
class AvaloniaXamlIlLanguage |
||||
|
{ |
||||
|
public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem) |
||||
|
{ |
||||
|
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); |
||||
|
var assignBindingAttribute = typeSystem.GetType("Avalonia.Data.AssignBindingAttribute"); |
||||
|
var bindingType = typeSystem.GetType("Avalonia.Data.IBinding"); |
||||
|
var rv = new XamlIlLanguageTypeMappings(typeSystem) |
||||
|
{ |
||||
|
SupportInitialize = typeSystem.GetType("System.ComponentModel.ISupportInitialize"), |
||||
|
XmlnsAttributes = |
||||
|
{ |
||||
|
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"), |
||||
|
typeSystem.FindType("Portable.Xaml.Markup.XmlnsDefinitionAttribute") |
||||
|
}, |
||||
|
ContentAttributes = |
||||
|
{ |
||||
|
typeSystem.GetType("Avalonia.Metadata.ContentAttribute") |
||||
|
}, |
||||
|
ProvideValueTarget = typeSystem.GetType("Portable.Xaml.Markup.IProvideValueTarget"), |
||||
|
RootObjectProvider = typeSystem.GetType("Portable.Xaml.IRootObjectProvider"), |
||||
|
UriContextProvider = typeSystem.GetType("Portable.Xaml.Markup.IUriContext"), |
||||
|
ParentStackProvider = |
||||
|
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"), |
||||
|
|
||||
|
XmlNamespaceInfoProvider = |
||||
|
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"), |
||||
|
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")}, |
||||
|
DeferredContentExecutorCustomization = |
||||
|
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"), |
||||
|
UsableDuringInitializationAttributes = |
||||
|
{ |
||||
|
typeSystem.GetType("Portable.Xaml.Markup.UsableDuringInitializationAttribute"), |
||||
|
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"), |
||||
|
}, |
||||
|
InnerServiceProviderFactoryMethod = |
||||
|
runtimeHelpers.FindMethod(m => m.Name == "CreateInnerServiceProviderV1"), |
||||
|
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit, |
||||
|
}; |
||||
|
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv); |
||||
|
return rv; |
||||
|
} |
||||
|
|
||||
|
class AttributeResolver : IXamlIlCustomAttributeResolver |
||||
|
{ |
||||
|
private readonly IXamlIlType _typeConverterAttribute; |
||||
|
|
||||
|
private readonly List<KeyValuePair<IXamlIlType, IXamlIlType>> _converters = |
||||
|
new List<KeyValuePair<IXamlIlType, IXamlIlType>>(); |
||||
|
|
||||
|
private readonly IXamlIlType _avaloniaList; |
||||
|
private readonly IXamlIlType _avaloniaListConverter; |
||||
|
|
||||
|
|
||||
|
public AttributeResolver(IXamlIlTypeSystem typeSystem, XamlIlLanguageTypeMappings mappings) |
||||
|
{ |
||||
|
_typeConverterAttribute = mappings.TypeConverterAttributes.First(); |
||||
|
|
||||
|
void AddType(IXamlIlType type, IXamlIlType conv) |
||||
|
=> _converters.Add(new KeyValuePair<IXamlIlType, IXamlIlType>(type, conv)); |
||||
|
|
||||
|
void Add(string type, string conv) |
||||
|
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv)); |
||||
|
|
||||
|
|
||||
|
//Add("Avalonia.AvaloniaProperty","Avalonia.Markup.Xaml.Converters.AvaloniaPropertyTypeConverter");
|
||||
|
Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"); |
||||
|
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1"); |
||||
|
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")), |
||||
|
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter")); |
||||
|
Add("Avalonia.Controls.Templates.IMemberSelector", |
||||
|
"Avalonia.Markup.Xaml.Converters.MemberSelectorTypeConverter"); |
||||
|
Add("Avalonia.Styling.Selector","Avalonia.Markup.Xaml.Converters.SelectorTypeConverter"); |
||||
|
Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter"); |
||||
|
Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter"); |
||||
|
Add("System.Uri", "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter"); |
||||
|
Add("System.TimeSpan", "Avalonia.Markup.Xaml.Converters.TimeSpanTypeConverter"); |
||||
|
Add("Avalonia.Media.FontFamily","Avalonia.Markup.Xaml.Converters.FontFamilyTypeConverter"); |
||||
|
_avaloniaList = typeSystem.GetType("Avalonia.Collections.AvaloniaList`1"); |
||||
|
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1"); |
||||
|
} |
||||
|
|
||||
|
IXamlIlType LookupConverter(IXamlIlType type) |
||||
|
{ |
||||
|
foreach(var p in _converters) |
||||
|
if (p.Key.Equals(type)) |
||||
|
return p.Value; |
||||
|
if (type.GenericTypeDefinition?.Equals(_avaloniaList) == true) |
||||
|
return _avaloniaListConverter.MakeGenericType(type.GenericArguments[0]); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
class ConstructedAttribute : IXamlIlCustomAttribute |
||||
|
{ |
||||
|
public bool Equals(IXamlIlCustomAttribute other) => false; |
||||
|
|
||||
|
public IXamlIlType Type { get; } |
||||
|
public List<object> Parameters { get; } |
||||
|
public Dictionary<string, object> Properties { get; } |
||||
|
|
||||
|
public ConstructedAttribute(IXamlIlType type, List<object> parameters, Dictionary<string, object> properties) |
||||
|
{ |
||||
|
Type = type; |
||||
|
Parameters = parameters ?? new List<object>(); |
||||
|
Properties = properties ?? new Dictionary<string, object>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType attributeType) |
||||
|
{ |
||||
|
if (attributeType.Equals(_typeConverterAttribute)) |
||||
|
{ |
||||
|
var conv = LookupConverter(type); |
||||
|
if (conv != null) |
||||
|
return new ConstructedAttribute(_typeConverterAttribute, new List<object>() {conv}, null); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlProperty property, IXamlIlType attributeType) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static bool CustomValueConverter(XamlIlAstTransformationContext context, |
||||
|
IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result) |
||||
|
{ |
||||
|
if (type.FullName == "System.TimeSpan" |
||||
|
&& node is XamlIlAstTextNode tn |
||||
|
&& !tn.Text.Contains(":")) |
||||
|
{ |
||||
|
var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture); |
||||
|
result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn, |
||||
|
type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double), |
||||
|
new[] |
||||
|
{ |
||||
|
new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds) |
||||
|
}); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (type.FullName == "Avalonia.AvaloniaProperty") |
||||
|
{ |
||||
|
var scope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault(); |
||||
|
if (scope == null) |
||||
|
throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node); |
||||
|
if (!(node is XamlIlAstTextNode text)) |
||||
|
throw new XamlIlLoadException("Property should be a text node", node); |
||||
|
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
result = null; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Transform; |
||||
|
using XamlIl.TypeSystem; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
||||
|
{ |
||||
|
class AddNameScopeRegistration : IXamlIlAstTransformer |
||||
|
{ |
||||
|
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
||||
|
{ |
||||
|
if (node is XamlIlPropertyAssignmentNode pa |
||||
|
&& pa.Property.Name == "Name" |
||||
|
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement") |
||||
|
{ |
||||
|
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg |
||||
|
&& mg.Children.OfType<ScopeRegistrationNode>().Any()) |
||||
|
return node; |
||||
|
|
||||
|
IXamlIlAstValueNode value = null; |
||||
|
for (var c = 0; c < pa.Values.Count; c++) |
||||
|
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String)) |
||||
|
{ |
||||
|
value = pa.Values[c]; |
||||
|
if (!(value is XamlIlAstTextNode)) |
||||
|
{ |
||||
|
var local = new XamlIlAstCompilerLocalNode(value); |
||||
|
// Wrap original in local initialization
|
||||
|
pa.Values[c] = new XamlIlAstLocalInitializationNodeEmitter(value, value, local); |
||||
|
// Use local
|
||||
|
value = local; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (value != null) |
||||
|
return new XamlIlManipulationGroupNode(pa) |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
pa, |
||||
|
new ScopeRegistrationNode(value) |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
} |
||||
|
|
||||
|
class ScopeRegistrationNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode |
||||
|
{ |
||||
|
public IXamlIlAstValueNode Value { get; set; } |
||||
|
public ScopeRegistrationNode(IXamlIlAstValueNode value) : base(value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public override void VisitChildren(IXamlIlAstVisitor visitor) |
||||
|
=> Value = (IXamlIlAstValueNode)Value.Visit(visitor); |
||||
|
|
||||
|
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) |
||||
|
{ |
||||
|
var exts = context.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScopeExtensions"); |
||||
|
var findNameScope = exts.FindMethod(m => m.Name == "FindNameScope"); |
||||
|
var registerMethod = findNameScope.ReturnType.FindMethod(m => m.Name == "Register"); |
||||
|
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object)) |
||||
|
using (var nameScopeLoc = context.GetLocal(findNameScope.ReturnType)) |
||||
|
{ |
||||
|
var exit = codeGen.DefineLabel(); |
||||
|
codeGen |
||||
|
// var target = {pop}
|
||||
|
.Stloc(targetLoc.Local) |
||||
|
// var scope = target.FindNameScope()
|
||||
|
.Ldloc(targetLoc.Local) |
||||
|
.Castclass(findNameScope.Parameters[0]) |
||||
|
.EmitCall(findNameScope) |
||||
|
.Stloc(nameScopeLoc.Local) |
||||
|
// if({scope} != null) goto call;
|
||||
|
.Ldloc(nameScopeLoc.Local) |
||||
|
.Brfalse(exit) |
||||
|
.Ldloc(nameScopeLoc.Local); |
||||
|
context.Emit(Value, codeGen, Value.Type.GetClrType()); |
||||
|
codeGen |
||||
|
.Ldloc(targetLoc.Local) |
||||
|
.EmitCall(registerMethod) |
||||
|
.MarkLabel(exit); |
||||
|
} |
||||
|
return XamlIlNodeEmitResult.Void(1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Transform; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
||||
|
{ |
||||
|
class AvaloniaBindingExtensionHackTransformer : IXamlIlAstTransformer |
||||
|
{ |
||||
|
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
||||
|
{ |
||||
|
// Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
|
||||
|
// This is the legacy of Portable.Xaml, so we emulate that behavior here
|
||||
|
|
||||
|
if (node is XamlIlAstXmlTypeReference tref |
||||
|
&& tref.Name == "Binding" |
||||
|
&& tref.XmlNamespace == "https://github.com/avaloniaui") |
||||
|
tref.IsMarkupExtension = true; |
||||
|
return node; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System.Linq; |
||||
|
using XamlIl.Ast; |
||||
|
using XamlIl.Transform; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
||||
|
{ |
||||
|
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer |
||||
|
{ |
||||
|
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) |
||||
|
{ |
||||
|
if (node is XamlIlAstClrProperty prop) |
||||
|
{ |
||||
|
var n = prop.Name + "Property"; |
||||
|
var field = |
||||
|
prop.DeclaringType.Fields |
||||
|
.FirstOrDefault(f => f.Name == n); |
||||
|
if (field != null) |
||||
|
return new XamlIlAvaloniaProperty(prop, field, context.GetAvaloniaTypes()); |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue