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" |
|||
xmlns:pages="clr-namespace:ControlCatalog.Pages" |
|||
Title="Avalonia Control Gallery" |
|||
Icon="/Assets/test_icon.ico" |
|||
xmlns:local="clr-namespace:ControlCatalog" |
|||
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"> |
|||
<local:MainView/> |
|||
<Window.DataTemplates> |
|||
<DataTemplate DataType="vm:NotificationViewModel"> |
|||
<v:CustomNotificationView /> |
|||
</DataTemplate> |
|||
</Window.DataTemplates> |
|||
<Panel> |
|||
<local:MainView/> |
|||
</Panel> |
|||
</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