From: WonYoung Choi Date: Thu, 30 May 2019 09:20:23 +0000 (+0900) Subject: [NUI] Split NUI Assemblies (#865) X-Git-Tag: submit/tizen/20190531.005157~1^2~5 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=59970b38bb97cee7b6ff8afb9388996c022c9010;p=platform%2Fcore%2Fcsapi%2Ftizenfx.git [NUI] Split NUI Assemblies (#865) * Split Tizen.NUI (#848) * Split Tizen.NUI (#848) (#854) * Fix compile error (#857) * Split Tizen.NUI (#848) * [NUI]Fix compile error * Fix API break (#859) * Split Tizen.NUI (#848) * [NUI]Fix compile error * [NUI]Fix API break * [NUI]Fix API break * [Build] Update APITool * Fix build error of Tizen.NUI.Design (#863) * [NUI] Fix NUI version (#842) Signed-off-by: huiyu.eun * [NUI] Add Registry return (#831) Signed-off-by: huiyu.eun * [NUI] Xaml intellisense support (#853) * [NUI] Add XAML intellisense support (#834) I merge this PR to xaml-support branch first to fix some build and packaging files with other commits. After works in xaml-support branch, I will merge it to master later. * [Build] Add Tizen.NUI.Design and dependent runtime assemblies to nuget * [Build] Add dummy assemblies to nuget instead of reference assemblies * Update .travis.yml * [VoiceControlManager][TCSACR-213] Add VoiceControlManager APIs (#841) * Add Tizen.Uix.VoiceControlManager Signed-off-by: sungrae jo * [VoiceControlManager] Modified code by API review * [MediaContent] Deprecate filter keyword (#832) * [Build] Resolve conflict APITool with master branch * [NUI]Fix the API break (Tizen.NUI.BaseHandle) * [NUI]Fix PR check warning --- diff --git a/src/Tizen.NUI.Design/Tizen.NUI.Design.csproj b/src/Tizen.NUI.Design/Tizen.NUI.Design.csproj index 4b9e740f9..da1aa4c14 100755 --- a/src/Tizen.NUI.Design/Tizen.NUI.Design.csproj +++ b/src/Tizen.NUI.Design/Tizen.NUI.Design.csproj @@ -6,11 +6,11 @@ - + - + diff --git a/src/Tizen.NUI.Design/Tizen.NUI.Design.sln b/src/Tizen.NUI.Design/Tizen.NUI.Design.sln index 9ee267a3f..2d817695e 100755 --- a/src/Tizen.NUI.Design/Tizen.NUI.Design.sln +++ b/src/Tizen.NUI.Design/Tizen.NUI.Design.sln @@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen", "..\Tizen\Tizen.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Log", "..\Tizen.Log\Tizen.Log.csproj", "{837994B4-B99D-4EFE-B6FF-1BE17EE78711}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI.Xaml", "..\Tizen.NUI.Xaml\Tizen.NUI.Xaml.csproj", "{7940BB09-D1C6-45BC-A220-723F823C3DFB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -96,6 +98,18 @@ Global {837994B4-B99D-4EFE-B6FF-1BE17EE78711}.Release|x64.Build.0 = Release|Any CPU {837994B4-B99D-4EFE-B6FF-1BE17EE78711}.Release|x86.ActiveCfg = Release|Any CPU {837994B4-B99D-4EFE-B6FF-1BE17EE78711}.Release|x86.Build.0 = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|x64.Build.0 = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|x86.ActiveCfg = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Debug|x86.Build.0 = Debug|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|Any CPU.Build.0 = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|x64.ActiveCfg = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|x64.Build.0 = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|x86.ActiveCfg = Release|Any CPU + {7940BB09-D1C6-45BC-A220-723F823C3DFB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Tizen.NUI.Design/Tizen.NUI.Design/AttributeTableBuilder.cs b/src/Tizen.NUI.Design/Tizen.NUI.Design/AttributeTableBuilder.cs index 3422de831..09c0cd491 100755 --- a/src/Tizen.NUI.Design/Tizen.NUI.Design/AttributeTableBuilder.cs +++ b/src/Tizen.NUI.Design/Tizen.NUI.Design/AttributeTableBuilder.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using Microsoft.Windows.Design; using Tizen.NUI.BaseComponents; -using Tizen.NUI.Binding; +using Tizen.NUI.XamlBinding; namespace Tizen.NUI.Design { diff --git a/src/Tizen.NUI.Design/Tizen.NUI/AttributeTableBuilder.cs b/src/Tizen.NUI.Design/Tizen.NUI/AttributeTableBuilder.cs index 94e9a440c..77a8fd815 100755 --- a/src/Tizen.NUI.Design/Tizen.NUI/AttributeTableBuilder.cs +++ b/src/Tizen.NUI.Design/Tizen.NUI/AttributeTableBuilder.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Reflection; using Tizen.NUI.BaseComponents; using Tizen.NUI.UIComponents; +using Tizen.NUI.Xaml; namespace Tizen.NUI { diff --git a/src/Tizen.NUI.Xaml/Properties/AssemblyInfo.cs b/src/Tizen.NUI.Xaml/Properties/AssemblyInfo.cs new file mode 100755 index 000000000..3c78009c8 --- /dev/null +++ b/src/Tizen.NUI.Xaml/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Runtime.CompilerServices; + +using Tizen.NUI.XamlBinding; +using Tizen.NUI.Xaml; + +[assembly: XamlCompilationAttribute(XamlCompilationOptions.Compile)] + +[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI")] +[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI.Xaml.Forms.BaseComponents")] +[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI.UIComponents")] +[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI.Xaml")] +[assembly: XmlnsDefinition("http://tizen.org/Tizen.NUI/2018/XAML", "Tizen.NUI.XamlBinding")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Tizen.NUI.Xaml")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "Tizen.NUI.Xaml")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] diff --git a/src/Tizen.NUI.Xaml/Properties/GlobalAssemblyInfo.cs b/src/Tizen.NUI.Xaml/Properties/GlobalAssemblyInfo.cs new file mode 100755 index 000000000..10ce69d28 --- /dev/null +++ b/src/Tizen.NUI.Xaml/Properties/GlobalAssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; + +//[assembly: AssemblyCompany("Tizen.NUI.Xaml Inc.")] +//[assembly: AssemblyProduct("Tizen.NUI.Xaml")] +[assembly: AssemblyCopyright("Copyright ?Xamarin Inc. 2013-2017")] +[assembly: AssemblyTrademark("")] diff --git a/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.csproj b/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.csproj new file mode 100755 index 000000000..14c6f107f --- /dev/null +++ b/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + + + + + + + + + + TizenSystemSettings + + + + + \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.sln b/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.sln new file mode 100755 index 000000000..24d4d36fe --- /dev/null +++ b/src/Tizen.NUI.Xaml/Tizen.NUI.Xaml.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI.Xaml", "Tizen.NUI.Xaml.csproj", "{473C3BEC-2F67-4285-85FC-BF4E96BFFF1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.System.SystemSettings", "..\Tizen.System.SystemSettings\Tizen.System.SystemSettings.csproj", "{8D71B1B6-9901-436F-8914-9F812E1B10A7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen", "..\Tizen\Tizen.csproj", "{33B7EFD5-0050-416D-A2D1-8F18F26F106D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Log", "..\Tizen.Log\Tizen.Log.csproj", "{1C550C1F-9370-42FF-86FA-EDCD6262B258}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.Applications.Common", "..\Tizen.Applications.Common\Tizen.Applications.Common.csproj", "{2AEDCAA7-543F-48A1-BEA3-CF3E14F6EDC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI", "..\Tizen.NUI\Tizen.NUI.csproj", "{B5CEBFFC-3355-408A-8F99-3DE922271F72}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {473C3BEC-2F67-4285-85FC-BF4E96BFFF1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {473C3BEC-2F67-4285-85FC-BF4E96BFFF1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {473C3BEC-2F67-4285-85FC-BF4E96BFFF1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {473C3BEC-2F67-4285-85FC-BF4E96BFFF1C}.Release|Any CPU.Build.0 = Release|Any CPU + {8D71B1B6-9901-436F-8914-9F812E1B10A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D71B1B6-9901-436F-8914-9F812E1B10A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D71B1B6-9901-436F-8914-9F812E1B10A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D71B1B6-9901-436F-8914-9F812E1B10A7}.Release|Any CPU.Build.0 = Release|Any CPU + {33B7EFD5-0050-416D-A2D1-8F18F26F106D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33B7EFD5-0050-416D-A2D1-8F18F26F106D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33B7EFD5-0050-416D-A2D1-8F18F26F106D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33B7EFD5-0050-416D-A2D1-8F18F26F106D}.Release|Any CPU.Build.0 = Release|Any CPU + {1C550C1F-9370-42FF-86FA-EDCD6262B258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C550C1F-9370-42FF-86FA-EDCD6262B258}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C550C1F-9370-42FF-86FA-EDCD6262B258}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C550C1F-9370-42FF-86FA-EDCD6262B258}.Release|Any CPU.Build.0 = Release|Any CPU + {2AEDCAA7-543F-48A1-BEA3-CF3E14F6EDC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AEDCAA7-543F-48A1-BEA3-CF3E14F6EDC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AEDCAA7-543F-48A1-BEA3-CF3E14F6EDC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AEDCAA7-543F-48A1-BEA3-CF3E14F6EDC2}.Release|Any CPU.Build.0 = Release|Any CPU + {B5CEBFFC-3355-408A-8F99-3DE922271F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5CEBFFC-3355-408A-8F99-3DE922271F72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5CEBFFC-3355-408A-8F99-3DE922271F72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5CEBFFC-3355-408A-8F99-3DE922271F72}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F532FBB4-80FB-46CE-A5EC-4E1333BF2622} + EndGlobalSection +EndGlobal diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ApplyPropertiesVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ApplyPropertiesVisitor.cs new file mode 100755 index 000000000..bae2bde18 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ApplyPropertiesVisitor.cs @@ -0,0 +1,714 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml; +using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI.XamlBinding; +using Tizen.NUI.StyleSheets; + +using static System.String; + +namespace Tizen.NUI.Xaml +{ + internal class ApplyPropertiesVisitor : IXamlNodeVisitor + { + public static readonly IList Skips = new List { + XmlName.xKey, + XmlName.xTypeArguments, + XmlName.xArguments, + XmlName.xFactoryMethod, + XmlName.xName, + XmlName.xDataType + }; + + public ApplyPropertiesVisitor(HydrationContext context, bool stopOnResourceDictionary = false) + { + Context = context; + StopOnResourceDictionary = stopOnResourceDictionary; + } + + Dictionary Values => Context.Values; + HydrationContext Context { get; } + + public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp; + public bool StopOnDataTemplate => true; + public bool StopOnResourceDictionary { get; } + public bool VisitNodeOnDataTemplate => true; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]); + + public void Visit(ValueNode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + var value = Values [node]; + var source = Values [parentNode]; + XmlName propertyName; + + if (TryGetPropertyName(node, parentNode, out propertyName)) { + if (TrySetRuntimeName(propertyName, source, value, node)) + return; + if (Skips.Contains(propertyName)) + return; + if (parentElement.SkipProperties.Contains(propertyName)) + return; + if (propertyName.Equals(XamlParser.McUri, "Ignorable")) + return; + SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node); + } else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) { + // Collection element, implicit content, or implicit collection element. + var contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo()); + if (contentProperty != null) { + var name = new XmlName(((ElementNode)parentNode).NamespaceURI, contentProperty); + if (Skips.Contains(name)) + return; + if (parentElement.SkipProperties.Contains(propertyName)) + return; + SetPropertyValue(source, name, value, Context.RootElement, node, Context, node); + } + } + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + XmlName propertyName; + if (TryGetPropertyName(node, parentNode, out propertyName) && propertyName == XmlName._CreateContent) { + var s0 = Values[parentNode]; + if (s0 is ElementTemplate) { + SetTemplate(s0 as ElementTemplate, node); + return; + } + } + + var parentElement = parentNode as IElementNode; + propertyName = XmlName.Empty; + + //Simplify ListNodes with single elements + var pList = parentNode as ListNode; + if (pList != null && pList.CollectionItems.Count == 1) { + propertyName = pList.XmlName; + parentNode = parentNode.Parent; + parentElement = parentNode as IElementNode; + } + + var value = Values[node]; + + if (propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName)) { + if (Skips.Contains(propertyName)) + return; + if (parentElement == null) + return; + if (parentElement.SkipProperties.Contains(propertyName)) + return; + + var source = Values[parentNode]; + ProvideValue(ref value, node, source, propertyName); + SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node); + } + else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) { + var source = Values[parentNode]; + ProvideValue(ref value, node, source, XmlName.Empty); + string contentProperty; + Exception xpe = null; + var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null; + + //ResourceDictionary + if (xpe == null && TryAddToResourceDictionary(source as ResourceDictionary, value, xKey, node, out xpe)) + return; + + // Collection element, implicit content, or implicit collection element. + if (xpe == null && typeof(IEnumerable).IsAssignableFrom(Context.Types[parentElement]) && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) { + var addMethod = + Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1); + + addMethod?.Invoke(source, new[] { value }); + return; + } + if (xpe == null && (contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo())) != null) { + var name = new XmlName(node.NamespaceURI, contentProperty); + if (Skips.Contains(name)) + return; + if (parentElement.SkipProperties.Contains(propertyName)) + return; + + SetPropertyValue(source, name, value, Context.RootElement, node, Context, node); + return; + } + if (xpe == null && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) + { + //if there are similar parameters in the function, this will exist issue. + var addMethod = Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1); + if(addMethod != null) addMethod.Invoke(source, new[] { value }); + return; + } + xpe = xpe ?? new XamlParseException($"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute", node); + if (Context.ExceptionHandler != null) + Context.ExceptionHandler(xpe); + throw xpe; + } + else if (IsCollectionItem(node, parentNode) && parentNode is ListNode) { + var source = Values[parentNode.Parent]; + ProvideValue(ref value, node, source, XmlName.Empty); + var parentList = (ListNode)parentNode; + if (Skips.Contains(parentList.XmlName)) + return; + Exception xpe = null; + var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null; + + object _; + var collection = GetPropertyValue(source, parentList.XmlName, Context, parentList, out _) as IEnumerable; + if (collection == null) + xpe = new XamlParseException($"Property {parentList.XmlName.LocalName} is null or is not IEnumerable", node); + + if (xpe == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, node, out xpe)) + return; + + MethodInfo addMethod; + if (xpe == null && (addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) != null) { + addMethod.Invoke(collection, new[] { Values[node] }); + return; + } + xpe = xpe ?? new XamlParseException($"Value of {parentList.XmlName.LocalName} does not have a Add() method", node); + if (Context.ExceptionHandler != null) + Context.ExceptionHandler(xpe); + else + throw xpe; + } + } + + + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name) + { + name = default(XmlName); + var parentElement = parentNode as IElementNode; + if (parentElement == null) + return false; + foreach (var kvp in parentElement.Properties) { + if (kvp.Value != node) + continue; + name = kvp.Key; + return true; + } + return false; + } + + internal static bool IsCollectionItem(INode node, INode parentNode) + { + var parentList = parentNode as IListNode; + if (parentList == null) + return false; + return parentList.CollectionItems.Contains(node); + } + + internal static string GetContentPropertyName(System.Reflection.TypeInfo typeInfo) + { + while (typeInfo != null) { + var propName = GetContentPropertyName(typeInfo.CustomAttributes); + if (propName != null) + return propName; + typeInfo = typeInfo?.BaseType?.GetTypeInfo(); + } + return null; + } + + void ProvideValue(ref object value, ElementNode node, object source, XmlName propertyName) + { + var markupExtension = value as IMarkupExtension; + var valueProvider = value as IValueProvider; + + if (markupExtension == null && valueProvider == null) + return; + + XamlServiceProvider serviceProvider = null; + if (value.GetType().GetTypeInfo().GetCustomAttribute() == null) + serviceProvider = new XamlServiceProvider(node, Context); + + if (serviceProvider != null && serviceProvider.IProvideValueTarget != null && propertyName != XmlName.Empty) { + ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = GetTargetProperty(source, propertyName, Context, node); + } + + if (markupExtension != null) + value = markupExtension.ProvideValue(serviceProvider); + else if (valueProvider != null) + value = valueProvider.ProvideValue(serviceProvider); + } + + static string GetContentPropertyName(IEnumerable attributes) + { + var contentAttribute = + attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName)); + if (contentAttribute == null || contentAttribute.ConstructorArguments.Count != 1) + return null; + if (contentAttribute.ConstructorArguments [0].ArgumentType == typeof(string)) + return (string)contentAttribute.ConstructorArguments [0].Value; + return null; + } + + static bool GetRealNameAndType(ref Type elementType, string namespaceURI, ref string localname, + HydrationContext context, IXmlLineInfo lineInfo) + { + var dotIdx = localname.IndexOf('.'); + if (dotIdx > 0) { + var typename = localname.Substring(0, dotIdx); + localname = localname.Substring(dotIdx + 1); + XamlParseException xpe; + elementType = XamlParser.GetElementType(new XmlType(namespaceURI, typename, null), lineInfo, + context.RootElement.GetType().GetTypeInfo().Assembly, out xpe); + + if (xpe != null) + throw xpe; + return true; + } + return false; + } + + static BindableProperty GetBindableProperty(Type elementType, string localName, IXmlLineInfo lineInfo, + bool throwOnError = false) + { +#if NETSTANDARD1_0 + var bindableFieldInfo = elementType.GetFields().FirstOrDefault(fi => fi.Name == localName + "Property"); +#else + var bindableFieldInfo = elementType.GetFields(BindingFlags.Static | BindingFlags.NonPublic|BindingFlags.FlattenHierarchy).FirstOrDefault(fi => fi.Name == localName + "Property"); + + if (null == bindableFieldInfo) + { + bindableFieldInfo = elementType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy).FirstOrDefault(fi => fi.Name == localName + "Property"); + } +#endif + Exception exception = null; + if (exception == null && bindableFieldInfo == null) { + exception = + new XamlParseException( + Format("BindableProperty {0} not found on {1}", localName + "Property", elementType.Name), lineInfo); + } + + if (exception == null) + return bindableFieldInfo.GetValue(null) as BindableProperty; + if (throwOnError) + throw exception; + return null; + } + + static object GetTargetProperty(object xamlelement, XmlName propertyName, HydrationContext context, IXmlLineInfo lineInfo) + { + var localName = propertyName.LocalName; + //If it's an attached BP, update elementType and propertyName + var bpOwnerType = xamlelement.GetType(); + GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo); + var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false); + + if (property != null) + return property; + + var elementType = xamlelement.GetType(); + var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName); + return propertyInfo; + } + + public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, object rootElement, INode node, HydrationContext context, IXmlLineInfo lineInfo) + { + var localName = propertyName.LocalName; + var serviceProvider = new XamlServiceProvider(node, context); + Exception xpe = null; + var xKey = node is IElementNode && ((IElementNode)node).Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)((IElementNode)node).Properties[XmlName.xKey]).Value as string : null; + + //If it's an attached BP, update elementType and propertyName + var bpOwnerType = xamlelement.GetType(); + var attached = GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo); + + var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false); + + //If the target is an event, connect + if (xpe == null && TryConnectEvent(xamlelement, localName, attached, value, rootElement, lineInfo, out xpe)) + return; + + //If Value is DynamicResource and it's a BP, SetDynamicResource + if (xpe == null && TrySetDynamicResource(xamlelement, property, value, lineInfo, out xpe)) + return; + + //If value is BindingBase, SetBinding + if (xpe == null && TrySetBinding(xamlelement, property, localName, value, lineInfo, out xpe)) + return; + + //If it's a BindableProberty, SetValue + if (xpe == null && TrySetValue(xamlelement, property, attached, value, lineInfo, serviceProvider, out xpe)) + return; + + //If we can assign that value to a normal property, let's do it + if (xpe == null && TrySetProperty(xamlelement, localName, value, lineInfo, serviceProvider, context, out xpe)) + return; + + //If it's an already initialized property, add to it + if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, xKey, lineInfo, serviceProvider, context, out xpe)) + return; + + xpe = xpe ?? new XamlParseException($"Cannot assign property \"{localName}\": Property does not exist, or is not assignable, or mismatching type between value and property", lineInfo); + if (context.ExceptionHandler != null) + context.ExceptionHandler(xpe); + else + throw xpe; + } + + public static object GetPropertyValue(object xamlElement, XmlName propertyName, HydrationContext context, IXmlLineInfo lineInfo, out object targetProperty) + { + var localName = propertyName.LocalName; + Exception xpe = null; + object value; + targetProperty = null; + + //If it's an attached BP, update elementType and propertyName + var bpOwnerType = xamlElement.GetType(); + var attached = GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo); + var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false); + + //If it's a BindableProberty, GetValue + if (xpe == null && TryGetValue(xamlElement, property, attached, out value, lineInfo, out xpe, out targetProperty)) + return value; + + //If it's a normal property, get it + if (xpe == null && TryGetProperty(xamlElement, localName, out value, lineInfo, context, out xpe, out targetProperty)) + return value; + + xpe = xpe ?? new XamlParseException($"Property {localName} is not found or does not have an accessible getter", lineInfo); + if (context.ExceptionHandler != null) + context.ExceptionHandler(xpe); + else + throw xpe; + + return null; + } + + static bool TryConnectEvent(object element, string localName, bool attached, object value, object rootElement, IXmlLineInfo lineInfo, out Exception exception) + { + exception = null; + + if (attached) + return false; + + var elementType = element.GetType(); + var eventInfo = elementType.GetRuntimeEvent(localName); + var stringValue = value as string; + + if (eventInfo == null || IsNullOrEmpty(stringValue)) + return false; + + var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value); + if (methodInfo == null) { + exception = new XamlParseException($"No method {value} found on type {rootElement.GetType()}", lineInfo); + return false; + } + + try { + eventInfo.AddEventHandler(element, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement)); + return true; + } catch (ArgumentException ae) { + exception = new XamlParseException($"Method {stringValue} does not have the correct signature", lineInfo, ae); + } + return false; + } + + static bool TrySetDynamicResource(object element, BindableProperty property, object value, IXmlLineInfo lineInfo, out Exception exception) + { + exception = null; + + var elementType = element.GetType(); + var dynamicResource = value as DynamicResource; + var bindable = element as BindableObject; + + if (dynamicResource == null || property == null) + return false; + + if (bindable == null) { + exception = new XamlParseException($"{elementType.Name} is not a BindableObject", lineInfo); + return false; + } + + bindable.SetDynamicResource(property, dynamicResource.Key); + return true; + } + + static bool TrySetBinding(object element, BindableProperty property, string localName, object value, IXmlLineInfo lineInfo, out Exception exception) + { + exception = null; + + var elementType = element.GetType(); + var binding = value.ConvertTo(typeof(BindingBase),pinfoRetriever:null,serviceProvider:null) as BindingBase; + var bindable = element as BindableObject; + var nativeBindingService = DependencyService.Get(); + + if (binding == null) + return false; + + if (bindable != null && property != null) { + bindable.SetBinding(property, binding); + return true; + } + + if (nativeBindingService != null && property != null && nativeBindingService.TrySetBinding(element, property, binding)) + return true; + + if (nativeBindingService != null && nativeBindingService.TrySetBinding(element, localName, binding)) + return true; + + if (property != null) + exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support native bindings", lineInfo); + + return false; + } + + static bool TrySetValue(object element, BindableProperty property, bool attached, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, out Exception exception) + { + exception = null; + + var elementType = element.GetType(); + var bindable = element as BindableObject; + var nativeBindingService = DependencyService.Get(); + + if (property == null) + return false; + + if (serviceProvider != null && serviceProvider.IProvideValueTarget != null) + ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = property; + + Func minforetriever; + if (attached) + minforetriever = () => property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new [] { typeof(BindableObject) }); + else + { + minforetriever = () => property.DeclaringType.GetRuntimeProperties().LastOrDefault(p => p.Name == property.PropertyName); + } + //minforetriever = () => property.DeclaringType.GetRuntimeProperty(property.PropertyName); + var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider); + + if (bindable != null) { + //SetValue doesn't throw on mismatching type, so check before to get a chance to try the property setting or the collection adding + var nullable = property.ReturnTypeInfo.IsGenericType && + property.ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>); + if ((convertedValue == null && (!property.ReturnTypeInfo.IsValueType || nullable)) || + (property.ReturnType.IsInstanceOfType(convertedValue))) { + bindable.SetValue(property, convertedValue); + return true; + } + + // This might be a collection; see if we can add to it + return TryAddValue(bindable, property, value, serviceProvider); + } + + if (nativeBindingService != null && nativeBindingService.TrySetValue(element, property, convertedValue)) + return true; + + exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support setting native BindableProperties", lineInfo); + return false; + } + + static bool TryGetValue(object element, BindableProperty property, bool attached, out object value, IXmlLineInfo lineInfo, out Exception exception, out object targetProperty) + { + exception = null; + value = null; + targetProperty = property; + var elementType = element.GetType(); + var bindable = element as BindableObject; + + if (property == null) + return false; + + if (bindable == null) + return false; + + value = bindable.GetValue(property); + return true; + } + + static bool TrySetProperty(object element, string localName, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception) + { + exception = null; + + var elementType = element.GetType(); + var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName); + MethodInfo setter; + if (propertyInfo == null || !propertyInfo.CanWrite || (setter = propertyInfo.SetMethod) == null) + return false; + + if (!IsVisibleFrom(setter, context.RootElement)) + return false; + + if (serviceProvider != null && serviceProvider.IProvideValueTarget != null) + ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = propertyInfo; + + object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider); + if (convertedValue != null && !propertyInfo.PropertyType.IsInstanceOfType(convertedValue)) + return false; + + setter.Invoke(element, new object [] { convertedValue }); + return true; + } + + static bool TryGetProperty(object element, string localName, out object value, IXmlLineInfo lineInfo, HydrationContext context, out Exception exception, out object targetProperty) + { + exception = null; + value = null; + var elementType = element.GetType(); + PropertyInfo propertyInfo = null; + try { + propertyInfo = elementType.GetRuntimeProperty(localName); + } catch (AmbiguousMatchException) { + // Get most derived instance of property + foreach (var property in elementType.GetRuntimeProperties().Where(prop => prop.Name == localName)) { + if (propertyInfo == null || propertyInfo.DeclaringType.IsAssignableFrom(property.DeclaringType)) + propertyInfo = property; + } + } + MethodInfo getter; + targetProperty = propertyInfo; + if (propertyInfo == null || !propertyInfo.CanRead || (getter = propertyInfo.GetMethod) == null) + return false; + + if (!IsVisibleFrom(getter, context.RootElement)) + return false; + + value = getter.Invoke(element, new object[] { }); + return true; + } + + static bool IsVisibleFrom(MethodInfo method, object rootElement) + { + if (method.IsPublic) + return true; + if (method.IsPrivate && method.DeclaringType == rootElement.GetType()) + return true; + if ((method.IsAssembly || method.IsFamilyOrAssembly) && method.DeclaringType.AssemblyQualifiedName == rootElement.GetType().AssemblyQualifiedName) + return true; + if (method.IsFamily && method.DeclaringType.IsAssignableFrom(rootElement.GetType())) + return true; + return false; + } + + static bool TryAddToProperty(object element, XmlName propertyName, object value, string xKey, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception) + { + exception = null; + + object targetProperty; + var collection = GetPropertyValue(element, propertyName, context, lineInfo, out targetProperty) as IEnumerable; + + if (collection == null) + return false; + + if (exception == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, lineInfo, out exception)) + return true; + + if (exception != null) + return false; + + var addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1); + if (addMethod == null) + return false; + + if (serviceProvider != null && serviceProvider.IProvideValueTarget != null) + ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = targetProperty; + + addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func)null, serviceProvider) }); + return true; + } + + static bool TryAddToResourceDictionary(ResourceDictionary resourceDictionary, object value, string xKey, IXmlLineInfo lineInfo, out Exception exception) + { + exception = null; + + if (resourceDictionary == null) + return false; + + if (xKey != null) + resourceDictionary.Add(xKey, value); + else if (value is Tizen.NUI.XamlBinding.Style) + resourceDictionary.Add((Tizen.NUI.XamlBinding.Style)value); + else if (value is ResourceDictionary) + resourceDictionary.Add((ResourceDictionary)value); + else if (value is StyleSheets.StyleSheet) + resourceDictionary.Add((StyleSheets.StyleSheet)value); + else { + exception = new XamlParseException("resources in ResourceDictionary require a x:Key attribute", lineInfo); + return false; + } + return true; + } + + void SetTemplate(ElementTemplate dt, INode node) + { +#pragma warning disable 0612 + ((IDataTemplate)dt).LoadTemplate = () => { +#pragma warning restore 0612 + var cnode = node.Clone(); + var context = new HydrationContext { ParentContext = Context, RootElement = Context.RootElement }; + cnode.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), node.Parent); //set parents for {StaticResource} + cnode.Accept(new ExpandMarkupsVisitor(context), null); + cnode.Accept(new NamescopingVisitor(context), null); + cnode.Accept(new CreateValuesVisitor(context), null); + cnode.Accept(new RegisterXNamesVisitor(context), null); + cnode.Accept(new FillResourceDictionariesVisitor(context), null); + cnode.Accept(new ApplyPropertiesVisitor(context, true), null); + return context.Values [cnode]; + }; + } + + static bool TryAddValue(BindableObject bindable, BindableProperty property, object value, XamlServiceProvider serviceProvider) + { + if(property?.ReturnTypeInfo?.GenericTypeArguments == null){ + return false; + } + + if(property.ReturnType == null){ + return false; + } + + if (property.ReturnTypeInfo.GenericTypeArguments.Length != 1 || + !property.ReturnTypeInfo.GenericTypeArguments[0].IsInstanceOfType(value)) + return false; + + // This might be a collection we can add to; see if we can find an Add method + var addMethod = GetAllRuntimeMethods(property.ReturnType) + .FirstOrDefault(mi => mi.Name == "Add" && mi.GetParameters().Length == 1); + if (addMethod == null) + return false; + + // If there's an add method, get the collection + var collection = bindable.GetValue(property); + + // And add the new value to it + addMethod.Invoke(collection, new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func)null, serviceProvider) }); + return true; + } + + static IEnumerable GetAllRuntimeMethods(Type type) + { + return type.GetRuntimeMethods() + .Concat(type.GetTypeInfo().ImplementedInterfaces.SelectMany(t => t.GetRuntimeMethods())); + } + + bool TrySetRuntimeName(XmlName propertyName, object source, object value, ValueNode node) + { + if (propertyName != XmlName.xName) + return false; + + var runTimeName = source.GetType().GetTypeInfo().GetCustomAttribute(); + if (runTimeName == null) + return false; + + SetPropertyValue(source, new XmlName("", runTimeName.Name), value, Context.RootElement, node, Context, node); + return true; + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/CreateValuesVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/CreateValuesVisitor.cs new file mode 100755 index 000000000..d5e493a26 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/CreateValuesVisitor.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Xml; +using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI.XamlBinding; + + +namespace Tizen.NUI.Xaml +{ + internal class CreateValuesVisitor : IXamlNodeVisitor + { + public CreateValuesVisitor(HydrationContext context) + { + Context = context; + } + + Dictionary Values + { + get { return Context.Values; } + } + + HydrationContext Context { get; } + + public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp; + public bool StopOnDataTemplate => true; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => false; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]); + + public void Visit(ValueNode node, INode parentNode) + { + Values[node] = node.Value; + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + object value = null; + + XamlParseException xpe; + var type = XamlParser.GetElementType(node.XmlType, node, Context.RootElement?.GetType().GetTypeInfo().Assembly, + out xpe); + if (xpe != null) + throw xpe; + + Context.Types[node] = type; + string ctorargname; + if (IsXaml2009LanguagePrimitive(node)) + value = CreateLanguagePrimitive(type, node); + else if (node.Properties.ContainsKey(XmlName.xArguments) || node.Properties.ContainsKey(XmlName.xFactoryMethod)) + value = CreateFromFactory(type, node); + else if ( + type.GetTypeInfo() + .DeclaredConstructors.Any( + ci => + ci.IsPublic && ci.GetParameters().Length != 0 && + ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof (ParameterAttribute)))) && + ValidateCtorArguments(type, node, out ctorargname)) + value = CreateFromParameterizedConstructor(type, node); + else if (!type.GetTypeInfo().DeclaredConstructors.Any(ci => ci.IsPublic && ci.GetParameters().Length == 0) && + !ValidateCtorArguments(type, node, out ctorargname)) + { + throw new XamlParseException($"The Property {ctorargname} is required to create a {type?.FullName} object.", node); + } + else + { + //this is a trick as the DataTemplate parameterless ctor is internal, and we can't CreateInstance(..., false) on WP7 + try + { + if (type == typeof (DataTemplate)) + value = new DataTemplate(); + if (type == typeof (ControlTemplate)) + value = new ControlTemplate(); + if (value == null && node.CollectionItems.Any() && node.CollectionItems.First() is ValueNode) + { + var serviceProvider = new XamlServiceProvider(node, Context); + var converted = ((ValueNode)node.CollectionItems.First()).Value.ConvertTo(type, () => type.GetTypeInfo(), + serviceProvider); + if (converted != null && converted.GetType() == type) + value = converted; + } + if (value == null) + { + value = Activator.CreateInstance(type); + } + } + catch (TargetInvocationException e) + { + if (e.InnerException is XamlParseException || e.InnerException is XmlException) + throw e.InnerException; + throw; + } + } + + Values[node] = value; + + var markup = value as IMarkupExtension; + if (markup != null && (value is TypeExtension || value is StaticExtension || value is ArrayExtension)) + { + var serviceProvider = new XamlServiceProvider(node, Context); + + var visitor = new ApplyPropertiesVisitor(Context); + foreach (var cnode in node.Properties.Values.ToList()) + cnode.Accept(visitor, node); + foreach (var cnode in node.CollectionItems) + cnode.Accept(visitor, node); + + value = markup.ProvideValue(serviceProvider); + + INode xKey; + if (!node.Properties.TryGetValue(XmlName.xKey, out xKey)) + xKey = null; + + node.Properties.Clear(); + node.CollectionItems.Clear(); + + if (xKey != null) + node.Properties.Add(XmlName.xKey, xKey); + + Values[node] = value; + } + + if (value is BindableObject) + NameScope.SetNameScope(value as BindableObject, node.Namescope); + } + + public void Visit(RootNode node, INode parentNode) + { + var rnode = (XamlLoader.RuntimeRootNode)node; + Values[node] = rnode.Root; + Context.Types[node] = rnode.Root.GetType(); + var bindableRoot = rnode.Root as BindableObject; + if (bindableRoot != null) + NameScope.SetNameScope(bindableRoot, node.Namescope); + } + + public void Visit(ListNode node, INode parentNode) + { + //this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties + XmlName name; + if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out name)) + node.XmlName = name; + } + + bool ValidateCtorArguments(Type nodeType, IElementNode node, out string missingArgName) + { + missingArgName = null; + var ctorInfo = + nodeType.GetTypeInfo() + .DeclaredConstructors.FirstOrDefault( + ci => + ci.GetParameters().Length != 0 && ci.IsPublic && + ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof (ParameterAttribute)))); + if (ctorInfo == null) + return true; + foreach (var parameter in ctorInfo.GetParameters()) + { + // Modify the namespace + var propname = + parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.XamlBinding.ParameterAttribute")? + .ConstructorArguments.First() + .Value as string; + if (!node.Properties.ContainsKey(new XmlName("", propname))) + { + missingArgName = propname; + return false; + } + } + + return true; + } + + public object CreateFromParameterizedConstructor(Type nodeType, IElementNode node) + { + var ctorInfo = + nodeType.GetTypeInfo() + .DeclaredConstructors.FirstOrDefault( + ci => + ci.GetParameters().Length != 0 && ci.IsPublic && + ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof (ParameterAttribute)))); + object[] arguments = CreateArgumentsArray(node, ctorInfo); + + if (arguments != null) + { + return ctorInfo?.Invoke(arguments); + } + else + { + return null; + } + } + + public object CreateFromFactory(Type nodeType, IElementNode node) + { + object[] arguments = CreateArgumentsArray(node); + + if (!node.Properties.ContainsKey(XmlName.xFactoryMethod)) + { + //non-default ctor + return Activator.CreateInstance(nodeType, arguments); + } + + var factoryMethod = ((string)((ValueNode)node.Properties[XmlName.xFactoryMethod]).Value); + Type[] types = arguments == null ? new Type[0] : arguments.Select(a => a.GetType()).ToArray(); + Func isMatch = m => { + if (m.Name != factoryMethod) + return false; + var p = m.GetParameters(); + if (p.Length != types.Length) + return false; + if (!m.IsStatic) + return false; + for (var i = 0; i < p.Length; i++) { + if ((p [i].ParameterType.IsAssignableFrom(types [i]))) + continue; + var op_impl = p[i].ParameterType.GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType) + ?? types[i].GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType); + + if (op_impl == null) + return false; + arguments [i] = op_impl.Invoke(null, new [] { arguments [i]}); + } + return true; + }; + var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch); + if (mi == null) + throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})"); + return mi.Invoke(null, arguments); + } + + public object[] CreateArgumentsArray(IElementNode enode) + { + if (!enode.Properties.ContainsKey(XmlName.xArguments)) + return null; + var node = enode.Properties[XmlName.xArguments]; + var elementNode = node as ElementNode; + if (elementNode != null) + { + var array = new object[1]; + array[0] = Values[elementNode]; + return array; + } + + var listnode = node as ListNode; + if (listnode != null) + { + var array = new object[listnode.CollectionItems.Count]; + for (var i = 0; i < listnode.CollectionItems.Count; i++) + array[i] = Values[(ElementNode)listnode.CollectionItems[i]]; + return array; + } + return null; + } + + public object[] CreateArgumentsArray(IElementNode enode, ConstructorInfo ctorInfo) + { + if( ctorInfo != null ) + { + var n = ctorInfo.GetParameters().Length; + var array = new object[n]; + for (var i = 0; i < n; i++) + { + var parameter = ctorInfo.GetParameters()[i]; + var propname = + parameter?.CustomAttributes?.First(attr => attr.AttributeType == typeof (ParameterAttribute))? + .ConstructorArguments.First() + .Value as string; + var name = new XmlName("", propname); + INode node; + if (!enode.Properties.TryGetValue(name, out node)) + { + String msg = ""; + if (propname != null) + { + msg = String.Format("The Property {0} is required to create a {1} object.", propname, ctorInfo.DeclaringType.FullName); + } + else + { + msg = "propname is null."; + } + throw new XamlParseException(msg, enode as IXmlLineInfo); + } + if (!enode.SkipProperties.Contains(name)) + enode.SkipProperties.Add(name); + var value = Context.Values[node]; + var serviceProvider = new XamlServiceProvider(enode, Context); + var convertedValue = value?.ConvertTo(parameter?.ParameterType, () => parameter, serviceProvider); + array[i] = convertedValue; + } + return array; + } + + return null; + } + + static bool IsXaml2009LanguagePrimitive(IElementNode node) + { + return node.NamespaceURI == XamlParser.X2009Uri; + } + + static object CreateLanguagePrimitive(Type nodeType, IElementNode node) + { + object value = null; + if (nodeType == typeof(string)) + value = String.Empty; + else if (nodeType == typeof(Uri)) + value = null; + else + { + value = Activator.CreateInstance(nodeType); + } + + if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode && + ((ValueNode)node.CollectionItems[0]).Value is string) + { + var valuestring = ((ValueNode)node.CollectionItems[0]).Value as string; + + if (nodeType == typeof(SByte)) { + sbyte retval; + if (sbyte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Int16)) { + short retval; + if (short.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Int32)) { + int retval; + if (int.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Int64)) { + long retval; + if (long.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Byte)) { + byte retval; + if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(UInt16)) { + ushort retval; + if (ushort.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(UInt32)) { + uint retval; + if (uint.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(UInt64)) { + ulong retval; + if (ulong.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Single)) { + float retval; + if (float.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof(Double)) { + double retval; + if (double.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof (Boolean)) + { + bool outbool; + if (bool.TryParse(valuestring, out outbool)) + return outbool; + } + if (nodeType == typeof(TimeSpan)) { + TimeSpan retval; + if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval)) + return retval; + } + if (nodeType == typeof (char)) + { + char retval; + if (char.TryParse(valuestring, out retval)) + return retval; + } + if (nodeType == typeof (string)) + return valuestring; + if (nodeType == typeof (decimal)) + { + decimal retval; + if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval)) + return retval; + } + + else if (nodeType == typeof (Uri)) + { + Uri retval; + if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval)) + return retval; + } + } + return value; + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/DesignMode.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/DesignMode.cs new file mode 100755 index 000000000..b01f9725c --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/DesignMode.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.Xaml +{ + internal static class DesignMode + { + public static bool IsDesignModeEnabled { get; internal set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ExpandMarkupsVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ExpandMarkupsVisitor.cs new file mode 100755 index 000000000..4473df894 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ExpandMarkupsVisitor.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.Xaml +{ + internal class ExpandMarkupsVisitor : IXamlNodeVisitor + { + public ExpandMarkupsVisitor(HydrationContext context) + { + Context = context; + } + + public static readonly IList Skips = new List + { + XmlName.xKey, + XmlName.xTypeArguments, + XmlName.xFactoryMethod, + XmlName.xName, + XmlName.xDataType + }; + + Dictionary Values + { + get { return Context.Values; } + } + + HydrationContext Context { get; } + + public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp; + public bool StopOnDataTemplate => false; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => true; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => false; + + public void Visit(ValueNode node, INode parentNode) + { + } + + public void Visit(MarkupNode markupnode, INode parentNode) + { + var parentElement = parentNode as IElementNode; + XmlName propertyName; + if (!ApplyPropertiesVisitor.TryGetPropertyName(markupnode, parentNode, out propertyName)) + return; + if (Skips.Contains(propertyName)) + return; + if (parentElement.SkipProperties.Contains(propertyName)) + return; + + var markupString = markupnode.MarkupString; + var node = + ParseExpression(ref markupString, markupnode.NamespaceResolver, markupnode, markupnode, parentNode) as IElementNode; + if (node != null) + { + ((IElementNode)parentNode).Properties[propertyName] = node; + node.Parent = parentNode; + } + } + + public void Visit(ElementNode node, INode parentNode) + { + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + INode ParseExpression(ref string expression, IXmlNamespaceResolver nsResolver, IXmlLineInfo xmlLineInfo, INode node, + INode parentNode) + { + if (expression.StartsWith("{}", StringComparison.Ordinal)) + return new ValueNode(expression.Substring(2), null); + + if (expression[expression.Length - 1] != '}') + throw new Exception("Expression must end with '}'"); + + int len; + string match; + if (!MarkupExpressionParser.MatchMarkup(out match, expression, out len)) + throw new Exception(); + expression = expression.Substring(len).TrimStart(); + if (expression.Length == 0) + throw new Exception("Expression did not end in '}'"); + + var serviceProvider = new XamlServiceProvider(node, Context); + serviceProvider.Add(typeof (IXmlNamespaceResolver), nsResolver); + + return new MarkupExpansionParser().Parse(match, ref expression, serviceProvider); + } + + public class MarkupExpansionParser : MarkupExpressionParser, IExpressionParser + { + IElementNode node; + + object IExpressionParser.Parse(string match, ref string remaining, IServiceProvider serviceProvider) + { + return Parse(match, ref remaining, serviceProvider); + } + + public INode Parse(string match, ref string remaining, IServiceProvider serviceProvider) + { + var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver; + if (nsResolver == null) + throw new ArgumentException(); + IXmlLineInfo xmlLineInfo = null; + var xmlLineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider; + if (xmlLineInfoProvider != null) + xmlLineInfo = xmlLineInfoProvider.XmlLineInfo; + + var split = match.Split(':'); + if (split.Length > 2) + throw new ArgumentException(); + + string prefix; //, name; + if (split.Length == 2) + { + prefix = split[0]; + // name = split [1]; + } + else + { + prefix = ""; + // name = split [0]; + } + + Type type; + var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver; + if (typeResolver == null) + type = null; + // Add Binding and StaticResource support, The ordinal code can't find BindingExtension for Binding + //else if (match == "Binding") + //{ + // type = typeof(BindingExtension); + //} + //else if (match == "StaticResource") + //{ + // type = typeof(StaticResourceExtension); + //} + else + { + //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. + if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type)) + { + var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider; + var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); + throw new XamlParseException(String.Format("MarkupExtension not found for {0}", match), lineInfo); + } + } + + var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; + var xmltype = new XmlType(namespaceuri, type?.Name, null); + + if (type == null) + throw new NotSupportedException(); + + node = xmlLineInfo == null + ? new ElementNode(xmltype, null, nsResolver) + : new ElementNode(xmltype, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); + + if (remaining.StartsWith("}", StringComparison.Ordinal)) + { + remaining = remaining.Substring(1); + return node; + } + + char next; + string piece; + while ((piece = GetNextPiece(ref remaining, out next)) != null) + HandleProperty(piece, serviceProvider, ref remaining, next != '='); + + return node; + } + + protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) + { + var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver; + + var childnode = value as INode ?? new ValueNode(strValue, nsResolver); + childnode.Parent = node; + if (prop != null) + { + var name = new XmlName(node.NamespaceURI, prop); + node.Properties[name] = childnode; + } + else //ContentProperty + node.CollectionItems.Add(childnode); + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/FillResourceDictionariesVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/FillResourceDictionariesVisitor.cs new file mode 100755 index 000000000..eadbba59a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/FillResourceDictionariesVisitor.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + internal class FillResourceDictionariesVisitor : IXamlNodeVisitor + { + public FillResourceDictionariesVisitor(HydrationContext context) + { + Context = context; + } + + HydrationContext Context { get; } + Dictionary Values => Context.Values; + + public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown; + public bool StopOnDataTemplate => true; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => false; + + public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]); + + public void Visit(ValueNode node, INode parentNode) + { + if (!typeof(ResourceDictionary).IsAssignableFrom(Context.Types[((IElementNode)parentNode)])) + return; + + node.Accept(new ApplyPropertiesVisitor(Context, stopOnResourceDictionary: false), parentNode); + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + var value = Values[node]; + XmlName propertyName; + //Set RD to VE + if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]) && ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName)) { + if ((propertyName.LocalName == "Resources" || + propertyName.LocalName.EndsWith(".Resources", StringComparison.Ordinal)) && value is ResourceDictionary) { + var source = Values[parentNode]; + ApplyPropertiesVisitor.SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node); + return; + } + } + + //Only proceed further if the node is a keyless RD + if ( parentNode is IElementNode + && typeof(ResourceDictionary).IsAssignableFrom(Context.Types[((IElementNode)parentNode)]) + && !((IElementNode)parentNode).Properties.ContainsKey(XmlName.xKey)) + node.Accept(new ApplyPropertiesVisitor(Context, stopOnResourceDictionary: false), parentNode); + else if ( parentNode is ListNode + && typeof(ResourceDictionary).IsAssignableFrom(Context.Types[((IElementNode)parentNode.Parent)]) + && !((IElementNode)parentNode.Parent).Properties.ContainsKey(XmlName.xKey)) + node.Accept(new ApplyPropertiesVisitor(Context, stopOnResourceDictionary: false), parentNode); + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + public bool SkipChildren(INode node, INode parentNode) + { + var enode = node as ElementNode; + if (enode is null) + return false; + if ( parentNode is IElementNode + && typeof(ResourceDictionary).IsAssignableFrom(Context.Types[((IElementNode)parentNode)]) + && !((IElementNode)parentNode).Properties.ContainsKey(XmlName.xKey)) + return true; + if ( parentNode is ListNode + && typeof(ResourceDictionary).IsAssignableFrom(Context.Types[((IElementNode)parentNode.Parent)]) + && !((IElementNode)parentNode.Parent).Properties.ContainsKey(XmlName.xKey)) + return true; + return false; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/HydrationContext.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/HydrationContext.cs new file mode 100755 index 000000000..0285f0790 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/HydrationContext.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.Xaml +{ + internal class HydrationContext + { + public HydrationContext() + { + Values = new Dictionary(); + Types = new Dictionary(); + } + + public Dictionary Values { get; } + public Dictionary Types { get; } + public HydrationContext ParentContext { get; set; } + public Action ExceptionHandler { get; set; } + public object RootElement { get; set; } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IConverterOptions.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IConverterOptions.cs new file mode 100755 index 000000000..722707afa --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IConverterOptions.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.Xaml +{ + internal interface IConverterOptions + { + bool IgnoreCase { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IDictionaryExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IDictionaryExtensions.cs new file mode 100755 index 000000000..eb8a8973f --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IDictionaryExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.Xaml +{ + internal static class IDictionaryExtensions + { + public static void AddRange(this IDictionary dictionary, + IEnumerable> collection) + { + foreach (var kvp in collection) + dictionary.Add(kvp); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IExpressionParser.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IExpressionParser.cs new file mode 100755 index 000000000..7d5240588 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IExpressionParser.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + internal interface IExpressionParser + { + object Parse(string match, ref string expression, IServiceProvider serviceProvider); + } + + internal interface IExpressionParser : IExpressionParser + where T : class + { + new T Parse(string match, ref string expression, IServiceProvider serviceProvider); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/INativeValueConverterService.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/INativeValueConverterService.cs new file mode 100755 index 000000000..a5a60f273 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/INativeValueConverterService.cs @@ -0,0 +1,9 @@ +using System; + +namespace Tizen.NUI.Xaml.Internals +{ + internal interface INativeValueConverterService + { + bool ConvertTo(object value, Type toType, out object nativeValue); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IProvideParentValues.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IProvideParentValues.cs new file mode 100755 index 000000000..d7de36afc --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IProvideParentValues.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.Xaml +{ + internal interface IProvideParentValues : IProvideValueTarget + { + IEnumerable ParentObjects { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IResourcesLoader.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IResourcesLoader.cs new file mode 100755 index 000000000..820289d1a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IResourcesLoader.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; +using System.Xml; +using System.IO; + +namespace Tizen.NUI +{ + internal interface IResourcesLoader + { + T CreateFromResource(string resourcePath, Assembly assembly, IXmlLineInfo lineInfo) where T : new(); + string GetResource(string resourcePath, Assembly assembly, IXmlLineInfo lineInfo); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IRootObjectProvider.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IRootObjectProvider.cs new file mode 100755 index 000000000..057ae542b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IRootObjectProvider.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.Xaml +{ + internal interface IRootObjectProvider + { + object RootObject { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/IValueConverterProvider.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/IValueConverterProvider.cs new file mode 100755 index 000000000..72942e8dd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/IValueConverterProvider.cs @@ -0,0 +1,10 @@ +using System; +using System.Reflection; + +namespace Tizen.NUI.Xaml +{ + internal interface IValueConverterProvider + { + object Convert(object value, Type toType, Func minfoRetriever, IServiceProvider serviceProvider); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExpressionParser.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExpressionParser.cs new file mode 100755 index 000000000..188ec532b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExpressionParser.cs @@ -0,0 +1,229 @@ +// +// MarkupExpressionParser.cs +// +// This code is partly salvaged from moonlight. Following licence apply. +// +// +// Author(s): +// Moonlight List (moonlight-list@lists.ximian.com) +// Stephane Delcroix (stephane@mi8.be) +// +// Copyright 2009 Novell, Inc. +// Copyright 2013 Xamarin, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Text; + +namespace Tizen.NUI.Xaml +{ + internal abstract class MarkupExpressionParser + { + public object ParseExpression(ref string expression, IServiceProvider serviceProvider) + { + if (serviceProvider == null) + throw new ArgumentNullException(nameof(serviceProvider)); + if (expression.StartsWith("{}", StringComparison.Ordinal)) + return expression.Substring(2); + + if (expression[expression.Length - 1] != '}') + throw new Exception("Expression must end with '}'"); + + int len; + string match; + if (!MatchMarkup(out match, expression, out len)) + return false; + expression = expression.Substring(len).TrimStart(); + if (expression.Length == 0) + throw new Exception("Expression did not end in '}'"); + + var parser = Activator.CreateInstance(GetType()) as IExpressionParser; + return parser?.Parse(match, ref expression, serviceProvider); + } + + internal static bool MatchMarkup(out string match, string expression, out int end) + { + if (expression.Length < 2) + { + end = 1; + match = null; + return false; + } + + if (expression[0] != '{') + { + end = 2; + match = null; + return false; + } + + int i; + bool found = false; + for (i = 1; i < expression.Length; i++) + { + if (expression[i] == ' ') + continue; + found = true; + break; + } + + if (!found) + { + end = 3; + match = null; + return false; + } + + int c; + for (c = 0; c + i < expression.Length; c++) + { + if (expression[i + c] == ' ' || expression[i + c] == '}') + break; + } + + if (i + c == expression.Length) + { + end = 6; + match = null; + return false; + } + + end = i + c; + match = expression.Substring(i, c); + return true; + } + + protected void HandleProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit) + { + char next; + object value = null; + string str_value; + + if (isImplicit) + { + SetPropertyValue(null, prop, null, serviceProvider); + return; + } + remaining = remaining.TrimStart(); + if (remaining.StartsWith("{", StringComparison.Ordinal)) + { + value = ParseExpression(ref remaining, serviceProvider); + remaining = remaining.TrimStart(); + + if (remaining.Length > 0 && remaining[0] == ',') + remaining = remaining.Substring(1); + else if (remaining.Length > 0 && remaining[0] == '}') + remaining = remaining.Substring(1); + + str_value = value as string; + } + else + str_value = GetNextPiece(ref remaining, out next); + + SetPropertyValue(prop, str_value, value, serviceProvider); + } + + protected abstract void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider); + + protected string GetNextPiece(ref string remaining, out char next) + { + bool inString = false; + int end = 0; + char stringTerminator = '\0'; + remaining = remaining.TrimStart(); + if (remaining.Length == 0) + { + next = Char.MaxValue; + return null; + } + + var piece = new StringBuilder(); + // If we're inside a quoted string we append all chars to our piece until we hit the ending quote. + while (end < remaining.Length && + (inString || (remaining[end] != '}' && remaining[end] != ',' && remaining[end] != '='))) + { + if (inString) + { + if (remaining[end] == stringTerminator) + { + inString = false; + end ++; + break; + } + } + else + { + if (remaining[end] == '\'' || remaining[end] == '"') + { + inString = true; + stringTerminator = remaining[end]; + end ++; + continue; + } + } + + // If this is an escape char, consume it and append the next char to our piece. + if (remaining[end] == '\\') + { + end ++; + if (end == remaining.Length) + break; + } + piece.Append(remaining[end]); + end++; + } + + if (inString && end == remaining.Length) + throw new Exception("Unterminated quoted string"); + + if (end == remaining.Length && !remaining.EndsWith("}", StringComparison.Ordinal)) + throw new Exception("Expression did not end with '}'"); + + if (end == 0) + { + next = Char.MaxValue; + return null; + } + + next = remaining[end]; + remaining = remaining.Substring(end + 1); + + // Whitespace is trimmed from the end of the piece before stripping + // quote chars from the start/end of the string. + while (piece.Length > 0 && char.IsWhiteSpace(piece[piece.Length - 1])) + piece.Length --; + + if (piece.Length >= 2) + { + char first = piece[0]; + char last = piece[piece.Length - 1]; + if ((first == '\'' && last == '\'') || (first == '"' && last == '"')) + { + piece.Remove(piece.Length - 1, 1); + piece.Remove(0, 1); + } + } + + return piece.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExtensionParser.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExtensionParser.cs new file mode 100755 index 000000000..f360bdd28 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/MarkupExtensionParser.cs @@ -0,0 +1,81 @@ +using System; +using System.Reflection; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + internal sealed class MarkupExtensionParser : MarkupExpressionParser, IExpressionParser + { + IMarkupExtension markupExtension; + + public object Parse(string match, ref string remaining, IServiceProvider serviceProvider) + { + var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver; + + //shortcut for Binding and StaticResource, to avoid too many reflection calls. + if (match == "Binding") + markupExtension = new BindingExtension(); + else if (match == "TemplateBinding") + markupExtension = new TemplateBindingExtension(); + else if (match == "StaticResource") + markupExtension = new StaticResourceExtension(); + else + { + if (typeResolver == null) + return null; + Type type; + + //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. + if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type)) + { + var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider; + var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); + throw new XamlParseException(String.Format("MarkupExtension not found for {0}", match), lineInfo); + } + markupExtension = Activator.CreateInstance(type) as IMarkupExtension; + } + + if (markupExtension == null) + { + var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider; + var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); + throw new XamlParseException(String.Format("Missing public default constructor for MarkupExtension {0}", match), + lineInfo); + } + + char next; + if (remaining == "}") + return markupExtension.ProvideValue(serviceProvider); + + string piece; + while ((piece = GetNextPiece(ref remaining, out next)) != null) + HandleProperty(piece, serviceProvider, ref remaining, next != '='); + + return markupExtension.ProvideValue(serviceProvider); + } + + protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) + { + MethodInfo setter; + if (prop == null) + { + //implicit property + var t = markupExtension.GetType(); + prop = ApplyPropertiesVisitor.GetContentPropertyName(t.GetTypeInfo()); + if (prop == null) + return; + setter = t.GetRuntimeProperty(prop)?.SetMethod; + } + else + setter = markupExtension.GetType().GetRuntimeProperty(prop)?.SetMethod; + + if (value == null && strValue != null) + { + value = strValue.ConvertTo(markupExtension.GetType().GetRuntimeProperty(prop)?.PropertyType, + (Func)null, serviceProvider); + } + + setter?.Invoke(markupExtension, new[] { value }); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/NamescopingVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/NamescopingVisitor.cs new file mode 100755 index 000000000..5deb83d14 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/NamescopingVisitor.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.Xaml +{ + internal class NamescopingVisitor : IXamlNodeVisitor + { + readonly Dictionary scopes = new Dictionary(); + + public NamescopingVisitor(HydrationContext context) + { + Values = context.Values; + } + + Dictionary Values { get; set; } + + public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown; + public bool StopOnDataTemplate => false; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => true; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => false; + + public void Visit(ValueNode node, INode parentNode) + { + scopes[node] = scopes[parentNode]; + } + + public void Visit(MarkupNode node, INode parentNode) + { + scopes[node] = scopes[parentNode]; + } + + public void Visit(ElementNode node, INode parentNode) + { + var ns = parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode) || IsVisualStateGroupList(node) + ? new NameScope() + : scopes[parentNode]; + node.Namescope = ns; + scopes[node] = ns; + } + + public void Visit(RootNode node, INode parentNode) + { + var ns = new NameScope(); + node.Namescope = ns; + scopes[node] = ns; + } + + public void Visit(ListNode node, INode parentNode) + { + scopes[node] = scopes[parentNode]; + } + + static bool IsDataTemplate(INode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode createContent; + if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) && + createContent == node) + return true; + return false; + } + + static bool IsStyle(INode node, INode parentNode) + { + var pnode = parentNode as ElementNode; + return pnode != null && pnode.XmlType.Name == "Style"; + } + + static bool IsVisualStateGroupList(ElementNode node) + { + return node != null && node.XmlType.Name == "VisualStateGroup" && node.Parent is IListNode; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ProvideCompiledAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ProvideCompiledAttribute.cs new file mode 100755 index 000000000..4ea43855a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ProvideCompiledAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + sealed class ProvideCompiledAttribute : Attribute + { + public string CompiledVersion { get; } + + public ProvideCompiledAttribute (string compiledVersion) + { + CompiledVersion = compiledVersion; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/PruneIgnoredNodesVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/PruneIgnoredNodesVisitor.cs new file mode 100755 index 000000000..8c025e582 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/PruneIgnoredNodesVisitor.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Tizen.NUI.Xaml +{ + internal class PruneIgnoredNodesVisitor : IXamlNodeVisitor + { + public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown; + public bool StopOnDataTemplate => false; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => true; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => false; + + public void Visit(ElementNode node, INode parentNode) + { + foreach (var propertyKvp in node.Properties) + { + var propertyName = propertyKvp.Key; + var propertyValue = (propertyKvp.Value as ValueNode)?.Value as string; + if (propertyValue == null) + continue; + if (!propertyName.Equals(XamlParser.McUri, "Ignorable")) + continue; + (parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List())).AddRange(propertyValue.Split(',')); + } + + foreach (var propertyKvp in node.Properties.ToList()) + { + // skip d:foo="bar" + var prefix = node.NamespaceResolver.LookupPrefix(propertyKvp.Key.NamespaceURI); + if (node.SkipPrefix(prefix)) + node.Properties.Remove(propertyKvp.Key); + var propNs = (propertyKvp.Value as IElementNode)?.NamespaceURI ?? ""; + var propPrefix = node.NamespaceResolver.LookupPrefix(propNs); + if (node.SkipPrefix(propPrefix)) + node.Properties.Remove(propertyKvp.Key); + } + + foreach (var prop in node.CollectionItems.ToList()) + { + var propNs = (prop as IElementNode)?.NamespaceURI ?? ""; + var propPrefix = node.NamespaceResolver.LookupPrefix(propNs); + if (node.SkipPrefix(propPrefix)) + node.CollectionItems.Remove(prop); + } + + if (node.SkipPrefix(node.NamespaceResolver.LookupPrefix(node.NamespaceURI))) + { + node.Properties.Clear(); + node.CollectionItems.Clear(); + } + } + + public void Visit(RootNode node, INode parentNode) + { + Visit((ElementNode)node, node); + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + foreach (var prop in node.CollectionItems.ToList()) + { + var propNs = (prop as IElementNode)?.NamespaceURI ?? ""; + var propPrefix = node.NamespaceResolver.LookupPrefix(propNs); + if (node.SkipPrefix(propPrefix)) + node.CollectionItems.Remove(prop); + } + } + + public void Visit(ValueNode node, INode parentNode) + { + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ReflectionExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ReflectionExtensions.cs new file mode 100755 index 000000000..142a81457 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ReflectionExtensions.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace Tizen.NUI.XamlBinding.Internals +{ + internal static class ReflectionExtensions + { + public static FieldInfo GetField(this Type type, Func predicate) + { + return GetFields(type).FirstOrDefault(predicate); + } + + public static FieldInfo GetField(this Type type, string name) + { + return type.GetField(fi => fi.Name == name); + } + + public static IEnumerable GetFields(this Type type) + { + return GetParts(type, i => i.DeclaredFields); + } + + public static IEnumerable GetProperties(this Type type) + { + return GetParts(type, ti => ti.DeclaredProperties); + } + + public static PropertyInfo GetProperty(this Type type, string name) + { + Type t = type; + while (t != null) + { + System.Reflection.TypeInfo ti = t.GetTypeInfo(); + PropertyInfo property = ti.GetDeclaredProperty(name); + if (property != null) + return property; + + t = ti.BaseType; + } + + return null; + } + + public static bool IsAssignableFrom(this Type self, Type c) + { + return self.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo()); + } + + public static bool IsInstanceOfType(this Type self, object o) + { + return self.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); + } + + static IEnumerable GetParts(Type type, Func> selector) + { + Type t = type; + while (t != null) + { + System.Reflection.TypeInfo ti = t.GetTypeInfo(); + foreach (T f in selector(ti)) + yield return f; + t = ti.BaseType; + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/RegisterXNamesVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/RegisterXNamesVisitor.cs new file mode 100755 index 000000000..e6ad8fd86 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/RegisterXNamesVisitor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + internal class RegisterXNamesVisitor : IXamlNodeVisitor + { + public RegisterXNamesVisitor(HydrationContext context) + { + Context = context; + Values = context.Values; + } + + Dictionary Values { get; } + HydrationContext Context { get; } + public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown; + public bool StopOnDataTemplate => true; + public bool StopOnResourceDictionary => false; + public bool VisitNodeOnDataTemplate => false; + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]); + + public void Visit(ValueNode node, INode parentNode) + { + if (!IsXNameProperty(node, parentNode)) + return; + try + { + ((IElementNode)parentNode).Namescope.RegisterName((string)node.Value, Values[parentNode]); + } + catch (ArgumentException ae) + { + if (ae.ParamName != "name") + throw ae; + throw new XamlParseException($"An element with the name \"{(string)node.Value}\" already exists in this NameScope", node); + } + var element = Values[parentNode] as Element; + if (element != null) + element.StyleId = element.StyleId ?? (string)node.Value; + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + static bool IsXNameProperty(ValueNode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode xNameNode; + if (parentElement != null && parentElement.Properties.TryGetValue(XmlName.xName, out xNameNode) && xNameNode == node) + return true; + return false; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ResourcesLoader.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ResourcesLoader.cs new file mode 100755 index 000000000..dfcd4ddc2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ResourcesLoader.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Reflection; +using System.Xml; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.Xaml +{ + internal class ResourcesLoader : IResourcesLoader + { + public T CreateFromResource(string resourcePath, Assembly assembly, IXmlLineInfo lineInfo) where T: new() + { + var alternateResource = ResourceLoader.ResourceProvider?.Invoke(assembly.GetName(), resourcePath); + if (alternateResource != null) { + var rd = new T(); + rd.LoadFromXaml(alternateResource); + return rd; + } + + var resourceId = XamlResourceIdAttribute.GetResourceIdForPath(assembly, resourcePath); + if (resourceId == null) + throw new XamlParseException($"Resource '{resourcePath}' not found.", lineInfo); + + using (var stream = assembly.GetManifestResourceStream(resourceId)) { + if (stream == null) + throw new XamlParseException($"No resource found for '{resourceId}'.", lineInfo); + using (var reader = new StreamReader(stream)) { + var rd = new T(); + rd.LoadFromXaml(reader.ReadToEnd()); + return rd; + } + } + } + + public string GetResource(string resourcePath, Assembly assembly, IXmlLineInfo lineInfo) + { + var alternateResource = ResourceLoader.ResourceProvider?.Invoke(assembly.GetName(), resourcePath); + if (alternateResource != null) + return alternateResource; + + var resourceId = XamlResourceIdAttribute.GetResourceIdForPath(assembly, resourcePath); + if (resourceId == null) + throw new XamlParseException($"Resource '{resourcePath}' not found.", lineInfo); + + using (var stream = assembly.GetManifestResourceStream(resourceId)) { + if (stream == null) + throw new XamlParseException($"No resource found for '{resourceId}'.", lineInfo); + using (var reader = new StreamReader(stream)) + return reader.ReadToEnd(); + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/RuntimeNamePropertyAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/RuntimeNamePropertyAttribute.cs new file mode 100755 index 000000000..80b8fe5cd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/RuntimeNamePropertyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + [AttributeUsage(AttributeTargets.Class)] + internal sealed class RuntimeNamePropertyAttribute : Attribute + { + public RuntimeNamePropertyAttribute(string name) + { + Name = name; + } + + public string Name { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeArgumentsParser.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeArgumentsParser.cs new file mode 100755 index 000000000..42e80acbc --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeArgumentsParser.cs @@ -0,0 +1,71 @@ + +using System.Collections.Generic; +using System.Xml; + +namespace Tizen.NUI.Xaml +{ + internal static class TypeArgumentsParser + { + public static IList ParseExpression(string expression, IXmlNamespaceResolver resolver, IXmlLineInfo lineInfo) + { + var typeList = new List(); + while (!string.IsNullOrWhiteSpace(expression)) + { + var match = expression; + typeList.Add(Parse(match, ref expression, resolver, lineInfo)); + } + return typeList; + } + + static XmlType Parse(string match, ref string remaining, IXmlNamespaceResolver resolver, IXmlLineInfo lineinfo) + { + remaining = null; + int parensCount = 0; + int pos = 0; + bool isGeneric = false; + + for (pos = 0; pos < match.Length; pos++) + { + if (match[pos] == '(') + { + parensCount++; + isGeneric = true; + } + else if (match[pos] == ')') + parensCount--; + else if (match[pos] == ',' && parensCount == 0) + { + remaining = match.Substring(pos + 1); + break; + } + } + var type = match.Substring(0, pos).Trim(); + + IList typeArguments = null; + if (isGeneric) + { + typeArguments = ParseExpression( + type.Substring(type.IndexOf('(') + 1, type.LastIndexOf(')') - type.IndexOf('(') - 1), resolver, lineinfo); + type = type.Substring(0, type.IndexOf('(')); + } + + var split = type.Split(':'); + if (split.Length > 2) + return null; + + string prefix, name; + if (split.Length == 2) { + prefix = split [0]; + name = split [1]; + } else { + prefix = ""; + name = split [0]; + } + + var namespaceuri = resolver.LookupNamespace(prefix); + if (namespaceuri == null) + throw new XamlParseException($"No xmlns declaration for prefix '{prefix}'.", lineinfo, null); + return new XmlType(namespaceuri, name, typeArguments); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionAttribute.cs new file mode 100755 index 000000000..2b81846ff --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + [System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + internal sealed class TypeConversionAttribute : Attribute + { + public Type TargetType { get; private set; } + + public TypeConversionAttribute(Type targetType) + { + TargetType = targetType; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionExtensions.cs new file mode 100755 index 000000000..3fca3e392 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/TypeConversionExtensions.cs @@ -0,0 +1,256 @@ +// +// TypeConversionExtensions.cs +// +// Author: +// Stephane Delcroix +// +// Copyright (c) 2013 Mobile Inception +// Copyright (c) 2014 Xamarin, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +// using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI.Xaml.Internals; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + internal static class TypeConversionExtensions + { + internal static object ConvertTo(this object value, Type toType, Func pinfoRetriever, + IServiceProvider serviceProvider) + { + Func getConverter = () => + { + ParameterInfo pInfo; + if (pinfoRetriever == null || (pInfo = pinfoRetriever()) == null) + return null; + + var converterTypeName = pInfo.CustomAttributes.GetTypeConverterTypeName(); + if (converterTypeName == null) + return null; + var convertertype = Type.GetType(converterTypeName); + return (TypeConverter)Activator.CreateInstance(convertertype); + }; + + return ConvertTo(value, toType, getConverter, serviceProvider); + } + + static private Assembly assemblyOfConverter = null; + static private string nameSpaceOfConverter = null; + + static internal string GetConverterName(Type type) + { + if (null == TypeConversionExtensions.assemblyOfConverter) + { + Type position2DTypeConverterType = typeof(Position2DTypeConverter); + assemblyOfConverter = position2DTypeConverterType.Assembly; + + nameSpaceOfConverter = position2DTypeConverterType.FullName; + nameSpaceOfConverter = nameSpaceOfConverter.Substring(0, nameSpaceOfConverter.LastIndexOf('.') + 1); + } + + Type xamlToType = assemblyOfConverter.GetType(nameSpaceOfConverter + type.Name + "TypeConverter"); + + if (null == xamlToType) + { + return null; + } + else + { + return xamlToType.FullName; + } + } + + internal static object ConvertTo(this object value, Type toType, Func minfoRetriever, + IServiceProvider serviceProvider) + { + Func getConverter = () => + { + var converterTypeName = GetConverterName(toType); + + if (null == converterTypeName) + { + return null; + } + + var convertertype = Type.GetType(converterTypeName); + return Activator.CreateInstance(convertertype); + }; + + return ConvertTo(value, toType, getConverter, serviceProvider); + } + + static string GetTypeConverterTypeName(this IEnumerable attributes) + { + var converterAttribute = + attributes.FirstOrDefault(cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName)); + if (converterAttribute == null) + return null; + if (converterAttribute.ConstructorArguments[0].ArgumentType == typeof (string)) + return (string)converterAttribute.ConstructorArguments[0].Value; + if (converterAttribute.ConstructorArguments[0].ArgumentType == typeof (Type)) + return ((Type)converterAttribute.ConstructorArguments[0].Value).AssemblyQualifiedName; + return null; + } + + //Don't change the name or the signature of this, it's used by XamlC + public static object ConvertTo(this object value, Type toType, Type convertertype, IServiceProvider serviceProvider) + { + if (convertertype == null) + return value.ConvertTo(toType, (Func)null, serviceProvider); + Func getConverter = () => Activator.CreateInstance(convertertype); + ; + return value.ConvertTo(toType, getConverter, serviceProvider); + } + + private delegate void ParseValueFunc(string s, IFormatProvider provider); + + static private Dictionary typeToParseValueFunc = null; + + static private void BuildParseValueFunc() + { + if (null == typeToParseValueFunc) + { + typeToParseValueFunc = new Dictionary(); + + } + } + + internal static object ConvertTo(this object value, Type toType, Func getConverter, + IServiceProvider serviceProvider) + { + if (value == null) + return null; + + var str = value as string; + if (str != null) + { + //If there's a [TypeConverter], use it + object converter = getConverter?.Invoke(); + if (null != converter) + { + var xfTypeConverter = converter as TypeConverter; + var xfExtendedTypeConverter = xfTypeConverter as IExtendedTypeConverter; + if (xfExtendedTypeConverter != null) + return value = xfExtendedTypeConverter.ConvertFromInvariantString(str, serviceProvider); + if (xfTypeConverter != null) + return value = xfTypeConverter.ConvertFromInvariantString(str); + var converterType = converter?.GetType(); + if (converterType != null) + { + var convertFromStringInvariant = converterType.GetRuntimeMethod("ConvertFromInvariantString", + new[] { typeof(string) }); + if (convertFromStringInvariant != null) + return value = convertFromStringInvariant.Invoke(converter, new object[] { str }); + } + } + + var ignoreCase = (serviceProvider?.GetService(typeof(IConverterOptions)) as IConverterOptions)?.IgnoreCase ?? false; + + //If the type is nullable, as the value is not null, it's safe to assume we want the built-in conversion + if (toType.GetTypeInfo().IsGenericType && toType.GetGenericTypeDefinition() == typeof (Nullable<>)) + toType = Nullable.GetUnderlyingType(toType); + + //Obvious Built-in conversions + if (toType.GetTypeInfo().IsEnum) + return Enum.Parse(toType, str, ignoreCase); + + if (toType == typeof(SByte)) + return SByte.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(Int16)) + return Int16.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(Int32)) + return Int32.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(Int64)) + return Int64.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(Byte)) + return Byte.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(UInt16)) + return UInt16.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(UInt32)) + return UInt32.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(UInt64)) + return UInt64.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof (Single)) + return Single.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof (Double)) + return Double.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof (Boolean)) + return Boolean.Parse(str); + if (toType == typeof (TimeSpan)) + return TimeSpan.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof (DateTime)) + return DateTime.Parse(str, CultureInfo.InvariantCulture); + if (toType == typeof(Char)) { + char c = '\0'; + Char.TryParse(str, out c); + return c; + } + if (toType == typeof (String) && str.StartsWith("{}", StringComparison.Ordinal)) + return str.Substring(2); + if (toType == typeof (String)) + return value; + if (toType == typeof(Decimal)) + return Decimal.Parse(str, CultureInfo.InvariantCulture); + } + + //if the value is not assignable and there's an implicit conversion, convert + if (value != null && !toType.IsAssignableFrom(value.GetType())) { + var opImplicit = value.GetType().GetImplicitConversionOperator(fromType: value.GetType(), toType: toType) + ?? toType.GetImplicitConversionOperator(fromType: value.GetType(), toType: toType); + + if (opImplicit != null) { + value = opImplicit.Invoke(null, new[] { value }); + return value; + } + } + + var nativeValueConverterService = DependencyService.Get(); + + object nativeValue = null; + if (nativeValueConverterService != null && nativeValueConverterService.ConvertTo(value, toType, out nativeValue)) + return nativeValue; + + return value; + } + + internal static MethodInfo GetImplicitConversionOperator(this Type onType, Type fromType, Type toType) + { +#if NETSTANDARD1_0 + var mi = onType.GetRuntimeMethod("op_Implicit", new[] { fromType }); +#else + var bindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; + var mi = onType.GetMethod("op_Implicit", bindingFlags, null, new[] { fromType }, null); +#endif + if (mi == null) return null; + if (!mi.IsSpecialName) return null; + if (!mi.IsPublic) return null; + if (!mi.IsStatic) return null; + if (!toType.IsAssignableFrom(mi.ReturnType)) return null; + + return mi; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/ValueConverterProvider.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/ValueConverterProvider.cs new file mode 100755 index 000000000..005678eaf --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/ValueConverterProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Reflection; + +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.Xaml +{ + internal class ValueConverterProvider : IValueConverterProvider + { + public object Convert(object value, Type toType, Func minfoRetriever, IServiceProvider serviceProvider) + { + return value.ConvertTo(toType, minfoRetriever, serviceProvider); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/VisualStateManager.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/VisualStateManager.cs new file mode 100755 index 000000000..c0fec0454 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/VisualStateManager.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + internal static class VisualStateManager + { + internal class CommonStates + { + public const string Normal = "Normal"; + public const string Disabled = "Disabled"; + public const string Focused = "Focused"; + } + + public static readonly BindableProperty VisualStateGroupsProperty = + BindableProperty.CreateAttached("VisualStateGroups", typeof(VisualStateGroupList), typeof(Element), + defaultValue: null, propertyChanged: VisualStateGroupsPropertyChanged, + defaultValueCreator: bindable => new VisualStateGroupList()); + + static void VisualStateGroupsPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + GoToState((Element)bindable, CommonStates.Normal); + } + + public static IList GetVisualStateGroups(Element visualElement) + { + return (IList)visualElement.GetValue(VisualStateGroupsProperty); + } + + public static void SetVisualStateGroups(Element visualElement, VisualStateGroupList value) + { + visualElement.SetValue(VisualStateGroupsProperty, value); + } + + public static bool GoToState(Element visualElement, string name) + { + if (!visualElement.IsSet(VisualStateGroupsProperty)) + { + return false; + } + + var groups = (IList)visualElement.GetValue(VisualStateGroupsProperty); + + foreach (VisualStateGroup group in groups) + { + if (group.CurrentState?.Name == name) + { + // We're already in the target state; nothing else to do + return true; + } + + // See if this group contains the new state + var target = group.GetState(name); + if (target == null) + { + continue; + } + + // If we've got a new state to transition to, unapply the setters from the current state + if (group.CurrentState != null) + { + foreach (Setter setter in group.CurrentState.Setters) + { + setter.UnApply(visualElement); + } + } + + // Update the current state + group.CurrentState = target; + + // Apply the setters from the new state + foreach (Setter setter in target.Setters) + { + setter.Apply(visualElement); + } + + return true; + } + + return false; + } + + public static bool HasVisualStateGroups(this Element element) + { + return element.IsSet(VisualStateGroupsProperty); + } + } + + internal class VisualStateGroupList : IList + { + readonly IList _internalList; + + void Validate(IList groups) + { + // If we have 1 group, no need to worry about duplicate group names + if (groups.Count > 1) + { + if (groups.GroupBy(vsg => vsg.Name).Any(g => g.Count() > 1)) + { + throw new InvalidOperationException("VisualStateGroup Names must be unique"); + } + } + + // State names must be unique within this group list, so pull in all + // the states in all the groups, group them by name, and see if we have + // and duplicates + if (groups.SelectMany(group => group.States) + .GroupBy(state => state.Name) + .Any(g => g.Count() > 1)) + { + throw new InvalidOperationException("VisualState Names must be unique"); + } + } + + public VisualStateGroupList() + { + _internalList = new WatchAddList(Validate); + } + + void ValidateOnStatesChanged(object sender, EventArgs eventArgs) + { + Validate(_internalList); + } + + public IEnumerator GetEnumerator() + { + return _internalList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_internalList).GetEnumerator(); + } + + public void Add(VisualStateGroup item) + { + _internalList.Add(item); + item.StatesChanged += ValidateOnStatesChanged; + } + + public void Clear() + { + foreach (var group in _internalList) + { + group.StatesChanged -= ValidateOnStatesChanged; + } + + _internalList.Clear(); + } + + public bool Contains(VisualStateGroup item) + { + return _internalList.Contains(item); + } + + public void CopyTo(VisualStateGroup[] array, int arrayIndex) + { + _internalList.CopyTo(array, arrayIndex); + } + + public bool Remove(VisualStateGroup item) + { + item.StatesChanged -= ValidateOnStatesChanged; + return _internalList.Remove(item); + } + + public int Count => _internalList.Count; + + public bool IsReadOnly => false; + + public int IndexOf(VisualStateGroup item) + { + return _internalList.IndexOf(item); + } + + public void Insert(int index, VisualStateGroup item) + { + item.StatesChanged += ValidateOnStatesChanged; + _internalList.Insert(index, item); + } + + public void RemoveAt(int index) + { + _internalList[index].StatesChanged -= ValidateOnStatesChanged; + _internalList.RemoveAt(index); + } + + public VisualStateGroup this[int index] + { + get => _internalList[index]; + set => _internalList[index] = value; + } + } + + [RuntimeNameProperty(nameof(Name))] + [ContentProperty(nameof(States))] + internal sealed class VisualStateGroup + { + public VisualStateGroup() + { + States = new WatchAddList(OnStatesChanged); + } + + public Type TargetType { get; set; } + public string Name { get; set; } + public IList States { get; } + public VisualState CurrentState { get; internal set; } + + internal VisualState GetState(string name) + { + foreach (VisualState state in States) + { + if (string.CompareOrdinal(state.Name, name) == 0) + { + return state; + } + } + + return null; + } + + internal VisualStateGroup Clone() + { + var clone = new VisualStateGroup {TargetType = TargetType, Name = Name, CurrentState = CurrentState}; + foreach (VisualState state in States) + { + clone.States.Add(state.Clone()); + } + + return clone; + } + + internal event EventHandler StatesChanged; + + void OnStatesChanged(IList list) + { + if (list.Any(state => string.IsNullOrEmpty(state.Name))) + { + throw new InvalidOperationException("State names may not be null or empty"); + } + + StatesChanged?.Invoke(this, EventArgs.Empty); + } + } + + [RuntimeNameProperty(nameof(Name))] + internal sealed class VisualState + { + public VisualState() + { + Setters = new ObservableCollection(); + } + + public string Name { get; set; } + public IList Setters { get;} + public Type TargetType { get; set; } + + internal VisualState Clone() + { + var clone = new VisualState { Name = Name, TargetType = TargetType }; + foreach (var setter in Setters) + { + clone.Setters.Add(setter); + } + + return clone; + } + } + + internal static class VisualStateGroupListExtensions + { + internal static IList Clone(this IList groups) + { + var actual = new VisualStateGroupList(); + foreach (var group in groups) + { + actual.Add(group.Clone()); + } + + return actual; + } + } + + internal class WatchAddList : IList + { + readonly Action> _onAdd; + readonly List _internalList; + + public WatchAddList(Action> onAdd) + { + _onAdd = onAdd; + _internalList = new List(); + } + + public IEnumerator GetEnumerator() + { + return _internalList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_internalList).GetEnumerator(); + } + + public void Add(T item) + { + _internalList.Add(item); + _onAdd(_internalList); + } + + public void Clear() + { + _internalList.Clear(); + } + + public bool Contains(T item) + { + return _internalList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _internalList.CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + return _internalList.Remove(item); + } + + public int Count => _internalList.Count; + + public bool IsReadOnly => false; + + public int IndexOf(T item) + { + return _internalList.IndexOf(item); + } + + public void Insert(int index, T item) + { + _internalList.Insert(index, item); + _onAdd(_internalList); + } + + public void RemoveAt(int index) + { + _internalList.RemoveAt(index); + } + + public T this[int index] + { + get => _internalList[index]; + set => _internalList[index] = value; + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlLoader.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlLoader.cs new file mode 100755 index 000000000..ae3a60fe2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlLoader.cs @@ -0,0 +1,398 @@ +// +// XamlLoader.cs +// +// Author: +// Stephane Delcroix +// +// Copyright (c) 2018 Mobile Inception +// Copyright (c) 2018-2014 Xamarin, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Xml; +using Tizen.NUI.Xaml.Forms.BaseComponents; +using Tizen.NUI.XamlBinding; +using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI; + +namespace Tizen.NUI.Xaml.Internals +{ + [Obsolete ("Replaced by ResourceLoader")] + internal static class XamlLoader + { + static Func xamlFileProvider; + + public static Func XamlFileProvider { + get { return xamlFileProvider; } + internal set { + xamlFileProvider = value; + Tizen.NUI.Xaml.DesignMode.IsDesignModeEnabled = true; + //¯\_(ツ)_/¯ the previewer forgot to set that bool + DoNotThrowOnExceptions = value != null; + } + } + + internal static bool DoNotThrowOnExceptions { get; set; } + } +} + +namespace Tizen.NUI.Xaml +{ + static internal class XamlLoader + { + public static void Load(object view, Type callingType) + { + try + { + string xaml = ""; + + var assembly = callingType.GetTypeInfo().Assembly; + var resourceId = XamlResourceIdAttribute.GetResourceIdForType(callingType); + + if (resourceId == null) + { + xaml = LegacyGetXamlForType(callingType); + } + else + { + using (var stream = assembly.GetManifestResourceStream(resourceId)) + { + if (stream != null) + using (var reader = new StreamReader(stream)) + xaml = reader.ReadToEnd(); + else + xaml = null; + } + } + + if (string.IsNullOrEmpty(xaml)) + { + xaml = GetXamlForType(callingType); + if (string.IsNullOrEmpty(xaml)) + throw new XamlParseException(string.Format("Can't get xaml from type {0}", callingType), new XmlLineInfo()); + } + + Load(view, xaml); + } + catch (XamlParseException e) + { + Tizen.Log.Fatal("NUI", "XamlParseException e.Message: " + e.Message); + Console.WriteLine("\n[FATAL] XamlParseException e.Message: {0}\n", e.Message); + } + } + + public static T LoadObject(string path) + { + var xaml = GetAnimationXaml(path); + if (string.IsNullOrEmpty(xaml)) + throw new XamlParseException(string.Format("No embeddedresource found for {0}", path), new XmlLineInfo()); + Type type = typeof(T); + T ret = (T)type.Assembly.CreateInstance(type.FullName); + + NameScopeExtensions.PushElement(ret); + + using (var textReader = new StringReader(xaml)) + using (var reader = XmlReader.Create(textReader)) + { + while (reader.Read()) + { + //Skip until element + if (reader.NodeType == XmlNodeType.Whitespace) + continue; + if (reader.NodeType == XmlNodeType.XmlDeclaration) + continue; + if (reader.NodeType != XmlNodeType.Element) + { + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + continue; + } + + var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), ret, (IXmlNamespaceResolver)reader); + XamlParser.ParseXaml(rootnode, reader); + Visit(rootnode, new HydrationContext + { + RootElement = ret, +#pragma warning disable 0618 + ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action)null) +#pragma warning restore 0618 + }); + break; + } + } + + NameScopeExtensions.PopElement(); + return ret; + } + + public static void Load(object view, string xaml) + { + using (var textReader = new StringReader(xaml)) + using (var reader = XmlReader.Create(textReader)) + { + while (reader.Read()) + { + //Skip until element + if (reader.NodeType == XmlNodeType.Whitespace) + continue; + if (reader.NodeType == XmlNodeType.XmlDeclaration) + continue; + if (reader.NodeType != XmlNodeType.Element) { + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + continue; + } + + var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader); + XamlParser.ParseXaml (rootnode, reader); + Visit (rootnode, new HydrationContext { + RootElement = view, +#pragma warning disable 0618 + ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { }: (Action)null) +#pragma warning restore 0618 + }); + break; + } + } + } + + [Obsolete ("Use the XamlFileProvider to provide xaml files. We will remove this when Cycle 8 hits Stable.")] + public static object Create (string xaml, bool doNotThrow = false) + { + object inflatedView = null; + using (var textreader = new StringReader(xaml)) + using (var reader = XmlReader.Create (textreader)) { + while (reader.Read ()) { + //Skip until element + if (reader.NodeType == XmlNodeType.Whitespace) + continue; + if (reader.NodeType == XmlNodeType.XmlDeclaration) + continue; + if (reader.NodeType != XmlNodeType.Element) { + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + continue; + } + + var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader); + XamlParser.ParseXaml (rootnode, reader); + var visitorContext = new HydrationContext { + ExceptionHandler = doNotThrow ? e => { } : (Action)null, + }; + var cvv = new CreateValuesVisitor (visitorContext); + cvv.Visit ((ElementNode)rootnode, null); + inflatedView = rootnode.Root = visitorContext.Values [rootnode]; + visitorContext.RootElement = inflatedView as BindableObject; + + Visit (rootnode, visitorContext); + break; + } + } + return inflatedView; + } + + static void Visit (RootNode rootnode, HydrationContext visitorContext) + { + rootnode.Accept (new XamlNodeVisitor ((node, parent) => node.Parent = parent), null); //set parents for {StaticResource} + rootnode.Accept (new ExpandMarkupsVisitor (visitorContext), null); + rootnode.Accept (new PruneIgnoredNodesVisitor(), null); + rootnode.Accept (new NamescopingVisitor (visitorContext), null); //set namescopes for {x:Reference} + rootnode.Accept (new CreateValuesVisitor (visitorContext), null); + rootnode.Accept (new RegisterXNamesVisitor (visitorContext), null); + rootnode.Accept (new FillResourceDictionariesVisitor (visitorContext), null); + rootnode.Accept (new ApplyPropertiesVisitor (visitorContext, true), null); + } + + static string GetAnimationXaml(string animationXamlPath) + { + string xaml; + if (File.Exists(animationXamlPath)) + { + StreamReader reader = new StreamReader(animationXamlPath); + xaml = reader.ReadToEnd(); + reader.Close(); + reader.Dispose(); + Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml); + return xaml; + } + + return null; + } + static string GetXamlForType(Type type) + { + //the Previewer might want to provide it's own xaml for this... let them do that + //the check at the end is preferred (using ResourceLoader). keep this until all the previewers are updated + + string xaml; + string resourceName = type.Name + ".xaml"; + string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource; + + Tizen.Log.Fatal("NUI", "the resource path: " + resource); + int windowWidth = Window.Instance.Size.Width; + int windowHeight = Window.Instance.Size.Height; + + string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName; + Tizen.Log.Fatal("NUI", "the resource path: " + likelyResourcePath); + + if (!File.Exists(likelyResourcePath)) + { + likelyResourcePath = resource + "layout/" + resourceName; + } + + //Find the xaml file in the layout folder + if (File.Exists(likelyResourcePath)) + { + StreamReader reader = new StreamReader(likelyResourcePath); + xaml = reader.ReadToEnd(); + reader.Close(); + reader.Dispose(); + Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml); + var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName); + var regex = new Regex(pattern, RegexOptions.ECMAScript); + if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName))) + { + return xaml; + } + else + { + throw new XamlParseException(string.Format("Can't find type {0}", type.FullName), new XmlLineInfo()); + } + } + + return null; + } + + //if the assembly was generated using a version of XamlG that doesn't outputs XamlResourceIdAttributes, we still need to find the resource, and load it + static readonly Dictionary XamlResources = new Dictionary(); + static string LegacyGetXamlForType(Type type) + { + var assembly = type.GetTypeInfo().Assembly; + + string resourceId; + if (XamlResources.TryGetValue(type, out resourceId)) { + var result = ReadResourceAsXaml(type, assembly, resourceId); + if (result != null) + return result; + } + + var likelyResourceName = type.Name + ".xaml"; + var resourceNames = assembly.GetManifestResourceNames(); + string resourceName = null; + + // first pass, pray to find it because the user named it correctly + + foreach (var resource in resourceNames) { + if (ResourceMatchesFilename(assembly, resource, likelyResourceName)) { + resourceName = resource; + var xaml = ReadResourceAsXaml(type, assembly, resource); + if (xaml != null) + return xaml; + } + } + + // okay maybe they at least named it .xaml + + foreach (var resource in resourceNames) { + if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase)) + continue; + + resourceName = resource; + var xaml = ReadResourceAsXaml(type, assembly, resource); + if (xaml != null) + return xaml; + } + + foreach (var resource in resourceNames) { + if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase)) + continue; + + resourceName = resource; + var xaml = ReadResourceAsXaml(type, assembly, resource, true); + if (xaml != null) + return xaml; + } + + return null; + } + + //legacy... + static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename) + { + try { + var info = assembly.GetManifestResourceInfo(resource); + + if (!string.IsNullOrEmpty(info.FileName) && + string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0) + return true; + } + catch (PlatformNotSupportedException) { + // Because Win10 + .NET Native + } + + if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) || + string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0) + return true; + + return false; + } + + //part of the legacy as well... + static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false) + { + using (var stream = assembly.GetManifestResourceStream(likelyTargetName)) + using (var reader = new StreamReader(stream)) { + if (validate) { + // terrible validation of XML. Unfortunately it will probably work most of the time since comments + // also start with a <. We can't bring in any real deps. + + var firstNonWhitespace = (char)reader.Read(); + while (char.IsWhiteSpace(firstNonWhitespace)) + firstNonWhitespace = (char)reader.Read(); + + if (firstNonWhitespace != '<') + return null; + + stream.Seek(0, SeekOrigin.Begin); + } + + var xaml = reader.ReadToEnd(); + + var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName); + var regex = new Regex(pattern, RegexOptions.ECMAScript); + if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName))) + return xaml; + } + return null; + } + + public class RuntimeRootNode : RootNode + { + public RuntimeRootNode(XmlType xmlType, object root, IXmlNamespaceResolver resolver) : base (xmlType, resolver) + { + Root = root; + } + + public object Root { get; internal set; } + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNode.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNode.cs new file mode 100755 index 000000000..5e7f339a6 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNode.cs @@ -0,0 +1,257 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Tizen.NUI.XamlBinding; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.Xaml +{ + internal interface INode + { + List IgnorablePrefixes { get; set; } + + IXmlNamespaceResolver NamespaceResolver { get; } + + INode Parent { get; set; } + + void Accept(IXamlNodeVisitor visitor, INode parentNode); + INode Clone(); + } + + internal interface IValueNode : INode + { + } + + internal interface IElementNode : INode, IListNode + { + Dictionary Properties { get; } + List SkipProperties { get; } + INameScope Namescope { get; } + XmlType XmlType { get; } + string NamespaceURI { get; } + } + + internal interface IListNode : INode + { + List CollectionItems { get; } + } + + [DebuggerDisplay("{NamespaceUri}:{Name}")] + internal class XmlType + { + public XmlType(string namespaceUri, string name, IList typeArguments) + { + NamespaceUri = namespaceUri; + Name = name; + TypeArguments = typeArguments; + } + + public string NamespaceUri { get; } + public string Name { get; } + public IList TypeArguments { get; } + } + + internal abstract class BaseNode : IXmlLineInfo, INode + { + protected BaseNode(IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1) + { + NamespaceResolver = namespaceResolver; + LineNumber = linenumber; + LinePosition = lineposition; + } + + public IXmlNamespaceResolver NamespaceResolver { get; } + public INode Parent { get; set; } + public List IgnorablePrefixes { get; set; } + public int LineNumber { get; set; } + public int LinePosition { get; set; } + + public bool HasLineInfo() => LineNumber >= 0 && LinePosition >= 0; + + public abstract void Accept(IXamlNodeVisitor visitor, INode parentNode); + public abstract INode Clone(); + } + + [DebuggerDisplay("{Value}")] + internal class ValueNode : BaseNode, IValueNode + { + public ValueNode(object value, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1) + : base(namespaceResolver, linenumber, lineposition) + { + Value = value; + } + + public object Value { get; set; } + + public override void Accept(IXamlNodeVisitor visitor, INode parentNode) + { + visitor.Visit(this, parentNode); + } + + public override INode Clone() => new ValueNode(Value, NamespaceResolver, LineNumber, LinePosition) { + IgnorablePrefixes = IgnorablePrefixes + }; + } + + [DebuggerDisplay("{MarkupString}")] + internal class MarkupNode : BaseNode, IValueNode + { + public MarkupNode(string markupString, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1) + : base(namespaceResolver, linenumber, lineposition) + { + MarkupString = markupString; + } + + public string MarkupString { get; } + + public override void Accept(IXamlNodeVisitor visitor, INode parentNode) + { + visitor.Visit(this, parentNode); + } + + public override INode Clone() => new MarkupNode(MarkupString, NamespaceResolver, LineNumber, LinePosition) { + IgnorablePrefixes = IgnorablePrefixes + }; + } + + [DebuggerDisplay("{XmlType.Name}")] + internal class ElementNode : BaseNode, IValueNode, IElementNode + { + public ElementNode(XmlType type, string namespaceURI, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, + int lineposition = -1) + : base(namespaceResolver, linenumber, lineposition) + { + Properties = new Dictionary(); + SkipProperties = new List(); + CollectionItems = new List(); + XmlType = type; + NamespaceURI = namespaceURI; + } + + public Dictionary Properties { get; } + public List SkipProperties { get; } + public List CollectionItems { get; } + public XmlType XmlType { get; } + public string NamespaceURI { get; } + public INameScope Namescope { get; set; } + + public override void Accept(IXamlNodeVisitor visitor, INode parentNode) + { + if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.TopDown) + visitor.Visit(this, parentNode); + + if (!SkipChildren(visitor, this, parentNode)) { + foreach (var node in Properties.Values.ToList()) + node.Accept(visitor, this); + foreach (var node in CollectionItems) + node.Accept(visitor, this); + } + + if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.BottomUp) + visitor.Visit(this, parentNode); + + } + + bool IsDataTemplate(INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode createContent; + if (parentElement != null && + parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) && + createContent == this) + return true; + return false; + } + + protected bool SkipChildren(IXamlNodeVisitor visitor, INode node, INode parentNode) => + (visitor.StopOnDataTemplate && IsDataTemplate(parentNode)) + || (visitor.StopOnResourceDictionary && visitor.IsResourceDictionary(this)) + || visitor.SkipChildren(node, parentNode); + + protected bool SkipVisitNode(IXamlNodeVisitor visitor, INode parentNode) => + !visitor.VisitNodeOnDataTemplate && IsDataTemplate(parentNode); + + public override INode Clone() + { + var clone = new ElementNode(XmlType, NamespaceURI, NamespaceResolver, LineNumber, LinePosition) { + IgnorablePrefixes = IgnorablePrefixes + }; + foreach (var kvp in Properties) + clone.Properties.Add(kvp.Key, kvp.Value.Clone()); + foreach (var p in SkipProperties) + clone.SkipProperties.Add(p); + foreach (var p in CollectionItems) + clone.CollectionItems.Add(p.Clone()); + return clone; + } + } + + internal abstract class RootNode : ElementNode + { + protected RootNode(XmlType xmlType, IXmlNamespaceResolver nsResolver) : base(xmlType, xmlType.NamespaceUri, nsResolver) + { + } + + public override void Accept(IXamlNodeVisitor visitor, INode parentNode) + { + if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.TopDown) + visitor.Visit(this, parentNode); + + if (!SkipChildren(visitor, this, parentNode)) { + foreach (var node in Properties.Values.ToList()) + node.Accept(visitor, this); + foreach (var node in CollectionItems) + node.Accept(visitor, this); + } + + if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.BottomUp) + visitor.Visit(this, parentNode); + } + } + + internal class ListNode : BaseNode, IListNode, IValueNode + { + public ListNode(IList nodes, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1) + : base(namespaceResolver, linenumber, lineposition) + { + CollectionItems = nodes.ToList(); + } + + public XmlName XmlName { get; set; } + public List CollectionItems { get; set; } + + public override void Accept(IXamlNodeVisitor visitor, INode parentNode) + { + if (visitor.VisitingMode == TreeVisitingMode.TopDown) + visitor.Visit(this, parentNode); + foreach (var node in CollectionItems) + node.Accept(visitor, this); + if (visitor.VisitingMode == TreeVisitingMode.BottomUp) + visitor.Visit(this, parentNode); + } + + public override INode Clone() + { + var items = new List(); + foreach (var p in CollectionItems) + items.Add(p.Clone()); + return new ListNode(items, NamespaceResolver, LineNumber, LinePosition) { + IgnorablePrefixes = IgnorablePrefixes + }; + } + } + + internal static class INodeExtensions + { + public static bool SkipPrefix(this INode node, string prefix) + { + do { + if (node.IgnorablePrefixes != null && node.IgnorablePrefixes.Contains(prefix)) + return true; + node = node.Parent; + } while (node != null); + return false; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNodeVisitor.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNodeVisitor.cs new file mode 100755 index 000000000..68687809f --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlNodeVisitor.cs @@ -0,0 +1,51 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + internal interface IXamlNodeVisitor + { + TreeVisitingMode VisitingMode { get; } + bool StopOnDataTemplate { get; } + bool VisitNodeOnDataTemplate { get; } + bool StopOnResourceDictionary { get; } + + void Visit(ValueNode node, INode parentNode); + void Visit(MarkupNode node, INode parentNode); + void Visit(ElementNode node, INode parentNode); + void Visit(RootNode node, INode parentNode); + void Visit(ListNode node, INode parentNode); + bool SkipChildren(INode node, INode parentNode); + bool IsResourceDictionary(ElementNode node); + } + + internal enum TreeVisitingMode { + TopDown, + BottomUp + } + + internal class XamlNodeVisitor : IXamlNodeVisitor + { + readonly Action action; + + public XamlNodeVisitor(Action action, TreeVisitingMode visitingMode = TreeVisitingMode.TopDown, bool stopOnDataTemplate = false, bool visitNodeOnDataTemplate = true) + { + this.action = action; + VisitingMode = visitingMode; + StopOnDataTemplate = stopOnDataTemplate; + VisitNodeOnDataTemplate = visitNodeOnDataTemplate; + } + + public TreeVisitingMode VisitingMode { get; } + public bool StopOnDataTemplate { get; } + public bool StopOnResourceDictionary { get; } + public bool VisitNodeOnDataTemplate { get; } + + public void Visit(ValueNode node, INode parentNode) => action(node, parentNode); + public void Visit(MarkupNode node, INode parentNode) => action(node, parentNode); + public void Visit(ElementNode node, INode parentNode) => action(node, parentNode); + public void Visit(RootNode node, INode parentNode) => action(node, parentNode); + public void Visit(ListNode node, INode parentNode) => action(node, parentNode); + public bool SkipChildren(INode node, INode parentNode) => false; + public bool IsResourceDictionary(ElementNode node) => false; + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParseException.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParseException.cs new file mode 100755 index 000000000..d96e83751 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParseException.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Xml; + +namespace Tizen.NUI.Xaml +{ + internal class XamlParseException : Exception + { + readonly string _unformattedMessage; + + static private StringBuilder GetStackInfo() + { + StringBuilder ret = new StringBuilder("\nStack:\n"); + + StackTrace st = new StackTrace(true); + + for (int i = 2; i < st.FrameCount; i++) + { + StackFrame sf = st.GetFrame(i); + ret.AppendFormat("File:{0}, Method:{1}, Line:{2}\n", sf.GetFileName(), sf.GetMethod().Name, sf.GetFileLineNumber()); + } + + return ret; + } + + public XamlParseException(string message, IXmlLineInfo xmlInfo, Exception innerException = null) : base(FormatMessage(message + GetStackInfo(), xmlInfo), innerException) + { + _unformattedMessage = message; + XmlInfo = xmlInfo; + } + + public IXmlLineInfo XmlInfo { get; private set; } + + internal string UnformattedMessage + { + get { return _unformattedMessage ?? Message; } + } + + static string FormatMessage(string message, IXmlLineInfo xmlinfo) + { + if (xmlinfo == null || !xmlinfo.HasLineInfo()) + return message; + return string.Format("Position {0}:{1}. {2}", xmlinfo.LineNumber, xmlinfo.LinePosition, message); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParser.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParser.cs new file mode 100755 index 000000000..45d7a88a4 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XamlParser.cs @@ -0,0 +1,413 @@ +// +// XamlParser.cs +// +// Author: +// Stephane Delcroix +// +// Copyright (c) 2013 Mobile Inception +// Copyright (c) 2013-2014 Xamarin, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Xml; +using Tizen.NUI.XamlBinding; +using Tizen.NUI.Xaml.Forms.BaseComponents; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.Xaml +{ + internal static class XamlParser + { + public const string XFUri = "http://tizen.org/Tizen.NUI/2018/XAML"; + public const string NUI2018Uri = "http://tizen.org/Tizen.NUI/2018/XAML"; + public const string X2006Uri = "http://schemas.microsoft.com/winfx/2006/xaml"; + public const string X2009Uri = "http://schemas.microsoft.com/winfx/2009/xaml"; + public const string McUri = "http://schemas.openxmlformats.org/markup-compatibility/2006"; + + public static void ParseXaml(RootNode rootNode, XmlReader reader) + { + IList> xmlns; + var attributes = ParseXamlAttributes(reader, out xmlns); + var prefixes = PrefixesToIgnore(xmlns); + (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes=new List())).AddRange(prefixes); + rootNode.Properties.AddRange(attributes); + ParseXamlElementFor(rootNode, reader); + } + + static void ParseXamlElementFor(IElementNode node, XmlReader reader) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + var elementName = reader.Name; + var isEmpty = reader.IsEmptyElement; + + if (isEmpty) + return; + + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + Debug.Assert(reader.Name == elementName); //make sure we close the right element + return; + case XmlNodeType.Element: + // 1. Property Element. + if (reader.Name.Contains(".")) + { + XmlName name; + if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal)) + name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1)); + else //Attached DP + name = new XmlName(reader.NamespaceURI, reader.LocalName); + + var prop = ReadNode(reader); + if (prop != null) + node.Properties.Add(name, prop); + } + // 2. Xaml2009 primitives, x:Arguments, ... + else if (reader.NamespaceURI == X2009Uri && reader.LocalName == "Arguments") + { + var prop = ReadNode(reader); + if (prop != null) + node.Properties.Add(XmlName.xArguments, prop); + } + // 3. DataTemplate (should be handled by 4.) + else if ((node.XmlType.NamespaceUri == XFUri || node.XmlType.NamespaceUri == NUI2018Uri) && + (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate")) + { + var prop = ReadNode(reader, true); + if (prop != null) + node.Properties.Add(XmlName._CreateContent, prop); + } + // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later. + else + { + var item = ReadNode(reader, true); + if (item != null) + node.CollectionItems.Add(item); + } + break; + case XmlNodeType.Whitespace: + break; + case XmlNodeType.Text: + case XmlNodeType.CDATA: + if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode) + ((ValueNode)node.CollectionItems[0]).Value += reader.Value.Trim(); + else + node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader)); + break; + default: + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + break; + } + } + } + + static INode ReadNode(XmlReader reader, bool nested = false) + { + var skipFirstRead = nested; + Debug.Assert(reader.NodeType == XmlNodeType.Element); + var name = reader.Name; + List nodes = new List(); + INode node = null; + + while (skipFirstRead || reader.Read()) + { + skipFirstRead = false; + + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + Debug.Assert(reader.Name == name); + if (nodes.Count == 0) //Empty element + return null; + if (nodes.Count == 1) + return nodes[0]; + return new ListNode(nodes, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber, + ((IXmlLineInfo)reader).LinePosition); + case XmlNodeType.Element: + var isEmpty = reader.IsEmptyElement && reader.Name == name; + var elementName = reader.Name; + var elementNsUri = reader.NamespaceURI; + var elementXmlInfo = (IXmlLineInfo)reader; + IList> xmlns; + + var attributes = ParseXamlAttributes(reader, out xmlns); + var prefixes = PrefixesToIgnore(xmlns); + + IList typeArguments = null; + if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments)) + { + typeArguments = + ((ValueNode)attributes.First(kvp => kvp.Key == XmlName.xTypeArguments).Value).Value as IList; + } + + node = new ElementNode(new XmlType(elementNsUri, elementName, typeArguments), elementNsUri, + reader as IXmlNamespaceResolver, elementXmlInfo.LineNumber, elementXmlInfo.LinePosition); + ((IElementNode)node).Properties.AddRange(attributes); + (node.IgnorablePrefixes ?? (node.IgnorablePrefixes = new List()))?.AddRange(prefixes); + + ParseXamlElementFor((IElementNode)node, reader); + nodes.Add(node); + if (isEmpty || nested) + return node; + break; + case XmlNodeType.Text: + node = new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber, + ((IXmlLineInfo)reader).LinePosition); + nodes.Add(node); + break; + case XmlNodeType.Whitespace: + break; + default: + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + break; + } + } + throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader); + } + + static IList> ParseXamlAttributes(XmlReader reader, out IList> xmlns) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + var attributes = new List>(); + xmlns = new List>(); + for (var i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + //skip xmlns + if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/") { + xmlns.Add(new KeyValuePair(reader.LocalName, reader.Value)); + continue; + } + + var namespaceUri = reader.NamespaceURI; + if (reader.LocalName.Contains(".") && namespaceUri == "") + namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace(""); + var propertyName = new XmlName(namespaceUri, reader.LocalName); + + object value = reader.Value; + + if (reader.NamespaceURI == X2006Uri) + { + switch (reader.Name) { + case "x:Key": + propertyName = XmlName.xKey; + break; + case "x:Name": + propertyName = XmlName.xName; + break; + case "x:Class": + case "x:FieldModifier": + continue; + default: + Debug.WriteLine("Unhandled attribute {0}", reader.Name); + continue; + } + } + + if (reader.NamespaceURI == X2009Uri) + { + switch (reader.Name) { + case "x:Key": + propertyName = XmlName.xKey; + break; + case "x:Name": + propertyName = XmlName.xName; + break; + case "x:TypeArguments": + propertyName = XmlName.xTypeArguments; + value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader); + break; + case "x:DataType": + propertyName = XmlName.xDataType; + break; + case "x:Class": + case "x:FieldModifier": + continue; + case "x:FactoryMethod": + propertyName = XmlName.xFactoryMethod; + break; + case "x:Arguments": + propertyName = XmlName.xArguments; + break; + default: + Debug.WriteLine("Unhandled attribute {0}", reader.Name); + continue; + } + } + + var propertyNode = GetValueNode(value, reader); + attributes.Add(new KeyValuePair(propertyName, propertyNode)); + } + reader.MoveToElement(); + return attributes; + } + + static IList PrefixesToIgnore(IList> xmlns) + { + var prefixes = new List(); + foreach (var kvp in xmlns) { + var prefix = kvp.Key; + + string typeName = null, ns = null, asm = null, targetPlatform = null; + XmlnsHelper.ParseXmlns(kvp.Value, out typeName, out ns, out asm, out targetPlatform); + if (targetPlatform == null) + continue; + try { + if (targetPlatform != Device.RuntimePlatform) + { + // Special case for Windows backward compatibility + if (targetPlatform == "Windows" && Device.RuntimePlatform == Device.UWP) + continue; + + prefixes.Add(prefix); + } + } catch (InvalidOperationException) { + prefixes.Add(prefix); + } + } + return prefixes; + } + + static IValueNode GetValueNode(object value, XmlReader reader) + { + var valueString = value as string; + if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal)) + { + return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber, + ((IXmlLineInfo)reader).LinePosition); + } + if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal)) + { + return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber, + ((IXmlLineInfo)reader).LinePosition); + } + return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber, + ((IXmlLineInfo)reader).LinePosition); + } + + static IList s_xmlnsDefinitions; + public static IList s_assemblies = new List();// = new Assembly[]{}; + + static void GatherXmlnsDefinitionAttributes() + { + //this could be extended to look for [XmlnsDefinition] in all assemblies + // var assemblies = new [] { + // typeof(View).GetTypeInfo().Assembly, + // //typeof(XamlLoader).GetTypeInfo().Assembly, + // }; + // s_assemblies = new Assembly[]{typeof(View).GetTypeInfo().Assembly}; + s_assemblies.Add(typeof(View).GetTypeInfo().Assembly); + + s_xmlnsDefinitions = new List(); + + foreach (var assembly in s_assemblies) + foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute))) { + s_xmlnsDefinitions.Add(attribute); + attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName; + } + } + + public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly, + out XamlParseException exception) + { + if (s_xmlnsDefinitions == null) + GatherXmlnsDefinitionAttributes(); + + var namespaceURI = xmlType.NamespaceUri; + var elementName = xmlType.Name; + var typeArguments = xmlType.TypeArguments; + exception = null; + + var lookupAssemblies = new List(); + var lookupNames = new List(); + + foreach (var xmlnsDef in s_xmlnsDefinitions) { + if (xmlnsDef.XmlNamespace != namespaceURI) + continue; + lookupAssemblies.Add(xmlnsDef); + } + + if (lookupAssemblies.Count == 0) { + string ns, asmstring, _; + XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring, out _); + lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns) { + AssemblyName = asmstring ?? currentAssembly.FullName + }); + } + + lookupNames.Add(elementName); + lookupNames.Add(elementName + "Extension"); + + for (var i = 0; i < lookupNames.Count; i++) + { + var name = lookupNames[i]; + if (name.Contains(":")) + name = name.Substring(name.LastIndexOf(':') + 1); + if (typeArguments != null) + name += "`" + typeArguments.Count; //this will return an open generic Type + lookupNames[i] = name; + } + + Type type = null; + foreach (var asm in lookupAssemblies) { + foreach (var name in lookupNames) + if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null) + break; + if (type != null) + break; + } + + if (type != null && typeArguments != null) + { + XamlParseException innerexception = null; + var args = typeArguments.Select(delegate(XmlType xmltype) + { + XamlParseException xpe; + var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe); + if (xpe != null) + { + innerexception = xpe; + return null; + } + return t; + }).ToArray(); + if (innerexception != null) + { + exception = innerexception; + return null; + } + type = type.MakeGenericType(args); + } + + if (type == null) + exception = new XamlParseException($"Type {elementName} not found in xmlns {namespaceURI}", xmlInfo); + + return type; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlName.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlName.cs new file mode 100755 index 000000000..7cb0a26c1 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlName.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; + +namespace Tizen.NUI.Xaml +{ + [DebuggerDisplay("{NamespaceURI}:{LocalName}")] + internal struct XmlName + { + public static readonly XmlName _CreateContent = new XmlName("_", "CreateContent"); + public static readonly XmlName xKey = new XmlName("x", "Key"); + public static readonly XmlName xName = new XmlName("x", "Name"); + public static readonly XmlName xTypeArguments = new XmlName("x", "TypeArguments"); + public static readonly XmlName xArguments = new XmlName("x", "Arguments"); + public static readonly XmlName xFactoryMethod = new XmlName("x", "FactoryMethod"); + public static readonly XmlName xDataType = new XmlName("x", "DataType"); + public static readonly XmlName Empty = new XmlName(); + + public string NamespaceURI { get; } + public string LocalName { get; } + + public XmlName(string namespaceUri, string localName) + { + NamespaceURI = namespaceUri; + LocalName = localName; + } + + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (obj.GetType() != typeof (XmlName)) + return false; + var other = (XmlName)obj; + return NamespaceURI == other.NamespaceURI && LocalName == other.LocalName; + } + + public bool Equals(string namespaceUri, string localName) + => Equals(new XmlName(namespaceUri, localName)); + + public override int GetHashCode() + { + unchecked + { + int hashCode = 0; + if (NamespaceURI != null) + hashCode = NamespaceURI.GetHashCode(); + if (LocalName != null) + hashCode = (hashCode * 397) ^ LocalName.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(XmlName x1, XmlName x2) + => x1.NamespaceURI == x2.NamespaceURI && x1.LocalName == x2.LocalName; + + public static bool operator !=(XmlName x1, XmlName x2) + => !(x1 == x2); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlnsHelper.cs b/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlnsHelper.cs new file mode 100755 index 000000000..8f86874fd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/Xaml/XmlnsHelper.cs @@ -0,0 +1,75 @@ +using System; + +namespace Tizen.NUI.Xaml +{ + internal static class XmlnsHelper + { + public static string ParseNamespaceFromXmlns(string xmlns) + { + string typeName; + string ns; + string asm; + string targetPlatform; + + ParseXmlns(xmlns, out typeName, out ns, out asm, out targetPlatform); + + return ns; + } + + public static void ParseXmlns(string xmlns, out string typeName, out string ns, out string asm, out string targetPlatform) + { + typeName = ns = asm = targetPlatform = null; + + xmlns = xmlns.Trim(); + + if (xmlns.StartsWith("using:", StringComparison.Ordinal)) { + ParseUsing(xmlns, out typeName, out ns, out asm, out targetPlatform); + return; + } + ParseClrNamespace(xmlns, out typeName, out ns, out asm, out targetPlatform); + } + + static void ParseClrNamespace(string xmlns, out string typeName, out string ns, out string asm, out string targetPlatform) + { + typeName = ns = asm = targetPlatform = null; + + foreach (var decl in xmlns.Split(';')) + { + if (decl.StartsWith("clr-namespace:", StringComparison.Ordinal)) + { + ns = decl.Substring(14, decl.Length - 14); + continue; + } + if (decl.StartsWith("assembly=", StringComparison.Ordinal)) + { + asm = decl.Substring(9, decl.Length - 9); + continue; + } + if (decl.StartsWith("targetPlatform=", StringComparison.Ordinal)) { + targetPlatform = decl.Substring(15, decl.Length - 15); + continue; + } + var nsind = decl.LastIndexOf(".", StringComparison.Ordinal); + if (nsind > 0) + { + ns = decl.Substring(0, nsind); + typeName = decl.Substring(nsind + 1, decl.Length - nsind - 1); + } + else + typeName = decl; + } + } + + static void ParseUsing(string xmlns, out string typeName, out string ns, out string asm, out string targetPlatform) + { + typeName = ns = asm = targetPlatform = null; + + foreach (var decl in xmlns.Split(';')) { + if (decl.StartsWith("using:", StringComparison.Ordinal)) { + ns = decl.Substring(6, decl.Length - 6); + continue; + } + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Accelerator.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Accelerator.cs new file mode 100755 index 000000000..c5741859a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Accelerator.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Tizen.NUI.XamlBinding +{ + [System.ComponentModel.TypeConverter(typeof(AcceleratorTypeConverter))] + internal class Accelerator + { + const char Separator = '+'; + string _text; + + internal Accelerator(string text) + { + if (string.IsNullOrEmpty(text)) + throw new ArgumentNullException(nameof(text)); + _text = text; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable Modifiers { get; set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable Keys { get; set; } + + public static Accelerator FromString(string text) + { + var accelarat = new Accelerator(text); + + var acceleratorParts = text.Split(Separator); + + if (acceleratorParts.Length > 1) + { + var modifiers = new List(); + for (int i = 0; i < acceleratorParts.Length; i++) + { + var modifierMask = acceleratorParts[i]; + var modiferMaskLower = modifierMask.ToLower(); + switch (modiferMaskLower) + { + case "ctrl": + case "cmd": + case "alt": + case "shift": + case "fn": + case "win": + modifiers.Add(modiferMaskLower); + text = text.Replace(modifierMask, ""); + break; + } + } + accelarat.Modifiers = modifiers; + + } + + var keys = text.Split(new char[] { Separator }, StringSplitOptions.RemoveEmptyEntries); + accelarat.Keys = keys; + return accelarat; + } + + public override string ToString() + { + return _text; + } + + public override bool Equals(object obj) + { + return obj != null && obj is Accelerator && Equals((Accelerator)obj); + } + + bool Equals(Accelerator other) + { + return other.ToString() == ToString(); + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + + public static implicit operator Accelerator(string accelerator) + { + return FromString(accelerator); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AcceleratorTypeConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AcceleratorTypeConverter.cs new file mode 100755 index 000000000..b1560fdf6 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AcceleratorTypeConverter.cs @@ -0,0 +1,13 @@ +namespace Tizen.NUI.XamlBinding +{ + internal class AcceleratorTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value == null) + return null; + + return Accelerator.FromString(value); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ActionSheetArguments.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ActionSheetArguments.cs new file mode 100755 index 000000000..1e9d7cd8c --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ActionSheetArguments.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding +{ + internal class ActionSheetArguments + { + public ActionSheetArguments(string title, string cancel, string destruction, IEnumerable buttons) + { + Title = title; + Cancel = cancel; + Destruction = destruction; + Buttons = buttons; + Result = new TaskCompletionSource(); + } + + /// + /// Gets titles of any buttons on the action sheet that aren't or . Can + /// be null. + /// + public IEnumerable Buttons { get; private set; } + + /// + /// Gets the text for a cancel button. Can be null. + /// + public string Cancel { get; private set; } + + /// + /// Gets the text for a destructive button. Can be null. + /// + public string Destruction { get; private set; } + + public TaskCompletionSource Result { get; } + + /// + /// Gets the title for the action sheet. Can be null. + /// + public string Title { get; private set; } + + public void SetResult(string result) + { + Result.TrySetResult(result); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AlertArguments.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AlertArguments.cs new file mode 100755 index 000000000..77fab01fa --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/AlertArguments.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding +{ + internal class AlertArguments + { + public AlertArguments(string title, string message, string accept, string cancel) + { + Title = title; + Message = message; + Accept = accept; + Cancel = cancel; + Result = new TaskCompletionSource(); + } + + /// + /// Gets the text for the accept button. Can be null. + /// + public string Accept { get; private set; } + + /// + /// Gets the text of the cancel button. + /// + public string Cancel { get; private set; } + + /// + /// Gets the message for the alert. Can be null. + /// + public string Message { get; private set; } + + public TaskCompletionSource Result { get; } + + /// + /// Gets the title for the alert. Can be null. + /// + public string Title { get; private set; } + + public void SetResult(bool result) + { + Result.TrySetResult(result); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BaseMenuItem.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BaseMenuItem.cs new file mode 100755 index 000000000..d71ceec82 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BaseMenuItem.cs @@ -0,0 +1,6 @@ +namespace Tizen.NUI.XamlBinding +{ + internal abstract class BaseMenuItem : Element + { + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindableObjectExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindableObjectExtensions.cs new file mode 100755 index 000000000..29c2f5aa8 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindableObjectExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq.Expressions; + +namespace Tizen.NUI.XamlBinding +{ + internal static class BindableObjectExtensions + { + public static void SetBinding(this BindableObject self, BindableProperty targetProperty, string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, + string stringFormat = null) + { + if (self == null) + throw new ArgumentNullException("self"); + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + + var binding = new Binding(path, mode, converter, stringFormat: stringFormat); + self.SetBinding(targetProperty, binding); + } + + [Obsolete] + public static void SetBinding(this BindableObject self, BindableProperty targetProperty, Expression> sourceProperty, BindingMode mode = BindingMode.Default, + IValueConverter converter = null, string stringFormat = null) + { + if (self == null) + throw new ArgumentNullException("self"); + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + if (sourceProperty == null) + throw new ArgumentNullException("sourceProperty"); + + Binding binding = Binding.Create(sourceProperty, mode, converter, stringFormat: stringFormat); + self.SetBinding(targetProperty, binding); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingBaseExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingBaseExtensions.cs new file mode 100755 index 000000000..740807967 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingBaseExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal static class BindingBaseExtensions + { + public static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property) + { + return self.Mode != BindingMode.Default ? self.Mode : property.DefaultBindingMode; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingExpression.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingExpression.cs new file mode 100755 index 000000000..880014d8f --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingExpression.cs @@ -0,0 +1,644 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Tizen.NUI.XamlBinding.Internals; +using System.Runtime.CompilerServices; + +namespace Tizen.NUI.XamlBinding +{ + internal class BindingExpression + { + internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'"; + + readonly List _parts = new List(); + + BindableProperty _targetProperty; + WeakReference _weakSource; + WeakReference _weakTarget; + + internal BindingExpression(BindingBase binding, string path) + { + if (binding == null) + throw new ArgumentNullException(nameof(binding)); + if (path == null) + throw new ArgumentNullException(nameof(path)); + + Binding = binding; + Path = path; + + ParsePath(); + } + + internal BindingBase Binding { get; } + + internal string Path { get; } + + /// + /// Applies the binding expression to a previously set source and target. + /// + internal void Apply(bool fromTarget = false) + { + if (_weakSource == null || _weakTarget == null) + return; + + BindableObject target; + if (!_weakTarget.TryGetTarget(out target)) + { + Unapply(); + return; + } + + object source; + if (_weakSource.TryGetTarget(out source) && _targetProperty != null) + ApplyCore(source, target, _targetProperty, fromTarget); + } + + /// + /// Applies the binding expression to a new source or target. + /// + internal void Apply(object sourceObject, BindableObject target, BindableProperty property) + { + _targetProperty = property; + + BindableObject prevTarget; + if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target)) + throw new InvalidOperationException("Binding instances can not be reused"); + + object previousSource; + if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject)) + throw new InvalidOperationException("Binding instances can not be reused"); + + _weakSource = new WeakReference(sourceObject); + _weakTarget = new WeakReference(target); + + ApplyCore(sourceObject, target, property); + } + + internal void Unapply() + { + object sourceObject; + if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject)) + { + for (var i = 0; i < _parts.Count - 1; i++) + { + BindingExpressionPart part = _parts[i]; + + if (!part.IsSelf) + { + part.TryGetValue(sourceObject, out sourceObject); + } + + part.Unsubscribe(); + } + } + + _weakSource = null; + _weakTarget = null; + } + + /// + /// Applies the binding expression to a previously set source or target. + /// + void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false) + { + BindingMode mode = Binding.GetRealizedMode(_targetProperty); + if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget) + return; + + bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime; + bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource); + + object current = sourceObject; + object previous = null; + BindingExpressionPart part = null; + + for (var i = 0; i < _parts.Count; i++) + { + part = _parts[i]; + bool isLast = i + 1 == _parts.Count; + + if (!part.IsSelf && current != null) + { + // Allow the object instance itself to provide its own TypeInfo + var reflectable = current as IReflectableType; + System.Reflection.TypeInfo currentType = reflectable != null ? reflectable.GetTypeInfo() : current.GetType().GetTypeInfo(); + if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType)) + SetupPart(currentType, part); + + if (!isLast) + part.TryGetValue(current, out current); + } + + if (!part.IsSelf && current != null) + { + if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null)) + { + Console.WriteLine("Binding", PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName); + break; + } + } + + if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) + { + var inpc = current as INotifyPropertyChanged; + if (inpc != null && !ReferenceEquals(current, previous)) + part.Subscribe(inpc); + } + + previous = current; + } + + Debug.Assert(part != null, "There should always be at least the self part in the expression."); + + if (needsGetter) + { + object value = property.DefaultValue; + if (part.TryGetValue(current, out value) || part.IsSelf) + { + value = Binding.GetSourceValue(value, property.ReturnType); + } + else + value = property.DefaultValue; + + if (!TryConvert(part, ref value, property.ReturnType, true)) + { + Console.WriteLine("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType); + return; + } + + target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted); + } + else if (needsSetter && part.LastSetter != null && current != null) + { + object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType); + + if (!TryConvert(part, ref value, part.SetterType, false)) + { + Console.WriteLine("Binding", "{0} can not be converted to type '{1}'", value, part.SetterType); + return; + } + + object[] args; + if (part.IsIndexer) + { + args = new object[part.Arguments.Length + 1]; + part.Arguments.CopyTo(args, 0); + args[args.Length - 1] = value; + } + else if (part.IsBindablePropertySetter) + { + args = new[] { part.BindablePropertyField, value }; + } + else + { + args = new[] { value }; + } + + part.LastSetter.Invoke(current, args); + } + } + + IEnumerable GetPart(string part) + { + part = part.Trim(); + if (part == string.Empty) + throw new FormatException("Path contains an empty part"); + + BindingExpressionPart indexer = null; + + int lbIndex = part.IndexOf('['); + if (lbIndex != -1) + { + int rbIndex = part.LastIndexOf(']'); + if (rbIndex == -1) + throw new FormatException("Indexer did not contain closing bracket"); + + int argLength = rbIndex - lbIndex - 1; + if (argLength == 0) + throw new FormatException("Indexer did not contain arguments"); + + string argString = part.Substring(lbIndex + 1, argLength); + indexer = new BindingExpressionPart(this, argString, true); + + part = part.Substring(0, lbIndex); + part = part.Trim(); + } + + if (part.Length > 0) + yield return new BindingExpressionPart(this, part); + if (indexer != null) + yield return indexer; + } + + void ParsePath() + { + string p = Path.Trim(); + + var last = new BindingExpressionPart(this, "."); + _parts.Add(last); + + if (p[0] == '.') + { + if (p.Length == 1) + return; + + p = p.Substring(1); + } + + string[] pathParts = p.Split('.'); + for (var i = 0; i < pathParts.Length; i++) + { + foreach (BindingExpressionPart part in GetPart(pathParts[i])) + { + last.NextPart = part; + _parts.Add(part); + last = part; + } + } + } + + void SetupPart(System.Reflection.TypeInfo sourceType, BindingExpressionPart part) + { + part.Arguments = null; + part.LastGetter = null; + part.LastSetter = null; + + PropertyInfo property = null; + if (part.IsIndexer) + { + if (sourceType.IsArray) + { + int index; + if (!int.TryParse(part.Content, out index)) + Console.WriteLine("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType); + else + part.Arguments = new object[] { index }; + + part.LastGetter = sourceType.GetDeclaredMethod("Get"); + part.LastSetter = sourceType.GetDeclaredMethod("Set"); + part.SetterType = sourceType.GetElementType(); + } + + DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType().FirstOrDefault(); + string indexerName = defaultMember != null ? defaultMember.MemberName : "Item"; + + part.IndexerName = indexerName; + +#if NETSTANDARD2_0 + try { + property = sourceType.GetDeclaredProperty(indexerName); + } + catch (AmbiguousMatchException) { + // Get most derived instance of property + foreach (var p in sourceType.GetProperties().Where(prop => prop.Name == indexerName)) { + if (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType)) + property = p; + } + } +#else + property = sourceType.GetDeclaredProperty(indexerName); +#endif + + if (property == null) //is the indexer defined on the base class? + property = sourceType.BaseType.GetProperty(indexerName); + if (property == null) //is the indexer defined on implemented interface ? + { + foreach (var implementedInterface in sourceType.ImplementedInterfaces) + { + property = implementedInterface.GetProperty(indexerName); + if (property != null) + break; + } + } + + if (property != null) + { + ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault(); + if (parameter != null) + { + try + { + object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture); + part.Arguments = new[] { arg }; + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + catch (OverflowException) + { + } + } + } + } + else + property = sourceType.GetDeclaredProperty(part.Content) ?? sourceType.BaseType?.GetProperty(part.Content); + + if (property != null) + { + if (property.CanRead && property.GetMethod.IsPublic && !property.GetMethod.IsStatic) + part.LastGetter = property.GetMethod; + if (property.CanWrite && property.SetMethod.IsPublic && !property.SetMethod.IsStatic) + { + part.LastSetter = property.SetMethod; + part.SetterType = part.LastSetter.GetParameters().Last().ParameterType; + + if (Binding.AllowChaining) + { + FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property"); + if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController))) + { + MethodInfo setValueMethod = null; +#if NETSTANDARD1_0 + foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods()) + { + if (m.Name.EndsWith("IElementController.SetValueFromRenderer")) + { + ParameterInfo[] parameters = m.GetParameters(); + if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty)) + { + setValueMethod = m; + break; + } + } + } +#else + setValueMethod = typeof(IElementController).GetMethod("SetValueFromRenderer", new[] { typeof(BindableProperty), typeof(object) }); +#endif + if (setValueMethod != null) + { + part.LastSetter = setValueMethod; + part.IsBindablePropertySetter = true; + part.BindablePropertyField = bindablePropertyField.GetValue(null); + } + } + } + } +#if !NETSTANDARD1_0 + //TupleElementNamesAttribute tupleEltNames; + //if (property != null + // && part.NextPart != null + // && property.PropertyType.IsGenericType + // && (property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>) + // || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>)) + // && (tupleEltNames = property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) as TupleElementNamesAttribute) != null) + //{ + // // modify the nextPart to access the tuple item via the ITuple indexer + // var nextPart = part.NextPart; + // var name = nextPart.Content; + // var index = tupleEltNames.TransformNames.IndexOf(name); + // if (index >= 0) + // { + // nextPart.IsIndexer = true; + // nextPart.Content = index.ToString(); + // } + //} +#endif + } + + } + static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) }; + + bool TryConvert(BindingExpressionPart part, ref object value, Type convertTo, bool toTarget) + { + if (value == null) + return true; + if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value))) + return true; + + object original = value; + try + { + var stringValue = value as string ?? string.Empty; + // see: https://bugzilla.xamarin.com/show_bug.cgi?id=32871 + // do not canonicalize "*.[.]"; "1." should not update bound BindableProperty + if (stringValue.EndsWith(".") && DecimalTypes.Contains(convertTo)) + throw new FormatException(); + + // do not canonicalize "-0"; user will likely enter a period after "-0" + if (stringValue == "-0" && DecimalTypes.Contains(convertTo)) + throw new FormatException(); + + value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture); + return true; + } + catch (InvalidCastException) + { + value = original; + return false; + } + catch (FormatException) + { + value = original; + return false; + } + catch (OverflowException) + { + value = original; + return false; + } + } + + class BindingPair + { + public BindingPair(BindingExpressionPart part, object source, bool isLast) + { + Part = part; + Source = source; + IsLast = isLast; + } + + public bool IsLast { get; set; } + + public BindingExpressionPart Part { get; private set; } + + public object Source { get; private set; } + } + + internal class WeakPropertyChangedProxy + { + readonly WeakReference _source = new WeakReference(null); + readonly WeakReference _listener = new WeakReference(null); + readonly PropertyChangedEventHandler _handler; + readonly EventHandler _bchandler; + internal WeakReference Source => _source; + + public WeakPropertyChangedProxy() + { + _handler = new PropertyChangedEventHandler(OnPropertyChanged); + _bchandler = new EventHandler(OnBCChanged); + } + + public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this() + { + SubscribeTo(source, listener); + } + + public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener) + { + source.PropertyChanged += _handler; + var bo = source as BindableObject; + if (bo != null) + bo.BindingContextChanged += _bchandler; + _source.SetTarget(source); + _listener.SetTarget(listener); + } + + public void Unsubscribe() + { + INotifyPropertyChanged source; + if (_source.TryGetTarget(out source) && source != null) + source.PropertyChanged -= _handler; + var bo = source as BindableObject; + if (bo != null) + bo.BindingContextChanged -= _bchandler; + + _source.SetTarget(null); + _listener.SetTarget(null); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + PropertyChangedEventHandler handler; + if (_listener.TryGetTarget(out handler) && handler != null) + handler(sender, e); + else + Unsubscribe(); + } + + void OnBCChanged(object sender, EventArgs e) + { + OnPropertyChanged(sender, new PropertyChangedEventArgs("BindingContext")); + } + } + + class BindingExpressionPart + { + readonly BindingExpression _expression; + readonly PropertyChangedEventHandler _changeHandler; + WeakPropertyChangedProxy _listener; + + public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false) + { + _expression = expression; + IsSelf = content == Tizen.NUI.XamlBinding.Binding.SelfPath; + Content = content; + IsIndexer = isIndexer; + + _changeHandler = PropertyChanged; + } + + public void Subscribe(INotifyPropertyChanged handler) + { + INotifyPropertyChanged source; + if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source)) + // Already subscribed + return; + + // Clear out the old subscription if necessary + Unsubscribe(); + + _listener = new WeakPropertyChangedProxy(handler, _changeHandler); + } + + public void Unsubscribe() + { + var listener = _listener; + if (listener != null) + { + listener.Unsubscribe(); + _listener = null; + } + } + + public object[] Arguments { get; set; } + + public object BindablePropertyField { get; set; } + + public string Content { get; internal set; } + + public string IndexerName { get; set; } + + public bool IsBindablePropertySetter { get; set; } + + public bool IsIndexer { get; internal set; } + + public bool IsSelf { get; } + + public MethodInfo LastGetter { get; set; } + + public MethodInfo LastSetter { get; set; } + + public BindingExpressionPart NextPart { get; set; } + + public Type SetterType { get; set; } + + public void PropertyChanged(object sender, PropertyChangedEventArgs args) + { + BindingExpressionPart part = NextPart ?? this; + + string name = args.PropertyName; + + if (!string.IsNullOrEmpty(name)) + { + if (part.IsIndexer) + { + if (name.Contains("[")) + { + if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content)) + return; + } + else if (name != part.IndexerName) + return; + } + else if (name != part.Content) + { + return; + } + } + + _expression.Apply(); + // Device.BeginInvokeOnMainThread(() => _expression.Apply()); + } + + public bool TryGetValue(object source, out object value) + { + value = source; + + if (LastGetter != null && value != null) + { + if (IsIndexer) + { + try + { + value = LastGetter.Invoke(value, Arguments); + } + catch (TargetInvocationException ex) + { + if (!(ex.InnerException is KeyNotFoundException)) + throw; + value = null; + } + return true; + } + value = LastGetter.Invoke(value, Arguments); + return true; + } + + return false; + } + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingTypeConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingTypeConverter.cs new file mode 100755 index 000000000..c1e58cc64 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/BindingTypeConverter.cs @@ -0,0 +1,12 @@ +namespace Tizen.NUI.XamlBinding +{ + [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Forms.XamlC.BindingTypeConverter")] + [Xaml.TypeConversion(typeof(Binding))] + internal sealed class BindingTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + return new Binding(value); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/CollectionSynchronizationContext.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/CollectionSynchronizationContext.cs new file mode 100755 index 000000000..f0b4bc4ed --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/CollectionSynchronizationContext.cs @@ -0,0 +1,22 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal sealed class CollectionSynchronizationContext + { + internal CollectionSynchronizationContext(object context, CollectionSynchronizationCallback callback) + { + ContextReference = new WeakReference(context); + Callback = callback; + } + + internal CollectionSynchronizationCallback Callback { get; private set; } + + internal object Context + { + get { return ContextReference != null ? ContextReference.Target : null; } + } + + internal WeakReference ContextReference { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Configuration.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Configuration.cs new file mode 100755 index 000000000..9e8898e31 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Configuration.cs @@ -0,0 +1,21 @@ + +namespace Tizen.NUI.XamlBinding +{ + internal class Configuration : IPlatformElementConfiguration + where TPlatform : IConfigPlatform + where TElement : Element + + { + public Configuration(TElement element) + { + Element = element; + } + + public TElement Element { get; } + + public static Configuration Create(TElement element) + { + return new Configuration(element); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ContentPropertyAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ContentPropertyAttribute.cs new file mode 100755 index 000000000..53cfb1730 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ContentPropertyAttribute.cs @@ -0,0 +1,26 @@ +// +// ContentPropertyAttribute.cs +// +// Author: +// Stephane Delcroix +// +// Copyright (c) 2013 S. Delcroix +// + +using System; + +namespace Tizen.NUI.XamlBinding +{ + [AttributeUsage(AttributeTargets.Class)] + internal sealed class ContentPropertyAttribute : Attribute + { + internal static string[] ContentPropertyTypes = { "Tizen.NUI.XamlBinding.ContentPropertyAttribute", "System.Windows.Markup.ContentPropertyAttribute" }; + + public ContentPropertyAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ControlTemplate.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ControlTemplate.cs new file mode 100755 index 000000000..baec3ee42 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ControlTemplate.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + /// + /// Template that specifies a group of styles and effects for controls. + /// + internal class ControlTemplate : ElementTemplate + { + /// + /// For internal use only. + /// + public ControlTemplate() + { + } + + /// + /// Creates a new control template for the specified control type. + /// + /// The type of control for which to create a template. + public ControlTemplate(Type type) : base(type) + { + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplate.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplate.cs new file mode 100755 index 000000000..1191b2fca --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplate.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal class DataTemplate : ElementTemplate + { + public DataTemplate() + { + } + + public DataTemplate(Type type) : base(type) + { + } + + public DataTemplate(Func loadTemplate) : base(loadTemplate) + { + } + + public IDictionary Bindings { get; } = new Dictionary(); + + public IDictionary Values { get; } = new Dictionary(); + + public void SetBinding(BindableProperty property, BindingBase binding) + { + if (property == null) + throw new ArgumentNullException("property"); + if (binding == null) + throw new ArgumentNullException("binding"); + + Values.Remove(property); + Bindings[property] = binding; + } + + public void SetValue(BindableProperty property, object value) + { + if (property == null) + throw new ArgumentNullException("property"); + + Bindings.Remove(property); + Values[property] = value; + } + + internal override void SetupContent(object item) + { + ApplyBindings(item); + ApplyValues(item); + } + + void ApplyBindings(object item) + { + if (Bindings == null) + return; + + var bindable = item as BindableObject; + if (bindable == null) + return; + + foreach (KeyValuePair kvp in Bindings) + { + if (Values.ContainsKey(kvp.Key)) + throw new InvalidOperationException("Binding and Value found for " + kvp.Key.PropertyName); + + bindable.SetBinding(kvp.Key, kvp.Value.Clone()); + } + } + + void ApplyValues(object item) + { + if (Values == null) + return; + + var bindable = item as BindableObject; + if (bindable == null) + return; + foreach (KeyValuePair kvp in Values) + bindable.SetValue(kvp.Key, kvp.Value); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateExtensions.cs new file mode 100755 index 000000000..b6191db32 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateExtensions.cs @@ -0,0 +1,19 @@ +namespace Tizen.NUI.XamlBinding +{ + internal static class DataTemplateExtensions + { + public static DataTemplate SelectDataTemplate(this DataTemplate self, object item, BindableObject container) + { + var selector = self as DataTemplateSelector; + if (selector == null) + return self; + + return selector.SelectTemplate(item, container); + } + + public static object CreateContent(this DataTemplate self, object item, BindableObject container) + { + return self.SelectDataTemplate(item, container).CreateContent(); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateSelector.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateSelector.cs new file mode 100755 index 000000000..5913df01b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DataTemplateSelector.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal abstract class DataTemplateSelector : DataTemplate + { + Dictionary _dataTemplates = new Dictionary(); + + public DataTemplate SelectTemplate(object item, BindableObject container) + { + DataTemplate dataTemplate = null; + + dataTemplate = OnSelectTemplate(item, container); + if (dataTemplate is DataTemplateSelector) + throw new NotSupportedException( + "DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector"); + + return dataTemplate; + } + + protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyAttribute.cs new file mode 100755 index 000000000..a29f70a5b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal class DependencyAttribute : Attribute + { + public DependencyAttribute(Type implementorType) + { + Implementor = implementorType; + } + + internal Type Implementor { get; private set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyFetchTarget.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyFetchTarget.cs new file mode 100755 index 000000000..ee2202ed5 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyFetchTarget.cs @@ -0,0 +1,8 @@ +namespace Tizen.NUI.XamlBinding +{ + internal enum DependencyFetchTarget + { + GlobalInstance, + NewInstance + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyResolver.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyResolver.cs new file mode 100755 index 000000000..2f7637e85 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyResolver.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.Reflection; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.XamlBinding +{ + internal static class DependencyResolver + { + static Func Resolver { get; set; } + + public static void ResolveUsing(Func resolver) + { + Resolver = resolver; + } + + public static void ResolveUsing(Func resolver) + { + Resolver = (type, objects) => resolver.Invoke(type); + } + + internal static object Resolve(Type type, params object[] args) + { + var result = Resolver?.Invoke(type, args); + + if (result != null) + { + if (!type.IsInstanceOfType(result)) + { + throw new InvalidCastException("Resolved instance is not of the correct type."); + } + } + + return result; + } + + internal static object ResolveOrCreate(Type type, params object[] args) + { + var result = Resolve(type, args); + + if (result != null) return result; + + if (args.Length > 0) + { + // This is by no means a general solution to matching with the correct constructor, but it'll + // do for finding Android renderers which need Context (vs older custom renderers which may still use + // parameterless constructors) + if (type.GetTypeInfo().DeclaredConstructors.Any(info => info.GetParameters().Length == args.Length)) + { + return Activator.CreateInstance(type, args); + } + } + + return Activator.CreateInstance(type); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyService.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyService.cs new file mode 100755 index 000000000..a6221ab69 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DependencyService.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.XamlBinding +{ + internal static class DependencyService + { + static bool s_initialized; + + static readonly List DependencyTypes = new List(); + static readonly Dictionary DependencyImplementations = new Dictionary(); + + public static T Resolve(DependencyFetchTarget fallbackFetchTarget = DependencyFetchTarget.GlobalInstance) where T : class + { + var result = DependencyResolver.Resolve(typeof(T)) as T; + + return result ?? Get(fallbackFetchTarget); + } + + public static T Get(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class + { + Initialize(); + + Type targetType = typeof(T); + + if (!DependencyImplementations.ContainsKey(targetType)) + { + Type implementor = FindImplementor(targetType); + DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null; + } + + DependencyData dependencyImplementation = DependencyImplementations[targetType]; + if (dependencyImplementation == null) + return null; + + if (fetchTarget == DependencyFetchTarget.GlobalInstance) + { + if (dependencyImplementation.GlobalInstance == null) + { + dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType); + } + return (T)dependencyImplementation.GlobalInstance; + } + return (T)Activator.CreateInstance(dependencyImplementation.ImplementorType); + } + + public static void Register() where T : class + { + Type type = typeof(T); + if (!DependencyTypes.Contains(type)) + DependencyTypes.Add(type); + } + + public static void Register() where T : class where TImpl : class, T + { + Type targetType = typeof(T); + Type implementorType = typeof(TImpl); + if (!DependencyTypes.Contains(targetType)) + DependencyTypes.Add(targetType); + + DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType }; + } + + static Type FindImplementor(Type target) + { + return DependencyTypes.FirstOrDefault(t => target.IsAssignableFrom(t)); + } + + static void Initialize() + { + if (s_initialized) + { + return; + } + + Assembly[] assemblies = Device.GetAssemblies(); + if (Tizen.NUI.XamlBinding.Internals.Registrar.ExtraAssemblies != null) + { + assemblies = assemblies.Union(Tizen.NUI.XamlBinding.Internals.Registrar.ExtraAssemblies).ToArray(); + } + + Initialize(assemblies); + } + + internal static void Initialize(Assembly[] assemblies) + { + if (s_initialized || assemblies == null) + { + return; + } + DependencyService.Register(); + + Type targetAttrType = typeof(DependencyAttribute); + + // Don't use LINQ for performance reasons + // Naive implementation can easily take over a second to run + foreach (Assembly assembly in assemblies) + { + Attribute[] attributes; + try + { + attributes = assembly.GetCustomAttributes(targetAttrType).ToArray(); + } + catch (System.IO.FileNotFoundException) + { + // Sometimes the previewer doesn't actually have everything required for these loads to work + Console.WriteLine(nameof(Registrar), "Could not load assembly: {0} for Attibute {1} | Some renderers may not be loaded", assembly.FullName, targetAttrType.FullName); + continue; + } + + if (attributes.Length == 0) + continue; + + foreach (DependencyAttribute attribute in attributes) + { + if (!DependencyTypes.Contains(attribute.Implementor)) + { + DependencyTypes.Add(attribute.Implementor); + } + } + } + + s_initialized = true; + } + + class DependencyData + { + public object GlobalInstance { get; set; } + + public Type ImplementorType { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Device.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Device.cs new file mode 100755 index 000000000..775f07628 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Device.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal static class Device + { + public const string iOS = "iOS"; + public const string Android = "Android"; + public const string UWP = "UWP"; + public const string macOS = "macOS"; + public const string GTK = "GTK"; + public const string Tizen = "Tizen"; + public const string WPF = "WPF"; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static DeviceInfo info; + + static IPlatformServices s_platformServices; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetIdiom(TargetIdiom value) => Idiom = value; + public static TargetIdiom Idiom { get; internal set; } + + //TODO: Why are there two of these? This is never used...? + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetTargetIdiom(TargetIdiom value) => Idiom = value; + + [Obsolete("TargetPlatform is obsolete as of version 2.3.4. Please use RuntimePlatform instead.")] +#pragma warning disable 0618 + public static TargetPlatform OS + { + get + { + TargetPlatform platform; + if (Enum.TryParse(RuntimePlatform, out platform)) + return platform; + + // In the old TargetPlatform, there was no distinction between WinRT/UWP + if (RuntimePlatform == UWP) + { + return TargetPlatform.Windows; + } + + return TargetPlatform.Other; + } + } +#pragma warning restore 0618 + + public static string RuntimePlatform => PlatformServices?.RuntimePlatform; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static DeviceInfo Info + { + get + { + // if (info == null) + // throw new InvalidOperationException("You MUST call Tizen.NUI.Xaml.Init(); prior to using it."); + return info; + } + set { info = value; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetFlowDirection(FlowDirection value) => FlowDirection = value; + public static FlowDirection FlowDirection { get; internal set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool IsInvokeRequired + { + get { return PlatformServices.IsInvokeRequired; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IPlatformServices PlatformServices + { + get + { + if (s_platformServices == null) + { + s_platformServices = new TizenPlatformServices(); + } + return s_platformServices; + } + set + { + s_platformServices = value; + Console.WriteLine("Device s_platformServices : " + s_platformServices ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IReadOnlyList Flags { get; private set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetFlags(IReadOnlyList flags) + { + Flags = flags; + } + + public static void BeginInvokeOnMainThread(Action action) + { + PlatformServices?.BeginInvokeOnMainThread(action); + action(); + Console.WriteLine("Device BeginInvokeOnMainThread action called"); + } + + // public static double GetNamedSize(NamedSize size, Element targetElement) + // { + // return GetNamedSize(size, targetElement.GetType()); + // } + + // public static double GetNamedSize(NamedSize size, Type targetElementType) + // { + // return GetNamedSize(size, targetElementType, false); + // } + + [Obsolete("OnPlatform is obsolete as of version 2.3.4. Please use switch(RuntimePlatform) instead.")] + public static void OnPlatform(Action iOS = null, Action Android = null, Action WinPhone = null, Action Default = null) + { + switch (OS) + { + case TargetPlatform.iOS: + if (iOS != null) + iOS(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Android: + if (Android != null) + Android(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Windows: + case TargetPlatform.WinPhone: + if (WinPhone != null) + WinPhone(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Other: + if (Default != null) + Default(); + break; + } + } + + [Obsolete("OnPlatform<> (generic) is obsolete as of version 2.3.4. Please use switch(RuntimePlatform) instead.")] + public static T OnPlatform(T iOS, T Android, T WinPhone) + { + switch (OS) + { + case TargetPlatform.iOS: + return iOS; + case TargetPlatform.Android: + return Android; + case TargetPlatform.Windows: + case TargetPlatform.WinPhone: + return WinPhone; + } + + return iOS; + } + + public static void OpenUri(Uri uri) + { + // PlatformServices?.OpenUriAction(uri); + } + + public static void StartTimer(TimeSpan interval, Func callback) + { + PlatformServices.StartTimer(interval, callback); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static Assembly[] GetAssemblies() + { + return PlatformServices?.GetAssemblies(); + } + + // [EditorBrowsable(EditorBrowsableState.Never)] + // public static double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes) + // { + // return PlatformServices.GetNamedSize(size, targetElementType, useOldSizes); + // } + + internal static Task GetStreamAsync(Uri uri, CancellationToken cancellationToken) + { + return PlatformServices?.GetStreamAsync(uri, cancellationToken); + } + + public static class Styles + { + public static readonly string TitleStyleKey = "TitleStyle"; + + public static readonly string SubtitleStyleKey = "SubtitleStyle"; + + public static readonly string BodyStyleKey = "BodyStyle"; + + public static readonly string ListItemTextStyleKey = "ListItemTextStyle"; + + public static readonly string ListItemDetailTextStyleKey = "ListItemDetailTextStyle"; + + public static readonly string CaptionStyleKey = "CaptionStyle"; + + public static readonly Style TitleStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = TitleStyleKey }; + + public static readonly Style SubtitleStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = SubtitleStyleKey }; + + public static readonly Style BodyStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = BodyStyleKey }; + + public static readonly Style ListItemTextStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = ListItemTextStyleKey }; + + public static readonly Style ListItemDetailTextStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = ListItemDetailTextStyleKey }; + + public static readonly Style CaptionStyle = new Style(typeof(Tizen.NUI.Xaml.Forms.BaseComponents.TextLabel)) { BaseResourceKey = CaptionStyleKey }; + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceInfo.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceInfo.cs new file mode 100755 index 000000000..b17cdacfe --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceInfo.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Tizen.NUI.XamlBinding +{ + internal abstract class DeviceInfo : INotifyPropertyChanged, IDisposable + { + DeviceOrientation _currentOrientation; + bool _disposed; + + public DeviceOrientation CurrentOrientation + { + get { return _currentOrientation; } + set + { + if (Equals(_currentOrientation, value)) + return; + _currentOrientation = value; + OnPropertyChanged(); + } + } + + public virtual double DisplayRound(double value) => + Math.Round(value); + + public void Dispose() + { + Dispose(true); + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + _disposed = true; + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceOrientation.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceOrientation.cs new file mode 100755 index 000000000..af374ba27 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/DeviceOrientation.cs @@ -0,0 +1,13 @@ +namespace Tizen.NUI.XamlBinding +{ + internal enum DeviceOrientation + { + Portrait, + Landscape, + PortraitUp, + PortraitDown, + LandscapeLeft, + LandscapeRight, + Other + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Effect.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Effect.cs new file mode 100755 index 000000000..a865b12bf --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Effect.cs @@ -0,0 +1,89 @@ +using System; +using System.ComponentModel; + +namespace Tizen.NUI.XamlBinding +{ + /// + /// A collection of styles and properties that can be added to an element at run time. + /// + internal abstract class Effect + { + internal Effect() + { + } + + /// + /// Gets the element to which the style is attached. + /// + public Element Element { get; internal set; } + + /// + /// Gets a value that tells whether the effect is attached to an element. + /// + public bool IsAttached { get; private set; } + + /// + /// Gets the ID that is used to resolve this effect at runtime. + /// + public string ResolveId { get; internal set; } + + #region Statics + /// + /// Returns an Effect for the specified name, which is of the form ResolutionGroupName.ExportEffect. + /// + /// The name of the effect to get. + /// The uniquely identified effect. + public static Effect Resolve(string name) + { + Effect result = null; + if (Tizen.NUI.XamlBinding.Internals.Registrar.Effects.TryGetValue(name, out Type effectType)) + { + result = (Effect)DependencyResolver.ResolveOrCreate(effectType); + } + + if (result == null) + result = new NullEffect(); + result.ResolveId = name; + return result; + } + + #endregion + + /// + /// Method that is called after the effect is attached and made valid. + /// + protected abstract void OnAttached(); + + /// + /// Method that is called after the effect is detached and invalidated. + /// + protected abstract void OnDetached(); + + internal virtual void ClearEffect() + { + if (IsAttached) + SendDetached(); + Element = null; + } + + internal virtual void SendAttached() + { + if (IsAttached) + return; + OnAttached(); + IsAttached = true; + } + + internal virtual void SendDetached() + { + if (!IsAttached) + return; + OnDetached(); + IsAttached = false; + } + + internal virtual void SendOnElementPropertyChanged(PropertyChangedEventArgs args) + { + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirection.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirection.cs new file mode 100755 index 000000000..8beb728ad --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirection.cs @@ -0,0 +1,11 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [Flags] + internal enum EffectiveFlowDirection + { + RightToLeft = 1 << 0, + Explicit = 1 << 1, + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirectionExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirectionExtensions.cs new file mode 100755 index 000000000..4e10ff313 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EffectiveFlowDirectionExtensions.cs @@ -0,0 +1,70 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal static class EffectiveFlowDirectionExtensions + { + internal static EffectiveFlowDirection ToEffectiveFlowDirection(this FlowDirection self, bool isExplicit = false) + { + switch (self) + { + case FlowDirection.MatchParent: + return default(EffectiveFlowDirection); + + + case FlowDirection.LeftToRight: + if (isExplicit) + { + return EffectiveFlowDirection.Explicit; + } + else + { + return default(EffectiveFlowDirection); + } + + case FlowDirection.RightToLeft: + if (isExplicit) + { + return EffectiveFlowDirection.RightToLeft | EffectiveFlowDirection.Explicit; + } + else + { + return EffectiveFlowDirection.RightToLeft; + } + + default: + throw new InvalidOperationException($"Cannot convert {self} to {nameof(EffectiveFlowDirection)}."); + } + } + + internal static FlowDirection ToFlowDirection(this EffectiveFlowDirection self) + { + if (self.IsLeftToRight()) + return FlowDirection.LeftToRight; + else + return FlowDirection.RightToLeft; + + throw new InvalidOperationException($"Cannot convert {self} to {nameof(FlowDirection)}."); + } + + public static bool IsRightToLeft(this EffectiveFlowDirection self) + { + return (self & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft; + } + + public static bool IsLeftToRight(this EffectiveFlowDirection self) + { + return (self & EffectiveFlowDirection.RightToLeft) != EffectiveFlowDirection.RightToLeft; + } + + public static bool IsImplicit(this EffectiveFlowDirection self) + { + return (self & EffectiveFlowDirection.Explicit) != EffectiveFlowDirection.Explicit; + } + + public static bool IsExplicit(this EffectiveFlowDirection self) + { + return (self & EffectiveFlowDirection.Explicit) == EffectiveFlowDirection.Explicit; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementCollection.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementCollection.cs new file mode 100755 index 000000000..e2640c25e --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementCollection.cs @@ -0,0 +1,11 @@ +using System.Collections.ObjectModel; + +namespace Tizen.NUI.XamlBinding +{ + internal class ElementCollection : ObservableWrapper where T : Element + { + public ElementCollection(ObservableCollection list) : base(list) + { + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementEventArgs.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementEventArgs.cs new file mode 100755 index 000000000..c0e6513a2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal class ElementEventArgs : EventArgs + { + public ElementEventArgs(Element element) + { + if (element == null) + throw new ArgumentNullException("element"); + + Element = element; + } + + public Element Element { get; private set; } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementTemplate.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementTemplate.cs new file mode 100755 index 000000000..9de7af2ed --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ElementTemplate.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + /// + /// Base class for DataTemplate and ControlTemplate classes. + /// + internal class ElementTemplate : IElement, IDataTemplate + { + List> _changeHandlers; + Element _parent; + bool _canRecycle; // aka IsDeclarative + + internal ElementTemplate() + { + } + + internal ElementTemplate(Type type) : this() + { + if (type == null) + throw new ArgumentNullException("type"); + + _canRecycle = true; + + LoadTemplate = () => Activator.CreateInstance(type); + } + + internal ElementTemplate(Func loadTemplate) : this() + { + if (loadTemplate == null) + throw new ArgumentNullException("loadTemplate"); + + LoadTemplate = loadTemplate; + } + + Func LoadTemplate { get; set; } + +#pragma warning disable 0612 + Func IDataTemplate.LoadTemplate + { + get { return LoadTemplate; } + set { LoadTemplate = value; } + } +#pragma warning restore 0612 + + void IElement.AddResourcesChangedListener(Action onchanged) + { + _changeHandlers = _changeHandlers ?? new List>(1); + _changeHandlers.Add(onchanged); + } + + internal bool CanRecycle => _canRecycle; + Element IElement.Parent + { + get { return _parent; } + set + { + if (_parent == value) + return; + if (_parent != null) + ((IElement)_parent).RemoveResourcesChangedListener(OnResourcesChanged); + _parent = value; + if (_parent != null) + ((IElement)_parent).AddResourcesChangedListener(OnResourcesChanged); + } + } + + void IElement.RemoveResourcesChangedListener(Action onchanged) + { + if (_changeHandlers == null) + return; + _changeHandlers.Remove(onchanged); + } + + /// + /// Used by the XAML infrastructure to load data templates and set up the content of the resulting UI. + /// + /// + public object CreateContent() + { + if (LoadTemplate == null) + throw new InvalidOperationException("LoadTemplate should not be null"); + if (this is DataTemplateSelector) + throw new InvalidOperationException("Cannot call CreateContent directly on a DataTemplateSelector"); + + object item = LoadTemplate(); + SetupContent(item); + + return item; + } + + internal virtual void SetupContent(object item) + { + } + + void OnResourcesChanged(object sender, ResourcesChangedEventArgs e) + { + if (_changeHandlers == null) + return; + foreach (Action handler in _changeHandlers) + handler(this, e); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EnumerableExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EnumerableExtensions.cs new file mode 100755 index 000000000..f4e0e7f3d --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EnumerableExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal static class EnumerableExtensions + { + public static IEnumerable GetGesturesFor(this IEnumerable gestures, Func predicate = null) where T : GestureRecognizer + { + if (gestures == null) + yield break; + + if (predicate == null) + predicate = x => true; + + foreach (IGestureRecognizer item in gestures) + { + var gesture = item as T; + if (gesture != null && predicate(gesture)) + { + yield return gesture; + } + } + } + + internal static IEnumerable Append(this IEnumerable enumerable, T item) + { + foreach (T x in enumerable) + yield return x; + + yield return item; + } + + public static void ForEach(this IEnumerable enumeration, Action action) + { + foreach (T item in enumeration) + { + action(item); + } + } + + public static int IndexOf(this IEnumerable enumerable, T item) + { + if (enumerable == null) + throw new ArgumentNullException("enumerable"); + + var i = 0; + foreach (T element in enumerable) + { + if (Equals(element, item)) + return i; + + i++; + } + + return -1; + } + + public static int IndexOf(this IEnumerable enumerable, Func predicate) + { + var i = 0; + foreach (T element in enumerable) + { + if (predicate(element)) + return i; + + i++; + } + + return -1; + } + + public static IEnumerable Prepend(this IEnumerable enumerable, T item) + { + yield return item; + + foreach (T x in enumerable) + yield return x; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EventArg.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EventArg.cs new file mode 100755 index 000000000..c402cd460 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/EventArg.cs @@ -0,0 +1,18 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal class EventArg : EventArgs + { + // Property variable + + // Constructor + public EventArg(T data) + { + Data = data; + } + + // Property for EventArgs argument + public T Data { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExportEffectAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExportEffectAttribute.cs new file mode 100755 index 000000000..b78c89e2a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExportEffectAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal class ExportEffectAttribute : Attribute + { + public ExportEffectAttribute(Type effectType, string uniqueName) + { + if (uniqueName.Contains(".")) + throw new ArgumentException("uniqueName must not contain a ."); + Type = effectType; + Id = uniqueName; + } + + internal string Id { get; private set; } + + internal Type Type { get; private set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExtentsTypeConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExtentsTypeConverter.cs new file mode 100755 index 000000000..5a4c1f652 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ExtentsTypeConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; + +namespace Tizen.NUI.XamlBinding +{ + internal class ExtentsTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + string[] parts = value.Split(','); + if (parts.Length == 4) + { + return new Extents(ushort.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + ushort.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + ushort.Parse(parts[2].Trim(), CultureInfo.InvariantCulture), + ushort.Parse(parts[3].Trim(), CultureInfo.InvariantCulture)); + } + } + + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(Extents)}"); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSource.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSource.cs new file mode 100755 index 000000000..963eede28 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSource.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding +{ + [TypeConverter(typeof(FileImageSourceConverter))] + internal sealed class FileImageSource : ImageSource + { + public static readonly BindableProperty FileProperty = BindableProperty.Create("File", typeof(string), typeof(FileImageSource), default(string)); + + public string File + { + get { return (string)GetValue(FileProperty); } + set { SetValue(FileProperty, value); } + } + + public override Task Cancel() + { + return Task.FromResult(false); + } + + public override string ToString() + { + return $"File: {File}"; + } + + public static implicit operator FileImageSource(string file) + { + return (FileImageSource)FromFile(file); + } + + public static implicit operator string(FileImageSource file) + { + return file != null ? file.File : null; + } + + protected override void OnPropertyChanged(string propertyName = null) + { + if (propertyName == FileProperty.PropertyName) + OnSourceChanged(); + base.OnPropertyChanged(propertyName); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSourceConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSourceConverter.cs new file mode 100755 index 000000000..5db70b40b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FileImageSourceConverter.cs @@ -0,0 +1,16 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [Xaml.TypeConversion(typeof(FileImageSource))] + internal sealed class FileImageSourceConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + return (FileImageSource)ImageSource.FromFile(value); + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(FileImageSource))); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FlowDirection.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FlowDirection.cs new file mode 100755 index 000000000..d23bfafc3 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/FlowDirection.cs @@ -0,0 +1,32 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [TypeConverter(typeof(FlowDirectionConverter))] + internal enum FlowDirection + { + MatchParent = 0, + LeftToRight = 1, + RightToLeft = 2, + } + + [Xaml.TypeConversion(typeof(FlowDirection))] + internal class FlowDirectionConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) { + if (Enum.TryParse(value, out FlowDirection direction)) + return direction; + + if (value.Equals("ltr", StringComparison.OrdinalIgnoreCase)) + return FlowDirection.LeftToRight; + if (value.Equals("rtl", StringComparison.OrdinalIgnoreCase)) + return FlowDirection.RightToLeft; + if (value.Equals("inherit", StringComparison.OrdinalIgnoreCase)) + return FlowDirection.MatchParent; + } + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(FlowDirection))); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/GestureRecognizer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/GestureRecognizer.cs new file mode 100755 index 000000000..eee08bf4a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/GestureRecognizer.cs @@ -0,0 +1,9 @@ +namespace Tizen.NUI.XamlBinding +{ + internal class GestureRecognizer : Element, IGestureRecognizer + { + internal GestureRecognizer() + { + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/HandlerAttribute.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/HandlerAttribute.cs new file mode 100755 index 000000000..9de9507cb --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/HandlerAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal abstract class HandlerAttribute : Attribute + { + protected HandlerAttribute(Type handler, Type target) + { + TargetType = target; + HandlerType = handler; + } + + internal Type HandlerType { get; private set; } + + internal Type TargetType { get; private set; } + + public virtual bool ShouldRegister() + { + return true; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppIndexingProvider.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppIndexingProvider.cs new file mode 100755 index 000000000..a1de516f3 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppIndexingProvider.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IAppIndexingProvider + { + IAppLinks AppLinks { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinkEntry.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinkEntry.cs new file mode 100755 index 000000000..75498ad49 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinkEntry.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IAppLinkEntry + { + Uri AppLinkUri { get; set; } + + string Description { get; set; } + + bool IsLinkActive { get; set; } + + IDictionary KeyValues { get; } + + ImageSource Thumbnail { get; set; } + + string Title { get; set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinks.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinks.cs new file mode 100755 index 000000000..bbfe9a907 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IAppLinks.cs @@ -0,0 +1,11 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IAppLinks + { + void DeregisterLink(IAppLinkEntry appLink); + void DeregisterLink(Uri appLinkUri); + void RegisterLink(IAppLinkEntry appLink); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigElement.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigElement.cs new file mode 100755 index 000000000..b30776ed2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigElement.cs @@ -0,0 +1,14 @@ +namespace Tizen.NUI.XamlBinding +{ + /// + /// This interface is for internal use by platform renderers. + /// + /// + internal interface IConfigElement where T : Element + { + /// + /// For internal use + /// + T Element { get; } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigPlatform.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigPlatform.cs new file mode 100755 index 000000000..c50a2d006 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IConfigPlatform.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.XamlBinding +{ + /// + /// Base interface for marker classes that identify target platforms for platform specific effects. + /// + internal interface IConfigPlatform { } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IControlTemplated.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IControlTemplated.cs new file mode 100755 index 000000000..f7c90acfb --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IControlTemplated.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IControlTemplated + { + // ControlTemplate ControlTemplate { get; set; } + + IList InternalChildren { get; } + + void OnControlTemplateChanged(ControlTemplate oldValue, ControlTemplate newValue); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IEffectControlProvider.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IEffectControlProvider.cs new file mode 100755 index 000000000..f6cbc24ff --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IEffectControlProvider.cs @@ -0,0 +1,14 @@ +namespace Tizen.NUI.XamlBinding +{ + /// + /// When implemented in a renderer, registers a platform-specific effect on an element. + /// + internal interface IEffectControlProvider + { + /// + /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform. + /// + /// The effect to register. + void RegisterEffect(Effect effect); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElement.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElement.cs new file mode 100755 index 000000000..6df3c941b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElement.cs @@ -0,0 +1,14 @@ +using System; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IElement + { + Element Parent { get; set; } + + //Use these 2 instead of an event to avoid cloning way too much multicastdelegates on mono + void AddResourcesChangedListener(Action onchanged); + void RemoveResourcesChangedListener(Action onchanged); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementConfiguration.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementConfiguration.cs new file mode 100755 index 000000000..16a1e0f3e --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementConfiguration.cs @@ -0,0 +1,8 @@ + +namespace Tizen.NUI.XamlBinding +{ + internal interface IElementConfiguration where TElement : Element + { + // IPlatformElementConfiguration On() where T : IConfigPlatform; + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementController.cs new file mode 100755 index 000000000..63ab9a8cd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IElementController.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IElementController + { + // IEffectControlProvider EffectControlProvider { get; set; } + + // bool EffectIsAttached(string name); + + // void SetValueFromRenderer(BindableProperty property, object value); + // void SetValueFromRenderer(BindablePropertyKey propertyKey, object value); + // ReadOnlyCollection LogicalChildren { get; } + // IPlatform Platform { get; set; } + // Element RealParent { get; } + // IEnumerable Descendants(); + // event EventHandler PlatformSet; + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IGestureRecognizer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IGestureRecognizer.cs new file mode 100755 index 000000000..83ba50a6e --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IGestureRecognizer.cs @@ -0,0 +1,8 @@ +using System.ComponentModel; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IGestureRecognizer : INotifyPropertyChanged + { + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IIsolatedStorageFile.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IIsolatedStorageFile.cs new file mode 100755 index 000000000..e71542dd9 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IIsolatedStorageFile.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IIsolatedStorageFile + { + Task CreateDirectoryAsync(string path); + Task GetDirectoryExistsAsync(string path); + Task GetFileExistsAsync(string path); + + Task GetLastWriteTimeAsync(string path); + + Task OpenFileAsync(string path, FileMode mode, FileAccess access); + Task OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayout.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayout.cs new file mode 100755 index 000000000..0a2fea1fc --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayout.cs @@ -0,0 +1,9 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal interface ILayout + { + event EventHandler LayoutChanged; + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayoutController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayoutController.cs new file mode 100755 index 000000000..e0f8d7974 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ILayoutController.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal interface ILayoutController + { + IReadOnlyList Children { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IMenuItemController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IMenuItemController.cs new file mode 100755 index 000000000..d9e3a2c41 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IMenuItemController.cs @@ -0,0 +1,10 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IMenuItemController + { + bool IsEnabled { get; set; } + string IsEnabledPropertyName { get; } + + void Activate(); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INativeBindingService.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INativeBindingService.cs new file mode 100755 index 000000000..34341fb21 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INativeBindingService.cs @@ -0,0 +1,10 @@ +namespace Tizen.NUI.XamlBinding +{ + + internal interface INativeBindingService + { + bool TrySetBinding(object target, string propertyName, BindingBase binding); + bool TrySetBinding(object target, BindableProperty property, BindingBase binding); + bool TrySetValue(object target, BindableProperty property, object value); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigation.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigation.cs new file mode 100755 index 000000000..d63d99a45 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigation.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.XamlBinding +{ + /// + /// Interface abstracting platform-specific navigation. + /// + internal interface INavigation + { + /// + /// Gets the modal navigation stack. + /// + IReadOnlyList ModalStack { get; } + + /// + /// Gets the stack of pages in the navigation. + /// + IReadOnlyList NavigationStack { get; } + + /// + /// Inserts a page in the navigation stack before an existing page in the stack. + /// + /// The page to add. + /// The existing page, before which page will be inserted. + void InsertPageBefore(Page page, Page before); + + /// + /// Asynchronously removes the most recent Page from the navigation stack. + /// + /// The Page that had been at the top of the navigation stack. + Task PopAsync(); + + /// + /// Asynchronously removes the most recent Page from the navigation stack, with optional animation. + /// + /// Whether to animate the pop. + /// The Page that had been at the top of the navigation stack. + Task PopAsync(bool animated); + + /// + /// Asynchronously dismisses the most recent modally presented Page. + /// + /// An awaitable instance, indicating the PopModalAsync completion. The Task.Result is the Page that has been popped. + Task PopModalAsync(); + + /// + /// Asynchronously dismisses the most recent modally presented Page, with optional animation. + /// + /// Whether to animate the pop. + /// An awaitable, indicating the PopModalAsync completion. The Task.Result is the Page that has been popped. + Task PopModalAsync(bool animated); + + /// + /// Pops all but the root Page off the navigation stack. + /// + /// A task representing the asynchronous dismiss operation. + Task PopToRootAsync(); + + /// + /// Pops all but the root Page off the navigation stack, with optional animation. + /// + /// Whether to animate the pop. + /// A task representing the asynchronous dismiss operation. + Task PopToRootAsync(bool animated); + + /// + /// Asynchronously adds a Page to the top of the navigation stack. + /// + /// The Page to be pushed on top of the navigation stack. + /// A task that represents the asynchronous push operation. + Task PushAsync(Page page); + + /// + /// Asynchronously adds a Page to the top of the navigation stack, with optional animation. + /// + /// The page to push. + /// Whether to animate the push. + /// A task that represents the asynchronous push operation. + Task PushAsync(Page page, bool animated); + + /// + /// Presents a Page modally. + /// + /// The Page to present modally. + /// An awaitable Task, indicating the PushModal completion. + Task PushModalAsync(Page page); + + /// + /// Presents a Page modally, with optional animation. + /// + /// The page to push. + /// Whether to animate the push. + /// An awaitable Task, indicating the PushModal completion. + Task PushModalAsync(Page page, bool animated); + + /// + /// Removes the specified page from the navigation stack. + /// + /// The page to remove. + void RemovePage(Page page); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationMenuController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationMenuController.cs new file mode 100755 index 000000000..87abb32d6 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationMenuController.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface INavigationMenuController : IViewController + { + void SendTargetSelected(Xaml.Page target); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationPageController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationPageController.cs new file mode 100755 index 000000000..506480af2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/INavigationPageController.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tizen.NUI.XamlBinding.Internals; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.XamlBinding +{ + internal interface INavigationPageController + { + Task RemoveAsyncInner(Page page, bool animated, bool fast); + + Page Peek(int depth = 0); + + IEnumerable Pages { get; } + + int StackDepth { get; } + + Task PopAsyncInner(bool animated, bool fast = false); + + event EventHandler InsertPageBeforeRequested; + + event EventHandler PopRequested; + + event EventHandler PopToRootRequested; + + event EventHandler PushRequested; + + event EventHandler RemovePageRequested; + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPaddingElement.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPaddingElement.cs new file mode 100755 index 000000000..3f6b7b262 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPaddingElement.cs @@ -0,0 +1,12 @@ +namespace Tizen.NUI.XamlBinding +{ + interface IPaddingElement + { + //note to implementor: implement this property publicly + // Thickness Padding { get; } + + //note to implementor: but implement this method explicitly + void OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue); + Thickness PaddingDefaultValueCreator(); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageContainer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageContainer.cs new file mode 100755 index 000000000..09009515d --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageContainer.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IPageContainer where T : Xaml.Page + { + T CurrentPage { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageController.cs new file mode 100755 index 000000000..233ad6ac8 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPageController.cs @@ -0,0 +1,18 @@ +using System.Collections.ObjectModel; +using Tizen.NUI; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IPageController + { + Rectangle ContainerArea { get; set; } + + bool IgnoresContainerArea { get; set; } + + ObservableCollection InternalChildren { get; } + + void SendAppearing(); + + void SendDisappearing(); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatform.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatform.cs new file mode 100755 index 000000000..51cb62ec0 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatform.cs @@ -0,0 +1,17 @@ +namespace Tizen.NUI.XamlBinding +{ + /// + /// For internal use. + /// + internal interface IPlatform + { + /// + /// Returns the native size. + /// + /// The view + /// The width constraint. + /// The height constraint. + /// The native size. + SizeRequest GetNativeSize(BaseHandle view, double widthConstraint, double heightConstraint); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformElementConfiguration.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformElementConfiguration.cs new file mode 100755 index 000000000..b35767c08 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformElementConfiguration.cs @@ -0,0 +1,13 @@ +namespace Tizen.NUI.XamlBinding +{ + /// + /// Marker interface for returning platform-specific configuration elements. + /// + /// The platform type. + /// The element type. + internal interface IPlatformElementConfiguration : IConfigElement + where TPlatform : IConfigPlatform + where TElement : Element + { + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformServices.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformServices.cs new file mode 100755 index 000000000..e4dc9933a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IPlatformServices.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IPlatformServices + { + bool IsInvokeRequired { get; } + + void BeginInvokeOnMainThread(Action action); + + Ticker CreateTicker(); + + Assembly[] GetAssemblies(); + + string GetMD5Hash(string input); + + // double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes); + + Task GetStreamAsync(Uri uri, CancellationToken cancellationToken); + + // IIsolatedStorageFile GetUserStoreForApplication(); + + // void OpenUriAction(Uri uri); + + void StartTimer(TimeSpan interval, Func callback); + + string RuntimePlatform { get; } + + void QuitApplication(); + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IRegisterable.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IRegisterable.cs new file mode 100755 index 000000000..306fbdf6b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IRegisterable.cs @@ -0,0 +1,6 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IRegisterable + { + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IResourceDictionary.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IResourceDictionary.cs new file mode 100755 index 000000000..88ff90d4b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IResourceDictionary.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IResourceDictionary : IEnumerable> + { + bool TryGetValue(string key, out object value); + + event EventHandler ValuesChanged; + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ISystemResourcesProvider.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ISystemResourcesProvider.cs new file mode 100755 index 000000000..98ffb60ab --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ISystemResourcesProvider.cs @@ -0,0 +1,7 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface ISystemResourcesProvider + { + IResourceDictionary GetSystemResources(); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ITimer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ITimer.cs new file mode 100755 index 000000000..0542ef363 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ITimer.cs @@ -0,0 +1,13 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + //this will go once Timer is included in Pcl profiles + internal interface ITimer + { + void Change(int dueTime, int period); + void Change(long dueTime, long period); + void Change(TimeSpan dueTime, TimeSpan period); + void Change(uint dueTime, uint period); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewContainer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewContainer.cs new file mode 100755 index 000000000..e95f10402 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewContainer.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IViewContainer where T : Element + { + IList Children { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewController.cs new file mode 100755 index 000000000..f31b79be8 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IViewController.cs @@ -0,0 +1,6 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IViewController : IVisualElementController + { + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IVisualElementController.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IVisualElementController.cs new file mode 100755 index 000000000..8266864f4 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/IVisualElementController.cs @@ -0,0 +1,20 @@ +using System; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal interface IVisualElementController : IElementController + { + void NativeSizeChanged(); + void InvalidateMeasure(InvalidationTrigger trigger); + bool Batched { get; } + bool DisableLayout { get; set; } + EffectiveFlowDirection EffectiveFlowDirection { get; } + bool IsInNativeLayout { get; set; } + bool IsNativeStateConsistent { get; set; } + bool IsPlatformEnabled { get; set; } + NavigationProxy NavigationProxy { get; } + event EventHandler> BatchCommitted; + event EventHandler FocusChangeRequested; + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSource.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSource.cs new file mode 100755 index 000000000..8df985811 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSource.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding +{ + [TypeConverter(typeof(ImageSourceConverter))] + internal abstract class ImageSource : Element + { + readonly object _synchandle = new object(); + CancellationTokenSource _cancellationTokenSource; + + TaskCompletionSource _completionSource; + + readonly WeakEventManager _weakEventManager = new WeakEventManager(); + + protected ImageSource() + { + } + + protected CancellationTokenSource CancellationTokenSource + { + get { return _cancellationTokenSource; } + private set + { + if (_cancellationTokenSource == value) + return; + if (_cancellationTokenSource != null) + _cancellationTokenSource.Cancel(); + _cancellationTokenSource = value; + } + } + + bool IsLoading + { + get { return _cancellationTokenSource != null; } + } + + public virtual Task Cancel() + { + if (!IsLoading) + return Task.FromResult(false); + + var tcs = new TaskCompletionSource(); + TaskCompletionSource original = Interlocked.CompareExchange(ref _completionSource, tcs, null); + if (original == null) + { + _cancellationTokenSource.Cancel(); + } + else + tcs = original; + + return tcs.Task; + } + + public static ImageSource FromFile(string file) + { + return new FileImageSource { File = file }; + } + + public static ImageSource FromResource(string resource, Type resolvingType) + { + return FromResource(resource, resolvingType.GetTypeInfo().Assembly); + } + + public static ImageSource FromResource(string resource, Assembly sourceAssembly = null) + { +#if NETSTANDARD2_0 + sourceAssembly = sourceAssembly ?? Assembly.GetCallingAssembly(); +#else + if (sourceAssembly == null) + { + MethodInfo callingAssemblyMethod = typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetCallingAssembly"); + if (callingAssemblyMethod != null) + { + sourceAssembly = (Assembly)callingAssemblyMethod.Invoke(null, new object[0]); + } + else + { + Internals.Log.Warning("Warning", "Can not find CallingAssembly, pass resolvingType to FromResource to ensure proper resolution"); + return null; + } + } +#endif + return FromStream(() => sourceAssembly.GetManifestResourceStream(resource)); + } + + public static ImageSource FromStream(Func stream) + { + // return new StreamImageSource { Stream = token => Task.Run(stream, token) }; + return null; + } + + public static ImageSource FromUri(Uri uri) + { + if (!uri.IsAbsoluteUri) + throw new ArgumentException("uri is relative"); + // return new UriImageSource { Uri = uri }; + return null; + } + + public static implicit operator ImageSource(string source) + { + Uri uri; + return Uri.TryCreate(source, UriKind.Absolute, out uri) && uri.Scheme != "file" ? FromUri(uri) : FromFile(source); + } + + public static implicit operator ImageSource(Uri uri) + { + if (!uri.IsAbsoluteUri) + throw new ArgumentException("uri is relative"); + return FromUri(uri); + } + + protected void OnLoadingCompleted(bool cancelled) + { + if (!IsLoading || _completionSource == null) + return; + + TaskCompletionSource tcs = Interlocked.Exchange(ref _completionSource, null); + if (tcs != null) + tcs.SetResult(cancelled); + + lock (_synchandle) + { + CancellationTokenSource = null; + } + } + + protected void OnLoadingStarted() + { + lock (_synchandle) + { + CancellationTokenSource = new CancellationTokenSource(); + } + } + + protected void OnSourceChanged() + { + _weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(SourceChanged)); + } + + internal event EventHandler SourceChanged + { + add { _weakEventManager.AddEventHandler(nameof(SourceChanged), value); } + remove { _weakEventManager.RemoveEventHandler(nameof(SourceChanged), value); } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSourceConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSourceConverter.cs new file mode 100755 index 000000000..f3319d834 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ImageSourceConverter.cs @@ -0,0 +1,19 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [Xaml.TypeConversion(typeof(ImageSource))] + internal sealed class ImageSourceConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + Uri uri; + return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ? ImageSource.FromUri(uri) : ImageSource.FromFile(value); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ImageSource))); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/AttachedCollection.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/AttachedCollection.cs new file mode 100755 index 000000000..14e3f1bd1 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/AttachedCollection.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Tizen.NUI.XamlBinding +{ + internal class AttachedCollection : ObservableCollection, ICollection, IAttachedObject where T : BindableObject, IAttachedObject + { + readonly List _associatedObjects = new List(); + + public AttachedCollection() + { + } + + public AttachedCollection(IEnumerable collection) : base(collection) + { + } + + public AttachedCollection(IList list) : base(list) + { + } + + public void AttachTo(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + OnAttachedTo(bindable); + } + + public void DetachFrom(BindableObject bindable) + { + OnDetachingFrom(bindable); + } + + protected override void ClearItems() + { + foreach (WeakReference weakbindable in _associatedObjects) + { + foreach (T item in this) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item?.DetachFrom(bindable); + } + } + base.ClearItems(); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item?.AttachTo(bindable); + } + } + + protected virtual void OnAttachedTo(BindableObject bindable) + { + lock (_associatedObjects) + { + _associatedObjects.Add(new WeakReference(bindable)); + } + foreach (T item in this) + item?.AttachTo(bindable); + } + + protected virtual void OnDetachingFrom(BindableObject bindable) + { + foreach (T item in this) + item?.DetachFrom(bindable); + lock (_associatedObjects) + { + for (var i = 0; i < _associatedObjects.Count; i++) + { + object target = _associatedObjects[i].Target; + + if (target == null || target == bindable) + { + _associatedObjects.RemoveAt(i); + i--; + } + } + } + } + + protected override void RemoveItem(int index) + { + T item = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item?.DetachFrom(bindable); + } + + base.RemoveItem(index); + } + + protected override void SetItem(int index, T item) + { + T old = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + old?.DetachFrom(bindable); + } + + base.SetItem(index, item); + + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item?.AttachTo(bindable); + } + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/BindingCondition.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/BindingCondition.cs new file mode 100755 index 000000000..bf1bb824c --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/BindingCondition.cs @@ -0,0 +1,98 @@ +using System; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.XamlBinding +{ + [ProvideCompiled("Tizen.NUI.Xaml.Forms.XamlC.PassthroughValueProvider")] + [AcceptEmptyServiceProvider] + internal sealed class BindingCondition : Condition, IValueProvider + { + readonly BindableProperty _boundProperty; + + BindingBase _binding; + object _triggerValue; + + public BindingCondition() + { + _boundProperty = BindableProperty.CreateAttached("Bound", typeof(object), typeof(BindingCondition), null, propertyChanged: OnBoundPropertyChanged); + } + + public BindingBase Binding + { + get { return _binding; } + set + { + if (_binding == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Binding once the Condition has been applied."); + _binding = value; + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Condition has been applied."); + _triggerValue = value; + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + //This is no longer required + return this; + } + + internal override bool GetState(BindableObject bindable) + { + object newValue = bindable.GetValue(_boundProperty); + return EqualsToValue(newValue); + } + + internal override void SetUp(BindableObject bindable) + { + if (Binding != null) + bindable.SetBinding(_boundProperty, Binding.Clone()); + } + + internal override void TearDown(BindableObject bindable) + { + bindable.RemoveBinding(_boundProperty); + bindable.ClearValue(_boundProperty); + } + + static IValueConverterProvider s_valueConverter = DependencyService.Get(); + + bool EqualsToValue(object other) + { + if ((other == Value) || (other != null && other.Equals(Value))) + return true; + + object converted = null; + if (s_valueConverter != null) + converted = s_valueConverter.Convert(Value, other != null ? other.GetType() : typeof(object), null, null); + else + return false; + + return (other == converted) || (other != null && other.Equals(converted)); + } + + void OnBoundPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + bool oldState = EqualsToValue(oldValue); + bool newState = EqualsToValue(newValue); + + if (newState == oldState) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, oldState, newState); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/Condition.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/Condition.cs new file mode 100755 index 000000000..26cb65dbd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/Condition.cs @@ -0,0 +1,51 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal abstract class Condition + { + Action _conditionChanged; + + bool _isSealed; + + internal Condition() + { + } + + internal Action ConditionChanged + { + get { return _conditionChanged; } + set + { + if (_conditionChanged == value) + return; + if (_conditionChanged != null) + throw new InvalidOperationException("The same condition instance can not be reused"); + _conditionChanged = value; + } + } + + internal bool IsSealed + { + get { return _isSealed; } + set + { + if (_isSealed == value) + return; + if (!value) + throw new InvalidOperationException("What is sealed can not be unsealed."); + _isSealed = value; + OnSealed(); + } + } + + internal abstract bool GetState(BindableObject bindable); + + internal virtual void OnSealed() + { + } + + internal abstract void SetUp(BindableObject bindable); + internal abstract void TearDown(BindableObject bindable); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/IAttachedObject.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/IAttachedObject.cs new file mode 100755 index 000000000..72b98f59a --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/IAttachedObject.cs @@ -0,0 +1,8 @@ +namespace Tizen.NUI.XamlBinding +{ + internal interface IAttachedObject + { + void AttachTo(BindableObject bindable); + void DetachFrom(BindableObject bindable); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiCondition.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiCondition.cs new file mode 100755 index 000000000..249370d72 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiCondition.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + internal sealed class MultiCondition : Condition + { + readonly BindableProperty _aggregatedStateProperty; + + public MultiCondition() + { + _aggregatedStateProperty = BindableProperty.CreateAttached("AggregatedState", typeof(bool), typeof(MultiCondition), false, propertyChanged: OnAggregatedStatePropertyChanged); + Conditions = new TriggerBase.SealedList(); + } + + public IList Conditions { get; } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_aggregatedStateProperty); + } + + internal override void OnSealed() + { + ((TriggerBase.SealedList)Conditions).IsReadOnly = true; + foreach (Condition condition in Conditions) + condition.ConditionChanged = OnConditionChanged; + } + + internal override void SetUp(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.SetUp(bindable); + } + + internal override void TearDown(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.TearDown(bindable); + } + + void OnAggregatedStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + ConditionChanged?.Invoke(bindable, (bool)oldValue, (bool)newValue); + } + + void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue) + { + var oldState = (bool)bindable.GetValue(_aggregatedStateProperty); + var newState = true; + foreach (Condition condition in Conditions) + { + if (!condition.GetState(bindable)) + { + newState = false; + break; + } + } + if (newState != oldState) + bindable.SetValue(_aggregatedStateProperty, newState); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiTrigger.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiTrigger.cs new file mode 100755 index 000000000..ac6e74f3f --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/MultiTrigger.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Tizen.NUI.XamlBinding +{ + [ContentProperty("Setters")] + internal sealed class MultiTrigger : TriggerBase + { + public MultiTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new MultiCondition(), targetType) + { + } + + public IList Conditions + { + get { return ((MultiCondition)Condition).Conditions; } + } + + public new IList Setters + { + get { return base.Setters; } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/PropertyCondition.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/PropertyCondition.cs new file mode 100755 index 000000000..35d4727c8 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/PropertyCondition.cs @@ -0,0 +1,112 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Tizen.NUI.XamlBinding; + +namespace Tizen.NUI.Xaml +{ + [ProvideCompiled("Tizen.NUI.Xaml.Forms.XamlC.PassthroughValueProvider")] + [AcceptEmptyServiceProvider] + internal sealed class PropertyCondition : Condition, IValueProvider + { + readonly BindableProperty _stateProperty; + + BindableProperty _property; + object _triggerValue; + + public PropertyCondition() + { + _stateProperty = BindableProperty.CreateAttached("State", typeof(bool), typeof(PropertyCondition), false, propertyChanged: this.OnStatePropertyChanged); + } + + public BindableProperty Property + { + get { return _property; } + set + { + if (_property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + _property = value; + + //convert the value + if (_property != null && s_valueConverter != null) + { + Func minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + Value = s_valueConverter.Convert(Value, Property.ReturnType, minforetriever, null); + } + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + + //convert the value + if (_property != null && s_valueConverter != null) + { + Func minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + value = s_valueConverter.Convert(value, Property.ReturnType, minforetriever, null); + } + _triggerValue = value; + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + //This is no longer required + return this; + } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_stateProperty); + } + + static IValueConverterProvider s_valueConverter = DependencyService.Get(); + + internal override void SetUp(BindableObject bindable) + { + object newvalue = bindable.GetValue(Property); + bool newState = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + bindable.SetValue(_stateProperty, newState); + bindable.PropertyChanged += OnAttachedObjectPropertyChanged; + } + + internal override void TearDown(BindableObject bindable) + { + bindable.ClearValue(_stateProperty); + bindable.PropertyChanged -= OnAttachedObjectPropertyChanged; + } + + void OnAttachedObjectPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var bindable = (BindableObject)sender; + var oldState = (bool)bindable.GetValue(_stateProperty); + + if (Property == null) + return; + if (e.PropertyName != Property.PropertyName) + return; + object newvalue = bindable.GetValue(Property); + bool newstate = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + if (oldState != newstate) + bindable.SetValue(_stateProperty, newstate); + } + + void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + ConditionChanged?.Invoke(bindable, (bool)oldValue, (bool)newValue); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/XamlPropertyCondition.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/XamlPropertyCondition.cs new file mode 100755 index 000000000..5b40402ed --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Interactivity/XamlPropertyCondition.cs @@ -0,0 +1,116 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI.XamlBinding +{ + [ProvideCompiled("Tizen.NUI.XamlC.PassthroughValueProvider")] + [AcceptEmptyServiceProvider] + internal sealed class XamlPropertyCondition : Condition, IValueProvider + { + readonly BindableProperty _stateProperty; + + BindableProperty _property; + object _triggerValue; + + public XamlPropertyCondition() + { + _stateProperty = BindableProperty.CreateAttached("State", typeof(bool), typeof(XamlPropertyCondition), false, propertyChanged: OnStatePropertyChanged); + } + + public BindableProperty Property + { + get { return _property; } + set + { + if (_property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + _property = value; + + //convert the value + if (_property != null && s_valueConverter != null) + { + Func minforetriever = () => _property.DeclaringType.GetRuntimeProperty(_property.PropertyName); + Value = s_valueConverter.Convert(Value, _property.ReturnType, minforetriever, null); + } + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + + //convert the value + if (_property != null && s_valueConverter != null) + { + Func minforetriever = () => _property.DeclaringType.GetRuntimeProperty(_property.PropertyName); + _triggerValue = s_valueConverter.Convert(value, _property.ReturnType, minforetriever, null); + } + else + { + _triggerValue = value; + } + + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + //This is no longer required + return this; + } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_stateProperty); + } + + static IValueConverterProvider s_valueConverter = DependencyService.Get(); + + internal override void SetUp(BindableObject bindable) + { + object newvalue = bindable.GetValue(Property); + bool newState = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + bindable.SetValue(_stateProperty, newState); + bindable.PropertyChanged += OnAttachedObjectPropertyChanged; + } + + internal override void TearDown(BindableObject bindable) + { + bindable.ClearValue(_stateProperty); + bindable.PropertyChanged -= OnAttachedObjectPropertyChanged; + } + + void OnAttachedObjectPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var bindable = (BindableObject)sender; + var oldState = (bool)bindable.GetValue(_stateProperty); + + if (Property == null) + return; + if (e.PropertyName != Property.PropertyName) + return; + object newvalue = bindable.GetValue(Property); + bool newstate = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + if (oldState != newstate) + bindable.SetValue(_stateProperty, newstate); + } + + void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + ConditionChanged?.Invoke(bindable, (bool)oldValue, (bool)newValue); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDataTemplate.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDataTemplate.cs new file mode 100755 index 000000000..c20a4a2e7 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDataTemplate.cs @@ -0,0 +1,9 @@ +using System; + +namespace Tizen.NUI.XamlBinding.Internals +{ + internal interface IDataTemplate + { + Func LoadTemplate { get; set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDeserializer.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDeserializer.cs new file mode 100755 index 000000000..f55e8dd99 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/IDeserializer.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Tizen.NUI.XamlBinding.Internals +{ + internal interface IDeserializer + { + Task> DeserializePropertiesAsync(); + Task SerializePropertiesAsync(IDictionary properties); + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/INamescopeProvider.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/INamescopeProvider.cs new file mode 100755 index 000000000..6eacf9ee2 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/INamescopeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Tizen.NUI.XamlBinding.Internals +{ + interface INameScopeProvider + { + INameScope NameScope { get; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/InvalidationTrigger.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/InvalidationTrigger.cs new file mode 100755 index 000000000..6ac149b7d --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/InvalidationTrigger.cs @@ -0,0 +1,16 @@ +using System; + +namespace Tizen.NUI.XamlBinding.Internals +{ + [Flags] + internal enum InvalidationTrigger + { + Undefined = 0, + MeasureChanged = 1 << 0, + HorizontalOptionsChanged = 1 << 1, + VerticalOptionsChanged = 1 << 2, + SizeRequestChanged = 1 << 3, + RendererReady = 1 << 4, + MarginChanged = 1 << 5 + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/NumericExtensions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/NumericExtensions.cs new file mode 100755 index 000000000..0432b88bd --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/NumericExtensions.cs @@ -0,0 +1,23 @@ +using System; + +namespace Tizen.NUI.XamlBinding.Internals +{ + internal static class NumericExtensions + { + + public static double Clamp(this double self, double min, double max) + { + return Math.Min(max, Math.Max(self, min)); + } + + public static float Clamp(this float self, float min, float max) + { + return Math.Min(max, Math.Max(self, min)); + } + + public static int Clamp(this int self, int min, int max) + { + return Math.Min(max, Math.Max(self, min)); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/Ticker.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/Ticker.cs new file mode 100755 index 000000000..1f20c9d7e --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/Internals/Ticker.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Tizen.NUI.XamlBinding.Internals +{ + internal abstract class Ticker + { + static Ticker s_ticker; + readonly Stopwatch _stopwatch; + readonly List>> _timeouts; + + int _count; + bool _enabled; + + protected Ticker() + { + _count = 0; + _timeouts = new List>>(); + + _stopwatch = new Stopwatch(); + } + + public static void SetDefault(Ticker ticker) => Default = ticker; + public static Ticker Default + { + internal set { s_ticker = value; } + get { return s_ticker ?? (s_ticker = Device.PlatformServices.CreateTicker()); } + } + + public virtual int Insert(Func timeout) + { + _count++; + _timeouts.Add(new Tuple>(_count, timeout)); + + if (!_enabled) + { + _enabled = true; + Enable(); + } + + return _count; + } + + public virtual void Remove(int handle) + { + Device.BeginInvokeOnMainThread(() => + { + _timeouts.RemoveAll(t => t.Item1 == handle); + + if (!_timeouts.Any()) + { + _enabled = false; + Disable(); + } + }); + } + + protected abstract void DisableTimer(); + + protected abstract void EnableTimer(); + + protected void SendSignals(int timestep = -1) + { + long step = timestep >= 0 ? timestep : _stopwatch.ElapsedMilliseconds; + _stopwatch.Reset(); + _stopwatch.Start(); + + var localCopy = new List>>(_timeouts); + foreach (Tuple> timeout in localCopy) + { + bool remove = !timeout.Item2(step); + if (remove) + _timeouts.RemoveAll(t => t.Item1 == timeout.Item1); + } + + if (!_timeouts.Any()) + { + _enabled = false; + Disable(); + } + } + + void Disable() + { + _stopwatch.Reset(); + DisableTimer(); + } + + void Enable() + { + _stopwatch.Start(); + EnableTimer(); + } + } +} diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidNavigationException.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidNavigationException.cs new file mode 100755 index 000000000..4b08123f3 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidNavigationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + internal class InvalidNavigationException : Exception + { + public InvalidNavigationException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidationEventArgs.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidationEventArgs.cs new file mode 100755 index 000000000..da26253f5 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/InvalidationEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + internal class InvalidationEventArgs : EventArgs + { + public InvalidationEventArgs(InvalidationTrigger trigger) + { + Trigger = trigger; + } + + public InvalidationTrigger Trigger { get; private set; } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutAlignment.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutAlignment.cs new file mode 100755 index 000000000..714e97321 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutAlignment.cs @@ -0,0 +1,13 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [Flags] + internal enum LayoutAlignment + { + Start = 0, + Center = 1, + End = 2, + Fill = 3 + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutExpandFlag.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutExpandFlag.cs new file mode 100755 index 000000000..e2fd4045b --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutExpandFlag.cs @@ -0,0 +1,10 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [Flags] + internal enum LayoutExpandFlag + { + Expand = 4 + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptions.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptions.cs new file mode 100755 index 000000000..68e265daa --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptions.cs @@ -0,0 +1,39 @@ +using System; + +namespace Tizen.NUI.XamlBinding +{ + [TypeConverter(typeof(LayoutOptionsConverter))] + internal struct LayoutOptions + { + int _flags; + + public static readonly LayoutOptions Start = new LayoutOptions(LayoutAlignment.Start, false); + public static readonly LayoutOptions Center = new LayoutOptions(LayoutAlignment.Center, false); + public static readonly LayoutOptions End = new LayoutOptions(LayoutAlignment.End, false); + public static readonly LayoutOptions Fill = new LayoutOptions(LayoutAlignment.Fill, false); + public static readonly LayoutOptions StartAndExpand = new LayoutOptions(LayoutAlignment.Start, true); + public static readonly LayoutOptions CenterAndExpand = new LayoutOptions(LayoutAlignment.Center, true); + public static readonly LayoutOptions EndAndExpand = new LayoutOptions(LayoutAlignment.End, true); + public static readonly LayoutOptions FillAndExpand = new LayoutOptions(LayoutAlignment.Fill, true); + + public LayoutOptions(LayoutAlignment alignment, bool expands) + { + var a = (int)alignment; + if (a < 0 || a > 3) + throw new ArgumentOutOfRangeException(); + _flags = (int)alignment | (expands ? (int)LayoutExpandFlag.Expand : 0); + } + + public LayoutAlignment Alignment + { + get { return (LayoutAlignment)(_flags & 3); } + set { _flags = (_flags & ~3) | (int)value; } + } + + public bool Expands + { + get { return (_flags & (int)LayoutExpandFlag.Expand) != 0; } + set { _flags = (_flags & 3) | (value ? (int)LayoutExpandFlag.Expand : 0); } + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptionsConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptionsConverter.cs new file mode 100755 index 000000000..793747491 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/LayoutOptionsConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Reflection; +using Tizen.NUI.XamlBinding.Internals; + +namespace Tizen.NUI.XamlBinding +{ + [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Forms.XamlC.LayoutOptionsConverter")] + [Xaml.TypeConversion(typeof(LayoutOptions))] + internal sealed class LayoutOptionsConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) { + var parts = value.Split('.'); + if (parts.Length > 2 || (parts.Length == 2 && parts [0] != "LayoutOptions")) + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(LayoutOptions)}"); + value = parts [parts.Length - 1]; + switch (value) { + case "Start": return LayoutOptions.Start; + case "Center": return LayoutOptions.Center; + case "End": return LayoutOptions.End; + case "Fill": return LayoutOptions.Fill; + case "StartAndExpand": return LayoutOptions.StartAndExpand; + case "CenterAndExpand": return LayoutOptions.CenterAndExpand; + case "EndAndExpand": return LayoutOptions.EndAndExpand; + case "FillAndExpand": return LayoutOptions.FillAndExpand; + } + FieldInfo field = typeof(LayoutOptions).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == value); + if (field != null) + return (LayoutOptions)field.GetValue(null); + } + + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(LayoutOptions)}"); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ListStringTypeConverter.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ListStringTypeConverter.cs new file mode 100755 index 000000000..7a97fd884 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/ListStringTypeConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tizen.NUI.XamlBinding +{ + [Xaml.ProvideCompiled("Tizen.NUI.XamlC.ListStringTypeConverter")] + [Xaml.TypeConversion(typeof(List))] + internal class ListStringTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value == null) + return null; + + return value.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToList(); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MenuItem.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MenuItem.cs new file mode 100755 index 000000000..9affa7e43 --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MenuItem.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Input; + +namespace Tizen.NUI.XamlBinding +{ + + internal class MenuItem : BaseMenuItem, IMenuItemController + { + public static readonly BindableProperty AcceleratorProperty = BindableProperty.CreateAttached(nameof(Accelerator), typeof(Accelerator), typeof(MenuItem), null); + + public static Accelerator GetAccelerator(BindableObject bindable) => (Accelerator)bindable.GetValue(AcceleratorProperty); + + public static void SetAccelerator(BindableObject bindable, Accelerator value) => bindable.SetValue(AcceleratorProperty, value); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(MenuItem), null); + + public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(MenuItem), null, + propertyChanging: (bo, o, n) => ((MenuItem)bo).OnCommandChanging(), propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandChanged()); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(MenuItem), null, + propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandParameterChanged()); + + public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create("IsDestructive", typeof(bool), typeof(MenuItem), false); + + public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(MenuItem), default(FileImageSource)); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(ToolbarItem), true); + + [EditorBrowsable(EditorBrowsableState.Never)] + public string IsEnabledPropertyName + { + get + { + return IsEnabledProperty.PropertyName; + } + } + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public FileImageSource Icon + { + get { return (FileImageSource)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + + public bool IsDestructive + { + get { return (bool)GetValue(IsDestructiveProperty); } + set { SetValue(IsDestructiveProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEnabled + { + get { return (bool)GetValue(IsEnabledProperty); } + set { SetValue(IsEnabledProperty, value); } + } + + bool IsEnabledCore + { + set { SetValueCore(IsEnabledProperty, value); } + } + + public event EventHandler Clicked; + + protected virtual void OnClicked() + => Clicked?.Invoke(this, EventArgs.Empty); + + [EditorBrowsable(EditorBrowsableState.Never)] + public void Activate() + { + if (Command != null) + { + if (IsEnabled) + Command.Execute(CommandParameter); + } + + OnClicked(); + } + + void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + IsEnabledCore = Command.CanExecute(CommandParameter); + } + + void OnCommandChanged() + { + if (Command == null) + { + IsEnabledCore = true; + return; + } + + IsEnabledCore = Command.CanExecute(CommandParameter); + + Command.CanExecuteChanged += OnCommandCanExecuteChanged; + } + + void OnCommandChanging() + { + if (Command == null) + return; + + Command.CanExecuteChanged -= OnCommandCanExecuteChanged; + } + + void OnCommandParameterChanged() + { + if (Command == null) + return; + + IsEnabledCore = Command.CanExecute(CommandParameter); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MergedStyle.cs b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MergedStyle.cs new file mode 100755 index 000000000..0519fb9cf --- /dev/null +++ b/src/Tizen.NUI.Xaml/src/internal/XamlBinding/MergedStyle.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Tizen.NUI.StyleSheets; +using Tizen.NUI.Xaml.Forms.BaseComponents; + +namespace Tizen.NUI.XamlBinding +{ + internal sealed class MergedStyle : IStyle + { + ////If the base type is one of these, stop registering dynamic resources further + ////The last one (typeof(Element)) is a safety guard as we might be creating VisualElement directly in internal code + static readonly IList s_stopAtTypes = new List { typeof(View), typeof(Element) }; + + IList _classStyleProperties; + + readonly List _implicitStyles = new List(); + + IList