diff --git a/.gitmodules b/.gitmodules
index 22c56307b0..22a241f120 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "nukebuild/Numerge"]
path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git
+[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
+ path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+ url = https://github.com/kekekeks/XamlIl.git
diff --git a/build/EmbedXaml.props b/build/EmbedXaml.props
index 219ffb2e42..dba14521ab 100644
--- a/build/EmbedXaml.props
+++ b/build/EmbedXaml.props
@@ -4,8 +4,8 @@
%(Filename)
-
+
Designer
-
+
-
\ No newline at end of file
+
diff --git a/build/SampleApp.props b/build/SampleApp.props
index 5580a4c2c9..285f880129 100644
--- a/build/SampleApp.props
+++ b/build/SampleApp.props
@@ -5,4 +5,9 @@
+
+
+
diff --git a/dirs.proj b/dirs.proj
index 0f24632b72..4939a158bb 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -6,12 +6,12 @@
+
-
+
+
-
-
diff --git a/nukebuild/Numerge b/nukebuild/Numerge
index aef10ae67d..4464343aef 160000
--- a/nukebuild/Numerge
+++ b/nukebuild/Numerge
@@ -1 +1 @@
-Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
+Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 10f971cc4c..a455e2b3fc 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -8,6 +8,10 @@
AssemblyFile="$(AvaloniaBuildTasksLocation)"
/>
+
+
@@ -20,11 +24,15 @@
+
+ $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences
+
+
+ DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
+ Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
+
+
+
+ $(IntermediateOutputPath)/Avalonia/references
+ $(IntermediateOutputPath)/Avalonia/original.dll
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index 589f41c06b..804ca1f9b8 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -3,6 +3,7 @@
Exe
netcoreapp2.0
+ true
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index d20e0100a0..2f6d25c089 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -4,7 +4,7 @@
-
+
-
-
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000000..28cb84dad0
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -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 ShowCustomManagedNotificationCommand { get; }
+
+ public ReactiveCommand ShowManagedNotificationCommand { get; }
+
+ public ReactiveCommand ShowNativeNotificationCommand { get; }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
new file mode 100644
index 0000000000..8724ba344b
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
@@ -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 YesCommand { get; }
+
+ public ReactiveCommand NoCommand { get; }
+
+ }
+}
diff --git a/samples/ControlCatalog/Views/CustomNotificationView.xaml b/samples/ControlCatalog/Views/CustomNotificationView.xaml
new file mode 100644
index 0000000000..5b99ed8e4d
--- /dev/null
+++ b/samples/ControlCatalog/Views/CustomNotificationView.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Views/CustomNotificationView.xaml.cs b/samples/ControlCatalog/Views/CustomNotificationView.xaml.cs
new file mode 100644
index 0000000000..50a782276a
--- /dev/null
+++ b/samples/ControlCatalog/Views/CustomNotificationView.xaml.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 953132116c..1de5cb06c6 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -19,7 +19,7 @@ namespace Avalonia
///
/// Represents an unset property value.
///
- public static readonly object UnsetValue = new Unset();
+ public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
private readonly Subject _initialized;
@@ -546,16 +546,17 @@ namespace Avalonia
}
}
+
+ }
+ ///
+ /// Class representing the .
+ ///
+ public class UnsetValueType
+ {
///
- /// Class representing the .
+ /// Returns the string representation of the .
///
- private class Unset
- {
- ///
- /// Returns the string representation of the .
- ///
- /// The string "(unset)".
- public override string ToString() => "(unset)";
- }
+ /// The string "(unset)".
+ public override string ToString() => "(unset)";
}
}
diff --git a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
index 0365ead24d..2432410203 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
@@ -9,7 +9,10 @@ namespace Avalonia.Data.Core
/// Exception thrown when could not parse the provided
/// expression string.
///
- public class ExpressionParseException : Exception
+#if !BUILDTASK
+ public
+#endif
+ class ExpressionParseException : Exception
{
///
/// Initializes a new instance of the class.
diff --git a/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs
new file mode 100644
index 0000000000..753a96b9ce
--- /dev/null
+++ b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Avalonia.Metadata
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class UsableDuringInitializationAttribute : Attribute
+ {
+
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs
index 880ed62046..4356fac4c0 100644
--- a/src/Avalonia.Base/Platform/IAssetLoader.cs
+++ b/src/Avalonia.Base/Platform/IAssetLoader.cs
@@ -61,6 +61,16 @@ namespace Avalonia.Platform
///
(Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null);
+ ///
+ /// Extracts assembly information from URI
+ ///
+ /// The URI.
+ ///
+ /// A base URI to use if is relative.
+ ///
+ /// Assembly associated with the Uri
+ Assembly GetAssembly(Uri uri, Uri baseUri = null);
+
///
/// Gets all assets of a folder and subfolders that match specified uri.
///
diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
index 66024236da..ed5bc697b0 100644
--- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
+++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
@@ -46,6 +46,36 @@ namespace Avalonia.Utilities
Entries = entries
});
}
+
+ public static byte[] Create(Dictionary data)
+ {
+ var sources = data.ToList();
+ var offsets = new Dictionary();
+ var coffset = 0;
+ foreach (var s in sources)
+ {
+ offsets[s.Key] = coffset;
+ coffset += s.Value.Length;
+ }
+ var index = sources.Select(s => new AvaloniaResourcesIndexEntry
+ {
+ Path = s.Key,
+ Size = s.Value.Length,
+ Offset = offsets[s.Key]
+ }).ToList();
+ var output = new MemoryStream();
+ var ms = new MemoryStream();
+ AvaloniaResourcesIndexReaderWriter.Write(ms, index);
+ new BinaryWriter(output).Write((int)ms.Length);
+ ms.Position = 0;
+ ms.CopyTo(output);
+ foreach (var s in sources)
+ {
+ output.Write(s.Value,0,s.Value.Length);
+ }
+
+ return output.ToArray();
+ }
}
[DataContract]
diff --git a/src/Avalonia.Base/Utilities/CharacterReader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs
index 56f3f7abd4..9be2d230d4 100644
--- a/src/Avalonia.Base/Utilities/CharacterReader.cs
+++ b/src/Avalonia.Base/Utilities/CharacterReader.cs
@@ -5,7 +5,10 @@ using System;
namespace Avalonia.Utilities
{
- public ref struct CharacterReader
+#if !BUILDTASK
+ public
+#endif
+ ref struct CharacterReader
{
private ReadOnlySpan _s;
diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs
index 09c50bf147..a57a2b7ba5 100644
--- a/src/Avalonia.Base/Utilities/IdentifierParser.cs
+++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs
@@ -6,7 +6,10 @@ using System.Globalization;
namespace Avalonia.Utilities
{
- public static class IdentifierParser
+#if !BUILDTASK
+ public
+#endif
+ static class IdentifierParser
{
public static ReadOnlySpan ParseIdentifier(this ref CharacterReader r)
{
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index ce767aaa87..582e4499c5 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -1,9 +1,13 @@
-
-
+
- netstandard2.0
+ netstandard2.0
+ netstandard2.0;netcoreapp2.0
+ exe
+ false
tools
- $(DefineConstants);BUILDTASK
+ $(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL
+ true
+ NU1605
@@ -13,6 +17,54 @@
Shared/AvaloniaResourceXamlInfo.cs
-
+
+ XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)
+
+
+
+ XamlIl/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+
+
+
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
new file mode 100644
index 0000000000..61303dbbfe
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -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; }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs
index faecee0f37..440c6d7489 100644
--- a/src/Avalonia.Build.Tasks/Extensions.cs
+++ b/src/Avalonia.Build.Tasks/Extensions.cs
@@ -1,8 +1,9 @@
+using System;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
{
- public static class Extensions
+ static class Extensions
{
static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}";
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
new file mode 100644
index 0000000000..c2d0950264
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/Program.cs
@@ -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; }
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs
new file mode 100644
index 0000000000..d5c406293e
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/SpanCompat.cs
@@ -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
+ {
+ 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 Slice(int start, int len)
+ {
+ return new ReadOnlySpan(_s, _start + start, len);
+ }
+
+ public static ReadOnlySpan Empty => default;
+
+ public ReadOnlySpan Slice(int start)
+ {
+ return new ReadOnlySpan(_s, _start + start, _length - start);
+ }
+
+ public bool SequenceEqual(ReadOnlySpan 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 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 AsSpan(this string s) => new ReadOnlySpan(s);
+ }
+
+}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
new file mode 100644
index 0000000000..05193172be
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
@@ -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 Resources { get; }
+ }
+
+ class EmbeddedResources : IResourceGroup
+ {
+ private readonly AssemblyDefinition _asm;
+ public string Name => "EmbeddedResource";
+
+ public IEnumerable Resources => _asm.MainModule.Resources.OfType()
+ .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 _resources = new Dictionary();
+ 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 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
+ });
+
+ }
+ }
+
+}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
new file mode 100644
index 0000000000..ec668f2a2b
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -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()
+ .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()
+ .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);
+ }
+
+ }
+}
diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
index 889ed84993..21d34ae4d6 100644
--- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
+++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -11,10 +11,14 @@
+
+
+
+
diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
index d213285aff..09c3d07a41 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
@@ -26,6 +26,7 @@ namespace Avalonia.Controls
/// Gets or sets the binding that associates the column with a property in the data source.
///
//TODO Binding
+ [AssignBinding]
public virtual IBinding Binding
{
get
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index 950d4f34da..07372eb714 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -20,6 +20,9 @@ namespace Avalonia.Controls
public static readonly AvaloniaProperty LayoutTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
+ public static readonly AvaloniaProperty UseRenderTransformProperty =
+ AvaloniaProperty.Register(nameof(LayoutTransform));
+
static LayoutTransformControl()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
@@ -29,6 +32,7 @@ namespace Avalonia.Controls
ChildProperty.Changed
.AddClassHandler(x => x.OnChildChanged);
+ UseRenderTransformProperty.Changed.AddClassHandler(x => x.OnUseRenderTransformPropertyChanged);
}
///
@@ -40,6 +44,15 @@ namespace Avalonia.Controls
set { SetValue(LayoutTransformProperty, value); }
}
+ ///
+ /// Utilize the for layout transforms.
+ ///
+ public bool UseRenderTransform
+ {
+ get { return GetValue(UseRenderTransformProperty); }
+ set { SetValue(UseRenderTransformProperty, value); }
+ }
+
public IControl TransformRoot => Child;
///
@@ -51,6 +64,7 @@ namespace Avalonia.Controls
{
if (TransformRoot == null || LayoutTransform == null)
{
+ LayoutTransform = RenderTransform;
return base.ArrangeOverride(finalSize);
}
@@ -133,6 +147,42 @@ namespace Avalonia.Controls
return transformedDesiredSize;
}
+ IDisposable _renderTransformChangedEvent;
+
+ private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ // HACK: In theory, this method and the UseRenderTransform shouldn't exist but
+ // it's hard to animate this particular control with style animations without
+ // PropertyPaths.
+ //
+ // So until we get that implemented, we'll stick on this not-so-good
+ // workaround.
+
+ var target = e.Sender as LayoutTransformControl;
+ var shouldUseRenderTransform = (bool)e.NewValue;
+ if (target != null)
+ {
+ if (shouldUseRenderTransform)
+ {
+ _renderTransformChangedEvent = RenderTransformProperty.Changed
+ .Subscribe(
+ (x) =>
+ {
+ var target2 = x.Sender as LayoutTransformControl;
+ if (target2 != null)
+ {
+ target2.LayoutTransform = target2.RenderTransform;
+ }
+ });
+ }
+ else
+ {
+ _renderTransformChangedEvent?.Dispose();
+ LayoutTransform = null;
+ }
+ }
+ }
+
private void OnChildChanged(AvaloniaPropertyChangedEventArgs e)
{
if (null != TransformRoot)
diff --git a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
new file mode 100644
index 0000000000..03c2fe85b8
--- /dev/null
+++ b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
@@ -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
+{
+ ///
+ /// Represents a notification manager that can show arbitrary content.
+ /// Managed notification managers can show any content.
+ ///
+ ///
+ /// 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.
+ ///
+ public interface IManagedNotificationManager : INotificationManager
+ {
+ ///
+ /// Shows a notification.
+ ///
+ /// The content to be displayed.
+ void Show(object content);
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/INotification.cs b/src/Avalonia.Controls/Notifications/INotification.cs
new file mode 100644
index 0000000000..2c6cb90133
--- /dev/null
+++ b/src/Avalonia.Controls/Notifications/INotification.cs
@@ -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
+{
+ ///
+ /// Represents a notification that can be shown in a window or by the host operating system.
+ ///
+ public interface INotification
+ {
+ ///
+ /// Gets the Title of the notification.
+ ///
+ string Title { get; }
+
+ ///
+ /// Gets the notification message.
+ ///
+ string Message { get; }
+
+ ///
+ /// Gets the of the notification.
+ ///
+ NotificationType Type { get; }
+
+ ///
+ /// Gets the expiration time of the notification after which it will automatically close.
+ /// If the value is then the notification will remain open until the user closes it.
+ ///
+ TimeSpan Expiration { get; }
+
+ ///
+ /// Gets an Action to be run when the notification is clicked.
+ ///
+ Action OnClick { get; }
+
+ ///
+ /// Gets an Action to be run when the notification is closed.
+ ///
+ Action OnClose { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/INotificationManager.cs b/src/Avalonia.Controls/Notifications/INotificationManager.cs
new file mode 100644
index 0000000000..21430c900d
--- /dev/null
+++ b/src/Avalonia.Controls/Notifications/INotificationManager.cs
@@ -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
+{
+ ///
+ /// Represents a notification manager that can be used to show notifications in a window or using
+ /// the host operating system.
+ ///
+ public interface INotificationManager
+ {
+ ///
+ /// Show a notification.
+ ///
+ /// The notification to be displayed.
+ void Show(INotification notification);
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/Notification.cs b/src/Avalonia.Controls/Notifications/Notification.cs
new file mode 100644
index 0000000000..4204ad0aee
--- /dev/null
+++ b/src/Avalonia.Controls/Notifications/Notification.cs
@@ -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
+{
+ ///
+ /// A notification that can be shown in a window or by the host operating system.
+ ///
+ ///
+ /// This class represents a notification that can be displayed either in a window using
+ /// or by the host operating system (to be implemented).
+ ///
+ public class Notification : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The title of the notification.
+ /// The message to be displayed in the notification.
+ /// The of the notification.
+ /// The expiry time at which the notification will close.
+ /// Use for notifications that will remain open.
+ /// An Action to call when the notification is clicked.
+ /// An Action to call when the notification is closed.
+ 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;
+ }
+
+ ///
+ public string Title { get; private set; }
+
+ ///
+ public string Message { get; private set; }
+
+ ///
+ public NotificationType Type { get; private set; }
+
+ ///
+ public TimeSpan Expiration { get; private set; }
+
+ ///
+ public Action OnClick { get; private set; }
+
+ ///
+ public Action OnClose { get; private set; }
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs
new file mode 100644
index 0000000000..7f69afaeeb
--- /dev/null
+++ b/src/Avalonia.Controls/Notifications/NotificationCard.cs
@@ -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
+{
+ ///
+ /// Control that represents and displays a notification.
+ ///
+ public class NotificationCard : ContentControl
+ {
+ private bool _isClosed;
+ private bool _isClosing;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationCard()
+ {
+ this.GetObservable(IsClosedProperty)
+ .Subscribe(x =>
+ {
+ if (!IsClosing && !IsClosed)
+ {
+ return;
+ }
+
+ RaiseEvent(new RoutedEventArgs(NotificationClosedEvent));
+ });
+
+ this.GetObservable(ContentProperty)
+ .OfType()
+ .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;
+ }
+ });
+ }
+
+ ///
+ /// Determines if the notification is already closing.
+ ///
+ public bool IsClosing
+ {
+ get { return _isClosing; }
+ private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); }
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsClosingProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsClosing), o => o.IsClosing);
+
+ ///
+ /// Determines if the notification is closed.
+ ///
+ public bool IsClosed
+ {
+ get { return _isClosed; }
+ set { SetAndRaise(IsClosedProperty, ref _isClosed, value); }
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsClosedProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent NotificationClosedEvent =
+ RoutedEvent.Register(nameof(NotificationClosed), RoutingStrategies.Bubble);
+
+
+ ///
+ /// Raised when the has closed.
+ ///
+ public event EventHandler 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);
+ }
+
+ ///
+ /// Defines the CloseOnClick property.
+ ///
+ public static readonly AvaloniaProperty CloseOnClickProperty =
+ AvaloniaProperty.RegisterAttached
+
diff --git a/src/Avalonia.Diagnostics/DevTools.xaml b/src/Avalonia.Diagnostics/DevTools.xaml
index a3b12f0ec5..0f55d42e33 100644
--- a/src/Avalonia.Diagnostics/DevTools.xaml
+++ b/src/Avalonia.Diagnostics/DevTools.xaml
@@ -1,4 +1,6 @@
-
+
diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs
index d084ec3014..e0bacf326b 100644
--- a/src/Avalonia.Diagnostics/DevTools.xaml.cs
+++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs
@@ -43,6 +43,12 @@ namespace Avalonia.Diagnostics
.Subscribe(RawKeyDown);
}
+ // HACK: needed for XAMLIL, will fix that later
+ public DevTools()
+ {
+
+ }
+
public IControl Root { get; }
public static IDisposable Attach(TopLevel control)
diff --git a/src/Avalonia.Diagnostics/Views/EventsView.xaml b/src/Avalonia.Diagnostics/Views/EventsView.xaml
index a5be86b613..8d4d37f7b3 100644
--- a/src/Avalonia.Diagnostics/Views/EventsView.xaml
+++ b/src/Avalonia.Diagnostics/Views/EventsView.xaml
@@ -1,6 +1,7 @@
+ xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
+ x:Class="Avalonia.Diagnostics.Views.EventsView">
diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Views/TreePageView.xaml
index 57398851ad..e927bcdf29 100644
--- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml
+++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml
@@ -1,5 +1,7 @@
+ xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ x:Class="Avalonia.Diagnostics.Views.TreePageView">
diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs
index 491e4d71a7..991a97a614 100644
--- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs
+++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs
@@ -71,10 +71,11 @@ namespace Avalonia.Controls
{
Contract.Requires(control != null);
- return control.GetSelfAndLogicalAncestors()
+ var scope = control.GetSelfAndLogicalAncestors()
.OfType()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
+ return scope;
}
}
}
diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs
index 3e7a30d389..323f53b85b 100644
--- a/src/Avalonia.Styling/Styling/Selectors.cs
+++ b/src/Avalonia.Styling/Styling/Selectors.cs
@@ -114,6 +114,17 @@ namespace Avalonia.Styling
{
return new NotSelector(previous, argument(null));
}
+
+ ///
+ /// Returns a selector which inverts the results of selector argument.
+ ///
+ /// The previous selector.
+ /// The selector to be not-ed.
+ /// The selector.
+ public static Selector Not(this Selector previous, Selector argument)
+ {
+ return new NotSelector(previous, argument);
+ }
///
/// Returns a selector which matches a type.
diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
index e9d710b3f0..f84e09510b 100644
--- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
+++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml
index fcdb76524e..92482a564c 100644
--- a/src/Avalonia.Themes.Default/TabItem.xaml
+++ b/src/Avalonia.Themes.Default/TabItem.xaml
@@ -24,19 +24,19 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs
index 5c152cd8a0..a486723d86 100644
--- a/src/Avalonia.Visuals/Media/FontFamily.cs
+++ b/src/Avalonia.Visuals/Media/FontFamily.cs
@@ -11,46 +11,49 @@ namespace Avalonia.Media
{
public class FontFamily
{
+ ///
///
/// Initializes a new instance of the class.
///
- /// The name of the .
- /// name
- public FontFamily(string name)
+ /// The name of the .
+ public FontFamily(string name) : this(null, name)
{
- Contract.Requires(name != null);
-
- FamilyNames = new FamilyNameCollection(new[] { name });
}
///
/// Initializes a new instance of the class.
///
- /// The names of the .
- /// name
- public FontFamily(IEnumerable names)
+ /// Specifies the base uri that is used to resolve font family assets.
+ /// The name of the .
+ /// Base uri must be an absolute uri.
+ public FontFamily(Uri baseUri, string name)
{
- Contract.Requires(names != null);
+ if (string.IsNullOrEmpty(name))
+ {
+ FamilyNames = new FamilyNameCollection(string.Empty);
- FamilyNames = new FamilyNameCollection(names);
- }
+ return;
+ }
- ///
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the .
- /// The source of font resources.
- ///
- public FontFamily(string name, Uri source, Uri baseUri = null) : this(name)
- {
- Key = new FontFamilyKey(source, baseUri);
+ var fontFamilySegment = GetFontFamilyIdentifier(name);
+
+ if (fontFamilySegment.Source != null)
+ {
+ if (baseUri != null && !baseUri.IsAbsoluteUri)
+ {
+ throw new ArgumentException("Base uri must be an absolute uri.", nameof(baseUri));
+ }
+
+ Key = new FontFamilyKey(fontFamilySegment.Source, baseUri);
+ }
+
+ FamilyNames = new FamilyNameCollection(fontFamilySegment.Name);
}
///
/// Represents the default font family
///
- public static FontFamily Default => new FontFamily(String.Empty);
+ public static FontFamily Default => new FontFamily(string.Empty);
///
/// Represents all font families in the system. This can be an expensive call depending on platform implementation.
@@ -88,46 +91,40 @@ namespace Avalonia.Media
///
public static implicit operator FontFamily(string s)
{
- return Parse(s);
+ return new FontFamily(s);
}
- ///
- /// Parses a string.
- ///
- /// The string.
- ///
- ///
- ///
- /// Specified family is not supported.
- ///
- public static FontFamily Parse(string s, Uri baseUri = null)
+ private struct FontFamilyIdentifier
{
- if (string.IsNullOrEmpty(s))
+ public FontFamilyIdentifier(string name, Uri source)
{
- throw new ArgumentException("Specified family is not supported.");
+ Name = name;
+ Source = source;
}
- var segments = s.Split('#');
+ public string Name { get; }
+
+ public Uri Source { get; }
+ }
+
+ private static FontFamilyIdentifier GetFontFamilyIdentifier(string name)
+ {
+ var segments = name.Split('#');
switch (segments.Length)
{
case 1:
{
- var names = segments[0].Split(',')
- .Select(x => x.Trim())
- .Where(x => !string.IsNullOrWhiteSpace(x));
- return new FontFamily(names);
+ return new FontFamilyIdentifier(segments[0], null);
}
case 2:
{
- var uri = segments[0].StartsWith("/")
- ? new Uri(segments[0], UriKind.Relative)
- : new Uri(segments[0], UriKind.RelativeOrAbsolute);
+ var source = segments[0].StartsWith("/")
+ ? new Uri(segments[0], UriKind.Relative)
+ : new Uri(segments[0], UriKind.RelativeOrAbsolute);
- return uri.IsAbsoluteUri
- ? new FontFamily(segments[1], uri)
- : new FontFamily(segments[1], uri, baseUri);
+ return new FontFamilyIdentifier(segments[1], source);
}
default:
@@ -137,6 +134,25 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// Specifies the base uri that is used to resolve font family assets.
+ ///
+ ///
+ /// Specified family is not supported.
+ ///
+ public static FontFamily Parse(string s, Uri baseUri = null)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ throw new ArgumentException("Specified family is not supported.", nameof(s));
+ }
+
+ return new FontFamily(baseUri, s);
+ }
+
///
/// Returns a that represents this instance.
///
diff --git a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
index 50511d2fb7..acf0bbdb11 100644
--- a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
+++ b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
@@ -4,31 +4,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
+using System.Text;
namespace Avalonia.Media.Fonts
{
- using System.Text;
-
public class FamilyNameCollection : IEnumerable
- {
+ {
///
/// Initializes a new instance of the class.
///
/// The family names.
/// familyNames
- public FamilyNameCollection(IEnumerable familyNames)
+ public FamilyNameCollection(string familyNames)
{
- Contract.Requires(familyNames != null);
-
- var names = new List(familyNames);
-
- if (names.Count == 0) throw new ArgumentException($"{nameof(familyNames)} must not be empty.");
+ if (familyNames == null)
+ {
+ throw new ArgumentNullException(nameof(familyNames));
+ }
- Names = new ReadOnlyCollection(names);
+ Names = familyNames.Split(',').Select(x => x.Trim()).ToArray();
- PrimaryFamilyName = Names.First();
+ PrimaryFamilyName = Names[0];
HasFallbacks = Names.Count > 1;
}
@@ -55,7 +52,7 @@ namespace Avalonia.Media.Fonts
///
/// The names.
///
- internal ReadOnlyCollection Names { get; }
+ internal IReadOnlyList Names { get; }
///
///
@@ -95,7 +92,10 @@ namespace Avalonia.Media.Fonts
{
builder.Append(Names[index]);
- if (index == Names.Count - 1) break;
+ if (index == Names.Count - 1)
+ {
+ break;
+ }
builder.Append(", ");
}
@@ -123,9 +123,12 @@ namespace Avalonia.Media.Fonts
///
public override bool Equals(object obj)
{
- if (!(obj is FamilyNameCollection other)) return false;
+ if (!(obj is FamilyNameCollection other))
+ {
+ return false;
+ }
return other.ToString().Equals(ToString());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
index 76ee2b7aad..7733dd7d2a 100644
--- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
+++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
@@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts
{
if (!Source.IsAbsoluteUri && BaseUri != null)
{
- return BaseUri.Authority + Source;
+ return BaseUri.AbsoluteUri + Source.OriginalString;
}
return Source.ToString();
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index cc7da96ee8..d294f111ad 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -10,6 +10,7 @@ using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
+using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@@ -25,6 +26,7 @@ namespace Avalonia
/// to render the control. To traverse the visual tree, use the
/// extension methods defined in .
///
+ [UsableDuringInitialization]
public class Visual : StyledElement, IVisual
{
///
diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs
index 772bd1989c..ef49a72c43 100644
--- a/src/Avalonia.X11/XIStructs.cs
+++ b/src/Avalonia.X11/XIStructs.cs
@@ -197,7 +197,7 @@ namespace Avalonia.X11
unsafe struct XIDeviceChangedEvent
{
public int Type; /* GenericEvent */
- public ulong Serial; /* # of last request processed by server */
+ public UIntPtr Serial; /* # of last request processed by server */
public Bool SendEvent; /* true if this came from a SendEvent request */
public IntPtr Display; /* Display the event was read from */
public int Extension; /* XI extension offset */
@@ -214,7 +214,7 @@ namespace Avalonia.X11
struct XIDeviceEvent
{
public XEventName type; /* GenericEvent */
- public ulong serial; /* # of last request processed by server */
+ public UIntPtr serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
@@ -241,7 +241,7 @@ namespace Avalonia.X11
unsafe struct XIEvent
{
public int type; /* GenericEvent */
- public ulong serial; /* # of last request processed by server */
+ public UIntPtr serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 3c1ef84c54..8ce29e5b8e 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -1,7 +1,9 @@
netstandard2.0
- PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER
+ PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLIL_INTERNAL
+ false
+ $(DefineConstants);RUNTIME_XAML_CECIL
False
false
CS1591
@@ -16,6 +18,7 @@
+
@@ -51,8 +54,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -64,6 +92,8 @@
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
index be92b6a8d7..f967bdf0af 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Markup.Xaml
///
public static class AvaloniaTypeConverters
{
+ // When adding item to that list make sure to modify AvaloniaXamlIlLanguage
private static Dictionary _converters = new Dictionary()
{
{ typeof(AvaloniaList<>), typeof(AvaloniaListConverter<>) },
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
index 4c352f199f..45424e67bb 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
@@ -10,6 +10,7 @@ using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;
+using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml;
@@ -25,6 +26,8 @@ namespace Avalonia.Markup.Xaml
{
public bool IsDesignMode { get; set; }
+ public static bool UseLegacyXamlLoader { get; set; } = false;
+
///
/// Initializes a new instance of the class.
///
@@ -65,8 +68,8 @@ namespace Avalonia.Markup.Xaml
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
- }
-
+ }
+
foreach (var uri in GetUrisFor(assetLocator, type))
{
if (assetLocator.Exists(uri))
@@ -113,21 +116,27 @@ namespace Avalonia.Markup.Xaml
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
+ var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
+ ?.GetType("CompiledAvaloniaXaml.!XamlLoader")
+ ?.GetMethod("TryLoad", new[] {typeof(string)});
+ if (compiledLoader != null)
+ {
+ var uriString = (!uri.IsAbsoluteUri && baseUri != null ? new Uri(baseUri, uri) : uri)
+ .ToString();
+ var compiledResult = compiledLoader.Invoke(null, new object[] {uriString});
+ if (compiledResult != null)
+ return compiledResult;
+ }
+
+
var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
using (var stream = asset.stream)
{
var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
- try
- {
- return Load(stream, asset.assembly, rootInstance, absoluteUri);
- }
- catch (Exception e)
- {
- throw new XamlLoadException("Error loading xaml at " + absoluteUri + ": " + e.Message, e);
- }
+ return Load(stream, asset.assembly, rootInstance, absoluteUri);
}
}
-
+
///
/// Loads XAML from a string.
///
@@ -159,6 +168,10 @@ namespace Avalonia.Markup.Xaml
/// The loaded object.
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
{
+ if (!UseLegacyXamlLoader)
+ return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
+
+
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
index 8a0ba64582..18a7fe9ab6 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
@@ -28,8 +28,8 @@ namespace Avalonia.Markup.Xaml.Converters
var parser = new PropertyParser();
var (ns, owner, propertyName) = parser.Parse(new CharacterReader(((string)value).AsSpan()));
var ownerType = TryResolveOwnerByName(context, ns, owner);
- var targetType = context.GetFirstAmbientValue()?.TargetType ??
- context.GetFirstAmbientValue";
+ var xaml = "";
var loader = new AvaloniaXamlLoader();
var style = (Style)loader.Load(xaml);
var setter = (Setter)(style.Setters.First());
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
index 2e67541c1f..359d2521e0 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
@@ -1,6 +1,7 @@
// 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 Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@@ -17,6 +18,7 @@ using Portable.Xaml;
using System.Collections;
using System.ComponentModel;
using System.Linq;
+using System.Xml;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@@ -48,6 +50,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void AvaloniaProperty_Without_Getter_And_Setter_Is_Set()
{
+ // It's not possible to know in compile time if a read-only property has a magic way of being not read-only
+ if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
+ return;
var xaml =
@"";
- Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml));
+ XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse(xaml));
}
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
+ // 1) It has been allowed in AvaloniaObject.SetValue for ages
+ // 2) There is no way to know if AddOwner was called in compile-time
+ if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
+ return;
var xaml =
@"";
- Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml));
+ XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse(xaml));
}
[Fact]
@@ -518,7 +529,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml = @"
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
index fd93c4a468..04db8fd2dd 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
@@ -1,8 +1,9 @@
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml
new file mode 100644
index 0000000000..ac2f75b893
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
new file mode 100644
index 0000000000..a584027768
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using JetBrains.Annotations;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests
+{
+ public class XamlIlTests
+ {
+ [Fact]
+ public void Binding_Button_IsPressed_ShouldWork()
+ {
+ var parsed = (Button)AvaloniaXamlLoader.Parse(@"
+");
+ var ctx = new XamlIlBugTestsDataContext();
+ parsed.DataContext = ctx;
+ parsed.SetValue(Button.IsPressedProperty, true);
+ Assert.True(ctx.IsPressed);
+ }
+
+ [Fact]
+ public void Transitions_Should_Be_Properly_Parsed()
+ {
+ var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
+
+
+
+
+");
+ Assert.Equal(1, parsed.Transitions.Count);
+ Assert.Equal(Visual.OpacityProperty, parsed.Transitions[0].Property);
+ }
+
+ [Fact]
+ public void Parser_Should_Override_Precompiled_Xaml()
+ {
+ var precompiled = new XamlIlClassWithPrecompiledXaml();
+ Assert.Equal(Brushes.Red, precompiled.Background);
+ Assert.Equal(1, precompiled.Opacity);
+ var loaded = (XamlIlClassWithPrecompiledXaml)AvaloniaXamlLoader.Parse(@"
+
+
+");
+ Assert.Equal(loaded.Opacity, 0);
+ Assert.Null(loaded.Background);
+
+ }
+
+ [Fact]
+ public void RelativeSource_TemplatedParent_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ new AvaloniaXamlLoader().Load(@"
+
+
+
+
+",
+ null, Application.Current);
+ var parsed = (Window)AvaloniaXamlLoader.Parse(@"
+
+
+
+
+
+");
+ var btn = ((Button)parsed.Content);
+ btn.ApplyTemplate();
+ var canvas = (Canvas)btn.GetVisualChildren().First()
+ .VisualChildren.First()
+ .VisualChildren.First()
+ .VisualChildren.First();
+ Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color);
+ }
+ }
+ }
+
+ public class XamlIlBugTestsBrushToColorConverter : IMultiValueConverter
+ {
+ public object Convert(IList