diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexParserFactory.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexParserFactory.cs index 405beda838..00aaa8f828 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexParserFactory.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexParserFactory.cs @@ -3,12 +3,11 @@ using OmniXaml; using OmniXaml.ObjectAssembler; +using OmniXaml.Parsers.Parser; using OmniXaml.Parsers.ProtoParser; namespace Perspex.Markup.Xaml.Context { - using OmniXaml.Parsers.Parser; - public class PerspexParserFactory : IParserFactory { private readonly IRuntimeTypeSource runtimeTypeSource; diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexRuntimeTypeSource.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexRuntimeTypeSource.cs index 1fffb83814..f27aa226d8 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexRuntimeTypeSource.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexRuntimeTypeSource.cs @@ -25,104 +25,17 @@ namespace Perspex.Markup.Xaml.Context public class PerspexRuntimeTypeSource : IRuntimeTypeSource { - private static readonly IEnumerable ForcedAssemblies = new[] - { - typeof(PerspexObject).GetTypeInfo().Assembly, - typeof(Control).GetTypeInfo().Assembly, - typeof(Style).GetTypeInfo().Assembly, - typeof(DataTemplate).GetTypeInfo().Assembly, - typeof(SolidColorBrush).GetTypeInfo().Assembly, - typeof(IValueConverter).GetTypeInfo().Assembly, - }; - private readonly RuntimeTypeSource inner; public PerspexRuntimeTypeSource(ITypeFactory typeFactory) { var namespaceRegistry = new PerspexNamespaceRegistry(); - var featureProvider = new TypeFeatureProvider(GetConverterProvider()); - LoadFeatureProvider(featureProvider); + var featureProvider = new PerspexTypeFeatureProvider(); var typeRepository = new PerspexTypeRepository(namespaceRegistry, typeFactory, featureProvider); inner = new RuntimeTypeSource(typeRepository, namespaceRegistry); } - private static IEnumerable ScannedAssemblies - { - get - { - var platform = PerspexLocator.Current.GetService(); - - if (platform != null) - { - return ForcedAssemblies.Concat(platform.GetLoadedAssemblies()).Distinct(); - } - else - { - return ForcedAssemblies; - } - } - } - - private void LoadFeatureProvider(ITypeFeatureProvider featureProvider) - { - featureProvider.RegisterMetadata(new GenericMetadata().WithRuntimeNameProperty(d => d.Name)); - featureProvider.RegisterMetadata(new GenericMetadata().WithMemberDependency(x => x.Value, x => x.Property)); - featureProvider.RegisterMetadata( - new GenericMetadata() - .WithMemberDependency(x => x.SelectedIndex, x => x.Items) - .WithMemberDependency(x => x.SelectedItem, x => x.Items)); - - RegisteContentProperties(featureProvider); - } - - private static void RegisteContentProperties(ITypeFeatureProvider featureProvider) - { - var typeAndAttribute = from type in ScannedAssemblies.AllExportedTypes() - let properties = type.GetTypeInfo().DeclaredProperties - from property in properties - let attr = property.GetCustomAttribute() - where attr != null - select new { Type = type, Property = property, ContentAttribute = attr }; - - foreach (var t in typeAndAttribute) - { - featureProvider.RegisterMetadata(t.Type, new Metadata {ContentProperty = t.Property.Name}); - } - } - - private static ITypeConverterProvider GetConverterProvider() - { - var typeConverterProvider = new TypeConverterProvider(); - var converters = new[] - { - new TypeConverterRegistration(typeof(IBitmap), new BitmapTypeConverter()), - new TypeConverterRegistration(typeof(Brush), new BrushTypeConverter()), - new TypeConverterRegistration(typeof(Color), new ColorTypeConverter()), - new TypeConverterRegistration(typeof(Classes), new ClassesTypeConverter()), - new TypeConverterRegistration(typeof(ColumnDefinitions), new ColumnDefinitionsTypeConverter()), - new TypeConverterRegistration(typeof(Geometry), new GeometryTypeConverter()), - new TypeConverterRegistration(typeof(GridLength), new GridLengthTypeConverter()), - new TypeConverterRegistration(typeof(KeyGesture), new KeyGestureConverter()), - new TypeConverterRegistration(typeof(PerspexList), new PerspexListTypeConverter()), - new TypeConverterRegistration(typeof(IMemberSelector), new MemberSelectorTypeConverter()), - new TypeConverterRegistration(typeof(Point), new PointTypeConverter()), - new TypeConverterRegistration(typeof(IList), new PointsListTypeConverter()), - new TypeConverterRegistration(typeof(PerspexProperty), new PerspexPropertyTypeConverter()), - new TypeConverterRegistration(typeof(RelativePoint), new RelativePointTypeConverter()), - new TypeConverterRegistration(typeof(RelativeRect), new RelativeRectTypeConverter()), - new TypeConverterRegistration(typeof(RowDefinitions), new RowDefinitionsTypeConverter()), - new TypeConverterRegistration(typeof(Selector), new SelectorTypeConverter()), - new TypeConverterRegistration(typeof(Thickness), new ThicknessTypeConverter()), - new TypeConverterRegistration(typeof(TimeSpan), new TimeSpanTypeConverter()), - new TypeConverterRegistration(typeof(Uri), new UriTypeConverter()), - new TypeConverterRegistration(typeof(Cursor), new CursorTypeConverter()) - }; - - typeConverterProvider.AddAll(converters); - return typeConverterProvider; - } - public Namespace GetNamespace(string name) { return inner.GetNamespace(name); @@ -145,7 +58,6 @@ namespace Perspex.Markup.Xaml.Context public IEnumerable RegisteredPrefixes => inner.RegisteredPrefixes; - public XamlType GetByType(Type type) { return inner.GetByType(type); diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs new file mode 100644 index 0000000000..25b3189da5 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeFeatureProvider.cs @@ -0,0 +1,175 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Reflection; +using OmniXaml; +using OmniXaml.Builder; +using OmniXaml.TypeConversion; +using OmniXaml.TypeConversion.BuiltInConverters; +using OmniXaml.Typing; +using Perspex.Collections; +using Perspex.Controls; +using Perspex.Controls.Templates; +using Perspex.Input; +using Perspex.Markup.Xaml.Converters; +using Perspex.Media; +using Perspex.Media.Imaging; +using Perspex.Metadata; +using Perspex.Styling; +using OmniMetadata = OmniXaml.Typing.Metadata; + +namespace Perspex.Markup.Xaml.Context +{ + public class PerspexTypeFeatureProvider : ITypeFeatureProvider + { + private Dictionary _metadata = + new Dictionary(); + + private Dictionary _typeConverters = + new Dictionary(); + + public IEnumerable TypeConverters => _typeConverters.Values; + + public PerspexTypeFeatureProvider() + { + RegisterTypeConverters(); + } + + public string GetContentPropertyName(Type type) + { + return GetMetadata(type)?.ContentProperty; + } + + public OmniMetadata GetMetadata(Type type) + { + OmniMetadata result; + + if (!_metadata.TryGetValue(type, out result)) + { + result = LoadMetadata(type); + _metadata.Add(type, result); + } + + return result; + } + + public ITypeConverter GetTypeConverter(Type type) + { + TypeConverterRegistration result; + _typeConverters.TryGetValue(type, out result); + return result?.TypeConverter; + } + + public void RegisterMetadata(Type type, OmniMetadata metadata) + { + _metadata.Add(type, metadata); + } + + public void RegisterTypeConverter(Type type, ITypeConverter converter) + { + _typeConverters.Add(type, new TypeConverterRegistration(type, converter)); + } + + private static OmniMetadata LoadMetadata(Type type) + { + return new OmniMetadata + { + ContentProperty = GetContentProperty(type), + PropertyDependencies = GetPropertyDependencies(type), + RuntimePropertyName = type.GetRuntimeProperty("Name")?.Name, + }; + } + + private static string GetContentProperty(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 result; + } + + type = type.GetTypeInfo().BaseType; + } + + return null; + } + + private static DependencyRegistrations GetPropertyDependencies(Type type) + { + var result = new List(); + + while (type != null) + { + var registrations = type.GetTypeInfo().DeclaredProperties + .Select(x => new + { + Property = x.Name, + Attributes = x.GetCustomAttributes(), + }) + .Where(x => x.Attributes.Any()) + .SelectMany(x => x.Attributes.Select(y => new DependencyRegistration(x.Property, y.Name))); + + result.AddRange(registrations); + type = type.GetTypeInfo().BaseType; + } + + return result.Count > 0 ? new DependencyRegistrations(result) : null; + } + + private void RegisterTypeConverters() + { + // HACK: For now these are hard-coded. Hopefully when the .NET Standard Platform + // is available we can use the System.ComponentModel.TypeConverters so don't want to + // spend time for now inventing a mechanism to register type converters if it's all + // going to change. + RegisterTypeConverter(typeof(string), new StringTypeConverter()); + RegisterTypeConverter(typeof(int), new IntTypeConverter()); + RegisterTypeConverter(typeof(long), new IntTypeConverter()); + RegisterTypeConverter(typeof(short), new IntTypeConverter()); + RegisterTypeConverter(typeof(double), new DoubleTypeConverter()); + RegisterTypeConverter(typeof(float), new IntTypeConverter()); + RegisterTypeConverter(typeof(bool), new BooleanConverter()); + RegisterTypeConverter(typeof(Type), new TypeTypeConverter()); + + RegisterTypeConverter(typeof(IBitmap), new BitmapTypeConverter()); + RegisterTypeConverter(typeof(Brush), new BrushTypeConverter()); + RegisterTypeConverter(typeof(Color), new ColorTypeConverter()); + RegisterTypeConverter(typeof(Classes), new ClassesTypeConverter()); + RegisterTypeConverter(typeof(ColumnDefinitions), new ColumnDefinitionsTypeConverter()); + RegisterTypeConverter(typeof(Geometry), new GeometryTypeConverter()); + RegisterTypeConverter(typeof(GridLength), new GridLengthTypeConverter()); + RegisterTypeConverter(typeof(KeyGesture), new KeyGestureConverter()); + RegisterTypeConverter(typeof(PerspexList), new PerspexListTypeConverter()); + RegisterTypeConverter(typeof(IMemberSelector), new MemberSelectorTypeConverter()); + RegisterTypeConverter(typeof(Point), new PointTypeConverter()); + RegisterTypeConverter(typeof(IList), new PointsListTypeConverter()); + RegisterTypeConverter(typeof(PerspexProperty), new PerspexPropertyTypeConverter()); + RegisterTypeConverter(typeof(RelativePoint), new RelativePointTypeConverter()); + RegisterTypeConverter(typeof(RelativeRect), new RelativeRectTypeConverter()); + RegisterTypeConverter(typeof(RowDefinitions), new RowDefinitionsTypeConverter()); + RegisterTypeConverter(typeof(Selector), new SelectorTypeConverter()); + RegisterTypeConverter(typeof(Thickness), new ThicknessTypeConverter()); + RegisterTypeConverter(typeof(TimeSpan), new TimeSpanTypeConverter()); + RegisterTypeConverter(typeof(Uri), new UriTypeConverter()); + RegisterTypeConverter(typeof(Cursor), new CursorTypeConverter()); + } + } +} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 2b28639da1..5ed61489b4 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 2b28639da15e5f43f4b713e0b115d1bb41679cc6 +Subproject commit 5ed61489b4db4578680004cd6ddbe811cdd91f8f diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index eef6776375..922fd20c41 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Perspex.Base/Metadata/DependsOnAttribute.cs b/src/Perspex.Base/Metadata/DependsOnAttribute.cs new file mode 100644 index 0000000000..d06649e9e9 --- /dev/null +++ b/src/Perspex.Base/Metadata/DependsOnAttribute.cs @@ -0,0 +1,30 @@ +// 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; + +namespace Perspex.Metadata +{ + /// + /// Indicates that the property depends on the value of another property in markup. + /// + [AttributeUsage(AttributeTargets.Property)] + public class DependsOnAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the property that this property depends on. + /// + public DependsOnAttribute(string propertyName) + { + Name = propertyName; + } + + /// + /// Gets the name of the property that this property depends on. + /// + public string Name { get; } + } +} diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index aab8ea1043..e7d9b3d405 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -55,6 +55,7 @@ +