diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 90550a12f2..acc4145dc5 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Avalonia.Base/Metadata/TemplateContent.cs b/src/Avalonia.Base/Metadata/TemplateContent.cs new file mode 100644 index 0000000000..ecc16b04a2 --- /dev/null +++ b/src/Avalonia.Base/Metadata/TemplateContent.cs @@ -0,0 +1,15 @@ +// 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.Metadata +{ + /// + /// Defines the property that contains the object's content in markup. + /// + [AttributeUsage(AttributeTargets.Property)] + public class TemplateContentAttribute : Attribute + { + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 4ed20629cf..2f33fb912d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -21,7 +21,7 @@ full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;OMNIXAML_no prompt 4 CS1591 @@ -40,6 +40,13 @@ Properties\SharedAssemblyInfo.cs + + + + + + + @@ -266,8 +273,9 @@ + - + @@ -322,6 +330,10 @@ + + ..\..\..\packages\Portable.Xaml.0.15.0\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Portable.Xaml.dll + True + ..\..\..\packages\Sprache.2.1.0\lib\netstandard1.0\Sprache.dll diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 7b3d0036ef..978048bb9d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,225 +1,11 @@ -// 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.Generic; -using System.IO; -using System.Reflection; -using System.Text; -using OmniXaml; -using Avalonia.Platform; -using Avalonia.Markup.Xaml.Context; -using Avalonia.Markup.Xaml.Styling; -using OmniXaml.ObjectAssembler; -using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; - -namespace Avalonia.Markup.Xaml +namespace Avalonia.Markup.Xaml { - /// - /// Loads XAML for a avalonia application. - /// - public class AvaloniaXamlLoader : XmlLoader + public class AvaloniaXamlLoader : +#if OMNIXAML + AvaloniaXamlLoaderOmniXaml +#else + AvaloniaXamlLoaderPortableXaml +#endif { - private static AvaloniaParserFactory s_parserFactory; - private static IInstanceLifeCycleListener s_lifeCycleListener = new AvaloniaLifeCycleListener(); - private static Stack s_uriStack = new Stack(); - - /// - /// Initializes a new instance of the class. - /// - public AvaloniaXamlLoader() - : this(GetParserFactory()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The parser factory to use. - public AvaloniaXamlLoader(IParserFactory xamlParserFactory) - : base(xamlParserFactory) - { - } - - /// - /// Gets the URI of the XAML file currently being loaded. - /// - /// - /// TODO: Making this internal for now as I'm not sure that this is the correct - /// thing to do, but its needed by to get the URL of - /// the currently loading XAML file, as we can't use the OmniXAML parsing context - /// there. Maybe we need a way to inject OmniXAML context into the objects its - /// constructing? - /// - internal static Uri UriContext => s_uriStack.Count > 0 ? s_uriStack.Peek() : null; - - /// - /// Loads the XAML into a Avalonia component. - /// - /// The object to load the XAML into. - public static void Load(object obj) - { - Contract.Requires(obj != null); - - var loader = new AvaloniaXamlLoader(); - loader.Load(obj.GetType(), obj); - } - - /// - /// Loads the XAML for a type. - /// - /// The type. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Type type, object rootInstance = null) - { - Contract.Requires(type != null); - - // HACK: Currently Visual Studio is forcing us to change the extension of xaml files - // in certain situations, so we try to load .xaml and if that's not found we try .xaml. - // Ideally we'd be able to use .xaml everywhere - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - foreach (var uri in GetUrisFor(type)) - { - if (assetLocator.Exists(uri)) - { - using (var stream = assetLocator.Open(uri)) - { - var initialize = rootInstance as ISupportInitialize; - initialize?.BeginInit(); - return Load(stream, rootInstance, uri); - } - } - } - - throw new FileNotFoundException("Unable to find view for " + type.FullName); - } - - /// - /// Loads XAML from a URI. - /// - /// The URI of the XAML file. - /// - /// A base URI to use if is relative. - /// - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) - { - Contract.Requires(uri != null); - - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - using (var stream = assetLocator.Open(uri, baseUri)) - { - return Load(stream, rootInstance, uri); - } - } - - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(string xaml, object rootInstance = null) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, rootInstance); - } - } - - /// - /// Loads XAML from a stream. - /// - /// The stream containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The URI of the XAML - /// The loaded object. - public object Load(Stream stream, object rootInstance = null, Uri uri = null) - { - try - { - if (uri != null) - { - s_uriStack.Push(uri); - } - - var result = base.Load(stream, new Settings - { - RootInstance = rootInstance, - InstanceLifeCycleListener = s_lifeCycleListener, - ParsingContext = new Dictionary - { - { "Uri", uri } - } - }); - - var topLevel = result as TopLevel; - - if (topLevel != null) - { - DelayedBinding.ApplyBindings(topLevel); - } - - return result; - } - finally - { - if (uri != null) - { - s_uriStack.Pop(); - } - } - } - - private static AvaloniaParserFactory GetParserFactory() - { - if (s_parserFactory == null) - { - s_parserFactory = new AvaloniaParserFactory(); - } - - return s_parserFactory; - } - - /// - /// Gets the URI for a type. - /// - /// The type. - /// The URI. - private static IEnumerable GetUrisFor(Type type) - { - var asm = type.GetTypeInfo().Assembly.GetName().Name; - var typeName = type.FullName; - yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); - yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); - - } } -} +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderOmniXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderOmniXaml.cs new file mode 100644 index 0000000000..df734dfe39 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderOmniXaml.cs @@ -0,0 +1,227 @@ +// 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.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using OmniXaml; +using Avalonia.Platform; +using Avalonia.Markup.Xaml.Context; +using Avalonia.Markup.Xaml.Styling; +using OmniXaml.ObjectAssembler; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.Data; + +namespace Avalonia.Markup.Xaml +{ +#if OMNIXAML + /// + /// Loads XAML for a avalonia application. + /// + public class AvaloniaXamlLoaderOmniXaml : XmlLoader + { + private static AvaloniaParserFactory s_parserFactory; + private static IInstanceLifeCycleListener s_lifeCycleListener = new AvaloniaLifeCycleListener(); + private static Stack s_uriStack = new Stack(); + + /// + /// Initializes a new instance of the class. + /// + public AvaloniaXamlLoaderOmniXaml() + : this(GetParserFactory()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The parser factory to use. + public AvaloniaXamlLoaderOmniXaml(IParserFactory xamlParserFactory) + : base(xamlParserFactory) + { + } + + /// + /// Gets the URI of the XAML file currently being loaded. + /// + /// + /// TODO: Making this internal for now as I'm not sure that this is the correct + /// thing to do, but its needed by to get the URL of + /// the currently loading XAML file, as we can't use the OmniXAML parsing context + /// there. Maybe we need a way to inject OmniXAML context into the objects its + /// constructing? + /// + internal static Uri UriContext => s_uriStack.Count > 0 ? s_uriStack.Peek() : null; + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The object to load the XAML into. + public static void Load(object obj) + { + Contract.Requires(obj != null); + + var loader = new AvaloniaXamlLoaderOmniXaml(); + loader.Load(obj.GetType(), obj); + } + + /// + /// Loads the XAML for a type. + /// + /// The type. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Type type, object rootInstance = null) + { + Contract.Requires(type != null); + + // HACK: Currently Visual Studio is forcing us to change the extension of xaml files + // in certain situations, so we try to load .xaml and if that's not found we try .xaml. + // Ideally we'd be able to use .xaml everywhere + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + foreach (var uri in GetUrisFor(type)) + { + if (assetLocator.Exists(uri)) + { + using (var stream = assetLocator.Open(uri)) + { + var initialize = rootInstance as ISupportInitialize; + initialize?.BeginInit(); + return Load(stream, rootInstance, uri); + } + } + } + + throw new FileNotFoundException("Unable to find view for " + type.FullName); + } + + /// + /// Loads XAML from a URI. + /// + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) + { + Contract.Requires(uri != null); + + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + using (var stream = assetLocator.Open(uri, baseUri)) + { + return Load(stream, rootInstance, uri); + } + } + + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(string xaml, object rootInstance = null) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, rootInstance); + } + } + + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The URI of the XAML + /// The loaded object. + public object Load(Stream stream, object rootInstance = null, Uri uri = null) + { + try + { + if (uri != null) + { + s_uriStack.Push(uri); + } + + var result = base.Load(stream, new Settings + { + RootInstance = rootInstance, + InstanceLifeCycleListener = s_lifeCycleListener, + ParsingContext = new Dictionary + { + { "Uri", uri } + } + }); + + var topLevel = result as TopLevel; + + if (topLevel != null) + { + DelayedBinding.ApplyBindings(topLevel); + } + + return result; + } + finally + { + if (uri != null) + { + s_uriStack.Pop(); + } + } + } + + private static AvaloniaParserFactory GetParserFactory() + { + if (s_parserFactory == null) + { + s_parserFactory = new AvaloniaParserFactory(); + } + + return s_parserFactory; + } + + /// + /// Gets the URI for a type. + /// + /// The type. + /// The URI. + private static IEnumerable GetUrisFor(Type type) + { + var asm = type.GetTypeInfo().Assembly.GetName().Name; + var typeName = type.FullName; + yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); + yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); + + } + } +#endif +} diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs new file mode 100644 index 0000000000..d09787803a --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs @@ -0,0 +1,219 @@ +// 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.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.Context; +using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Xaml.PortableXaml; +using Avalonia.Platform; +using Portable.Xaml; + +namespace Avalonia.Markup.Xaml +{ + /// + /// Loads XAML for a avalonia application. + /// + public class AvaloniaXamlLoaderPortableXaml + { + internal static readonly AvaloniaXamlSchemaContext _context + = new AvaloniaXamlSchemaContext(new AvaloniaRuntimeTypeProvider()); + + /// + /// Initializes a new instance of the class. + /// + public AvaloniaXamlLoaderPortableXaml() + { + } + + /// + /// Gets the URI of the XAML file currently being loaded. + /// + /// + /// TODO: Making this internal for now as I'm not sure that this is the correct + /// thing to do, but its needed by to get the URL of + /// the currently loading XAML file, as we can't use the OmniXAML parsing context + /// there. Maybe we need a way to inject OmniXAML context into the objects its + /// constructing? + /// + [Obsolete] + internal static Uri UriContext => s_uriStack.Count > 0 ? s_uriStack.Peek() : null; + + private static Stack s_uriStack = new Stack(); + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The object to load the XAML into. + public static void Load(object obj) + { + Contract.Requires(obj != null); + + var loader = new AvaloniaXamlLoader(); + loader.Load(obj.GetType(), obj); + } + + /// + /// Loads the XAML for a type. + /// + /// The type. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Type type, object rootInstance = null) + { + Contract.Requires(type != null); + + // HACK: Currently Visual Studio is forcing us to change the extension of xaml files + // in certain situations, so we try to load .xaml and if that's not found we try .xaml. + // Ideally we'd be able to use .xaml everywhere + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + foreach (var uri in GetUrisFor(type)) + { + if (assetLocator.Exists(uri)) + { + using (var stream = assetLocator.Open(uri)) + { + var initialize = rootInstance as ISupportInitialize; + initialize?.BeginInit(); + return Load(stream, type, rootInstance, uri); + } + } + } + + throw new FileNotFoundException("Unable to find view for " + type.FullName); + } + + /// + /// Loads XAML from a URI. + /// + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) + { + Contract.Requires(uri != null); + + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + using (var stream = assetLocator.Open(uri, baseUri)) + { + return Load(stream, null, rootInstance, uri); + } + } + + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(string xaml, Type type = null, object rootInstance = null) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, type, rootInstance); + } + } + + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The URI of the XAML + /// The loaded object. + public object Load(Stream stream, Type type = null, object rootInstance = null, Uri uri = null) + { + try + { + if (uri != null) + { + s_uriStack.Push(uri); + } + + var readerSettings = new XamlXmlReaderSettings(); + + if (rootInstance != null && type == null) + { + type = rootInstance.GetType(); + } + + if (type != null) + { + readerSettings.LocalAssembly = type.GetTypeInfo().Assembly; + } + + var reader = new XamlXmlReader(stream, _context, readerSettings); + object result = Load(reader, rootInstance); + + var topLevel = result as TopLevel; + + if (topLevel != null) + { + DelayedBinding.ApplyBindings(topLevel); + } + + return result; + } + finally + { + if (uri != null) + { + s_uriStack.Pop(); + } + } + } + + internal static object Load(XamlXmlReader reader, object instance) + { + var writer = AvaloniaXamlObjectWriter.Create(_context, instance); + + XamlServices.Transform(reader, writer); + + return writer.Result; + } + + /// + /// Gets the URI for a type. + /// + /// The type. + /// The URI. + private static IEnumerable GetUrisFor(Type type) + { + var asm = type.GetTypeInfo().Assembly.GetName().Name; + var typeName = type.FullName; + yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); + yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs index 1f5d259fc3..fefebdd18d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs @@ -12,6 +12,7 @@ using System.Collections.ObjectModel; namespace Avalonia.Markup.Xaml.Context { +#if OMNIXAML public class AvaloniaObjectAssembler : IObjectAssembler { private readonly TemplateHostingObjectAssembler objectAssembler; @@ -76,4 +77,5 @@ namespace Avalonia.Markup.Xaml.Context return dict; } } +#endif } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaParserFactory.cs b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaParserFactory.cs index 9e0c21edd4..69ab0727da 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaParserFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaParserFactory.cs @@ -8,6 +8,7 @@ using OmniXaml.Parsers.ProtoParser; namespace Avalonia.Markup.Xaml.Context { +#if OMNIXAML public class AvaloniaParserFactory : IParserFactory { private readonly IRuntimeTypeSource runtimeTypeSource; @@ -38,4 +39,5 @@ namespace Avalonia.Markup.Xaml.Context return new XmlParser(phaseParserKit); } } +#endif } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaTypeFeatureProvider.cs b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaTypeFeatureProvider.cs index fcb5703f83..7828934a62 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaTypeFeatureProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaTypeFeatureProvider.cs @@ -171,7 +171,7 @@ namespace Avalonia.Markup.Xaml.Context RegisterTypeConverter(typeof(SolidColorBrush), new BrushTypeConverter()); RegisterTypeConverter(typeof(Thickness), new ThicknessTypeConverter()); RegisterTypeConverter(typeof(TimeSpan), new TimeSpanTypeConverter()); - RegisterTypeConverter(typeof(Uri), new UriTypeConverter()); + RegisterTypeConverter(typeof(Uri), new Converters.UriTypeConverter()); RegisterTypeConverter(typeof(Cursor), new CursorTypeConverter()); RegisterTypeConverter(typeof(WindowIcon), new IconTypeConverter()); RegisterTypeConverter(typeof(FontWeight), new FontWeightConverter()); diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs new file mode 100644 index 0000000000..b878b860cc --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Reflection; +using Avalonia.Markup.Xaml.Templates; +using Portable.Xaml.ComponentModel; +using avm = Avalonia.Metadata; +using pm = Portable.Xaml.Markup; + +namespace Avalonia.Markup.Xaml.PortableXaml +{ + public class AvaloniaMemberAttributeProvider : ICustomAttributeProvider + { + public AvaloniaMemberAttributeProvider(MemberInfo info) + { + _info = info; + } + + public object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } + + public object[] GetCustomAttributes(Type attributeType, bool inherit) + { + object[] result = null; + + if (attributeType == typeof(pm.XamlDeferLoadAttribute)) + { + var attr = GetXamlDeferLoadAttribute(inherit); + + if (attr != null) + { + result = new object[] { attr }; + } + } + + if (result == null || result.Length == 0) + { + var attr = _info.GetCustomAttributes(attributeType, inherit); + return (attr as object[]) ?? attr.ToArray(); + } + else + { + return result; + } + } + + public bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + private readonly MemberInfo _info; + + private Attribute GetXamlDeferLoadAttribute(bool inherit) + { + var result = _info.GetCustomAttributes(typeof(avm.TemplateContentAttribute), inherit) + .Cast() + .FirstOrDefault(); + + if (result == null) + { + return null; + } + + return new pm.XamlDeferLoadAttribute(typeof(TemplateLoader), typeof(TemplateContent)); + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaNameScope.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaNameScope.cs new file mode 100644 index 0000000000..6f855bafa1 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaNameScope.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.PortableXaml +{ + internal class AvaloniaNameScope : Portable.Xaml.Markup.INameScope + { + public object Instance { get; set; } + + private Dictionary _names = new Dictionary(); + + public object FindName(string name) + { + object result; + if (_names.TryGetValue(name, out result)) + return result; + return null; + } + + public void RegisterName(string name, object scopedElement) + { + if (scopedElement != null) + _names.Add(name, scopedElement); + + //TODO: ??? + //var control = scopedElement as Control; + + //if (control != null) + //{ + // var nameScope = (Instance as INameScope) ?? control.FindNameScope(); + + // if (nameScope != null) + // { + // nameScope.Register(name, scopedElement); + // } + //} + } + + public void UnregisterName(string name) + { + } + + public void RegisterOnNameScope(object target) + { + var nameScope = target as INameScope; + + if (nameScope != null) + { + foreach (var v in _names) + { + nameScope.Register(v.Key, v.Value); + } + } + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs new file mode 100644 index 0000000000..b6ffac69ab --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs @@ -0,0 +1,147 @@ +// 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.Generic; +using System.Linq; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.Templates; +using Avalonia.Media; +using Avalonia.Metadata; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace Avalonia.Markup.Xaml.Context +{ + using ClrNamespaceInfo = Tuple; + + public interface IRuntimeTypeProvider + { + Type FindType(string xamlNamespace, string name, Type[] genArgs); + + IEnumerable ReferencedAssemblies { get; } + } + + public class AvaloniaRuntimeTypeProvider : IRuntimeTypeProvider + { + private const string ClrNamespace = "clr-namespace:"; + // private const string AvaloniaNs = "https://github.com/avaloniaui"; + + private static readonly IEnumerable ForcedAssemblies = new[] + { + typeof(AvaloniaObject).GetTypeInfo().Assembly, + typeof(Control).GetTypeInfo().Assembly, + typeof(Style).GetTypeInfo().Assembly, + typeof(DataTemplate).GetTypeInfo().Assembly, + typeof(SolidColorBrush).GetTypeInfo().Assembly, + typeof(IValueConverter).GetTypeInfo().Assembly, + }; + + private Dictionary> _namespaces = new Dictionary>(); + + private List _scanned = new List(); + + public IEnumerable ReferencedAssemblies => _scanned; + + public AvaloniaRuntimeTypeProvider() + { + ScanAssemblies(ForcedAssemblies); + ScanNewAssemblies(); + } + + private static bool IsClrNamespace(string ns) + { + return ns.StartsWith(ClrNamespace); + } + + private static Assembly GetAssembly(string assemblyName) + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + + private void ScanAssemblies(IEnumerable assemblies) + { + foreach (var assembly in assemblies) + { + var namespaces = assembly.GetCustomAttributes() + .Select(x => new { x.XmlNamespace, x.ClrNamespace }) + .GroupBy(x => x.XmlNamespace); + + foreach (var nsa in namespaces) + { + HashSet reg; + + if (!_namespaces.TryGetValue(nsa.Key, out reg)) + { + _namespaces[nsa.Key] = reg = new HashSet>(); + } + + foreach (var child in nsa) + { + reg.Add(new ClrNamespaceInfo(child.ClrNamespace, assembly)); + } + } + + _scanned.Add(assembly); + } + } + + private void ScanNewAssemblies() + { + IEnumerable assemblies = AvaloniaLocator.Current + .GetService() + ?.GetLoadedAssemblies(); + + if (assemblies != null) + { + assemblies = assemblies.Except(_scanned); + ScanAssemblies(assemblies); + } + } + + private Dictionary _typeCache = new Dictionary(); + + public Type FindType(string xamlNamespace, string name, Type[] genArgs) + { + if (IsClrNamespace(xamlNamespace)) + { + //we need to handle only xaml url namespaces for avalonia, + //the other namespaces are handled well in portable.xaml + return null; + } + + string key = $"{xamlNamespace}:{name}"; + + Type type; + + if (_typeCache.TryGetValue(key, out type)) + { + return type; + } + + HashSet reg; + + if (!_namespaces.TryGetValue(xamlNamespace, out reg)) + { + return null; + } + + if (genArgs != null) + name += "`" + genArgs.Length; + + foreach (var ns in reg) + { + var n = ns.Item1 + "." + name; + var t = ns.Item2.GetType(n); + if (t != null) + { + _typeCache[key] = t; + return t; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs new file mode 100644 index 0000000000..ba470d521d --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs @@ -0,0 +1,91 @@ +// Copyright (c) The Perspex 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.Reflection; +using Avalonia.Metadata; +using Portable.Xaml.ComponentModel; +using pm = Portable.Xaml.Markup; + +namespace Avalonia.Markup.Xaml.PortableXaml +{ + internal class AvaloniaTypeAttributeProvider : ICustomAttributeProvider + { + public AvaloniaTypeAttributeProvider(Type type) + { + _type = type; + } + + public object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } + + public object[] GetCustomAttributes(Type attributeType, bool inherit) + { + if (attributeType == typeof(pm.ContentPropertyAttribute)) + { + var cp = GetContentPropertyAttribute(inherit); + + if (cp != null) + { + return new object[] { cp }; + } + } + else if (attributeType == typeof(pm.RuntimeNamePropertyAttribute)) + { + if (_namedType.IsAssignableFrom(_type.GetTypeInfo())) + { + return new object[] + { + new pm.RuntimeNamePropertyAttribute(nameof(INamed.Name)) + }; + } + } + + var attr = _type.GetTypeInfo().GetCustomAttributes(attributeType, inherit); + return (attr as object[]) ?? attr.ToArray(); + } + + public bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + private readonly TypeInfo _namedType = typeof(INamed).GetTypeInfo(); + + private readonly Type _type; + + private Attribute GetContentPropertyAttribute(bool inherit) + { + var type = _type; + + while (type != null) + { + var properties = type.GetTypeInfo().DeclaredProperties + .Where(x => x.GetCustomAttribute() != null); + string result = null; + + foreach (var property in properties) + { + if (result != null) + { + throw new Exception($"Content property defined more than once on {type}."); + } + + result = property.Name; + } + + if (result != null) + { + return new pm.ContentPropertyAttribute(result); + } + + type = inherit ? type.GetTypeInfo().BaseType : null; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs new file mode 100644 index 0000000000..287e1a140e --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs @@ -0,0 +1,101 @@ +using Portable.Xaml; + +namespace Avalonia.Markup.Xaml.PortableXaml +{ + public class AvaloniaXamlObjectWriter : XamlObjectWriter + { + public static AvaloniaXamlObjectWriter Create(XamlSchemaContext schemaContext, object instance) + { + var writerSettings = new XamlObjectWriterSettings(); + var nameScope = new AvaloniaNameScope { Instance = instance }; + writerSettings.ExternalNameScope = nameScope; + writerSettings.RegisterNamesOnExternalNamescope = true; + writerSettings.RootObjectInstance = instance; + + return new AvaloniaXamlObjectWriter(schemaContext, writerSettings, nameScope); + } + + private AvaloniaXamlObjectWriter( + XamlSchemaContext schemaContext, + XamlObjectWriterSettings settings, + AvaloniaNameScope nameScope + ) + : base(schemaContext, settings) + { + _nameScope = nameScope; + } + + protected override void OnAfterBeginInit(object value) + { + base.OnAfterBeginInit(value); + } + + protected override void OnAfterEndInit(object value) + { + base.OnAfterEndInit(value); + } + + protected override void OnAfterProperties(object value) + { + base.OnAfterProperties(value); + + //AfterEndInit is not called as it supports only + //Portable.Xaml.ComponentModel.ISupportInitialize + //and we have Avalonia.ISupportInitialize so we need some hacks + _endEditValue = value; + } + + protected override void OnBeforeProperties(object value) + { + //OnAfterBeginInit is not called as it supports only + //Portable.Xaml.ComponentModel.ISupportInitialize + //and we have Avalonia.ISupportInitialize so we need some hacks + HandleBeginInit(value); + + base.OnBeforeProperties(value); + } + + public override void WriteEndObject() + { + base.WriteEndObject(); + + //AfterEndInit is not called as it supports only + //Portable.Xaml.ComponentModel.ISupportInitialize + //and we have Avalonia.ISupportInitialize so we need some hacks + HandleEndEdit(_endEditValue); + _endEditValue = null; + } + + private object _endEditValue; + + private AvaloniaNameScope _nameScope; + + private void HandleBeginInit(object value) + { + (value as Avalonia.ISupportInitialize)?.BeginInit(); + } + + private void HandleEndEdit(object value) + { + (value as Avalonia.ISupportInitialize)?.EndInit(); + } + + private void HandleFinished() + { + if(_nameScope != null && Result != null) + { + _nameScope.RegisterOnNameScope(Result); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + HandleFinished(); + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs new file mode 100644 index 0000000000..75ca5dff37 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Avalonia.Markup.Xaml.Context; +using Portable.Xaml; +using Portable.Xaml.ComponentModel; + +namespace Avalonia.Markup.Xaml.PortableXaml +{ + internal class AvaloniaXamlSchemaContext : XamlSchemaContext + { + public AvaloniaXamlSchemaContext(IRuntimeTypeProvider typeProvider) + : base(typeProvider.ReferencedAssemblies) + { + _avaloniaTypeProvider = typeProvider; + } + + private IRuntimeTypeProvider _avaloniaTypeProvider; + + protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments) + { + XamlType type = null; + try + { + type = ResolveXamlTypeName(xamlNamespace, name, typeArguments, false); + + if (type == null) + { + type = base.GetXamlType(xamlNamespace, name, typeArguments); + } + } + catch(Exception e) + { + //TODO: log or wrap exception + throw e; + } + return type; + } + + private XamlType ResolveXamlTypeName(string xmlNamespace, string xmlLocalName, XamlType[] typeArguments, bool required) + { + Type[] genArgs = null; + if (typeArguments != null && typeArguments.Any()) + { + genArgs = typeArguments.Select(t => t?.UnderlyingType).ToArray(); + + if (genArgs.Any(t => t == null)) + { + return null; + } + } + + Type type = _avaloniaTypeProvider.FindType(xmlNamespace, xmlLocalName, genArgs); + + if (type == null) + { + return null; + } + + return GetXamlType(type); + } + + protected override ICustomAttributeProvider GetCustomAttributeProvider(Type type) + => new AvaloniaTypeAttributeProvider(type); + + protected override ICustomAttributeProvider GetCustomAttributeProvider(MemberInfo member) + => new AvaloniaMemberAttributeProvider(member); + } +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs index 1feaed1b27..a1c77450e8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs @@ -13,10 +13,14 @@ namespace Avalonia.Markup.Xaml.Templates { public Type DataType { get; set; } + //we need content to be object otherwise portable.xaml is crashing [Content] - public TemplateContent Content { get; set; } + [TemplateContent] + public object Content { get; set; } - public bool SupportsRecycling => true; + private TemplateContent TemplateContent => (TemplateContent)Content; + + public bool SupportsRecycling { get; set; } public bool Match(object data) { @@ -30,6 +34,6 @@ namespace Avalonia.Markup.Xaml.Templates } } - public IControl Build(object data) => Content.Load(); + public IControl Build(object data) => TemplateContent.Load(); } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 325b9bf53f..695661a623 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -2,13 +2,38 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; -using OmniXaml; -using OmniXaml.ObjectAssembler; using Avalonia.Controls; using Avalonia.Markup.Xaml.Context; namespace Avalonia.Markup.Xaml.Templates { +#if !OMNIXAML + using Portable.Xaml; + + public class TemplateContent + { + public TemplateContent() + { + } + + public TemplateContent(XamlReader reader) + { + List = new XamlNodeList(reader.SchemaContext); + XamlServices.Transform(reader, List.Writer); + } + + public XamlNodeList List { get; set; } + + public IControl Load() + { + return (IControl)XamlServices.Load(List.GetReader()); + } + } +#else + + using OmniXaml; + using OmniXaml.ObjectAssembler; + public class TemplateContent { private readonly IEnumerable nodes; @@ -34,4 +59,6 @@ namespace Avalonia.Markup.Xaml.Templates return (Control)assembler.Result; } } + +#endif } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs index 75f0329629..0f67615527 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs @@ -2,10 +2,29 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Collections.Generic; -using OmniXaml; namespace Avalonia.Markup.Xaml.Templates { +#if !OMNIXAML + using Portable.Xaml; + using System; + + public class TemplateLoader : XamlDeferringLoader + { + public override object Load(XamlReader xamlReader, IServiceProvider serviceProvider) + { + return new TemplateContent(xamlReader); + } + + public override XamlReader Save(object value, IServiceProvider serviceProvider) + { + return ((TemplateContent)value).List.GetReader(); + } + } +#else + + using OmniXaml; + public class TemplateLoader : IDeferredLoader { public object Load(IEnumerable nodes, IRuntimeTypeSource runtimeTypeSource) @@ -13,4 +32,6 @@ namespace Avalonia.Markup.Xaml.Templates return new TemplateContent(nodes, runtimeTypeSource); } } + +#endif } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/packages.config b/src/Markup/Avalonia.Markup.Xaml/packages.config index 7a9353bb05..89e19db1dc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/packages.config +++ b/src/Markup/Avalonia.Markup.Xaml/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 5826346fc9..50d5886e10 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -114,6 +114,14 @@ + + ..\..\..\packages\System.ComponentModel.Primitives.4.3.0\lib\netstandard1.0\System.ComponentModel.Primitives.dll + True + + + ..\..\..\packages\System.ComponentModel.TypeConverter.4.3.0\lib\netstandard1.0\System.ComponentModel.TypeConverter.dll + True + ..\..\..\packages\System.Reactive.Core.3.0.0\lib\netstandard1.1\System.Reactive.Core.dll True @@ -134,6 +142,9 @@ + + +