using System.Reflection; using System; using System.Runtime.InteropServices; using System.Collections.Generic; using Tizen.NUI.BaseComponents; namespace Tizen.NUI { /// /// Adds this attribute to any property belonging to a view (control) you want to be scriptable from JSON. /// /// /// Example: /// /// class MyView : public CustomView /// { /// [ScriptableProperty()] /// public int MyProperty /// { /// get /// { /// return _myProperty; /// } /// set /// { /// _myProperty = value; /// } /// } /// } /// /// Internally the following occurs for property registration ( this only occurs once per Type, not per Instance): /// /// - The controls static constructor should call ViewRegistry.Register() (only called once for the lifecycle of the app). /// - Within Register() the code will introspect the Controls properties, looking for the ScriptableProperty() attribute. /// - For every property with the ScriptableProperty() attribute, TypeRegistration.RegisterProperty is called. /// - TypeRegistration.RegisterProperty calls in to DALi C++ Code Dali::CSharpTypeRegistry::RegisterProperty(). /// - DALi C++ now knows the existance of the property and will try calling SetProperty, if it finds the property in a JSON file (loaded using builder). /// /// The DALi C# example: /// /// class MyView : public CustomView /// { /// /// [ScriptableProperty()] /// public double Hours /// { /// get { return seconds / 3600; } /// set { seconds = value * 3600; } /// } /// } /// /// Equivalent code in DALi C++: /// in MyControl.h /// class MyControl : public Control /// { /// struct Property /// { /// enum /// { /// HOURS = Control::CONTROL_PROPERTY_END_INDEX + 1 /// } /// } /// } /// /// in MyControl-impl.cpp /// /// DALI_TYPE_REGISTRATION_BEGIN( Toolkit::MyControl, Toolkit::Control, Create ); /// DALI_PROPERTY_REGISTRATION( Toolkit, MyControl, "Hours", INTEGER, DISABLED ) /// DALI_TYPE_REGISTRATION_END() /// /// /// public class ScriptableProperty : System.Attribute { /// /// Rhe enum of ScriptableType /// /// 3 public enum ScriptableType { /// /// Read Writable, non-animatable property, event thread only. /// /// 3 Default, // Read Writable, non-animatable property, event thread only // Animatable // Animatable property, Currently disabled, UK } /// 4 public readonly ScriptableType type; /// 4 public ScriptableProperty(ScriptableType type = ScriptableType.Default) { this.type = type; } } /// /// View the Registry singleton. /// Used for registering controls and any scriptable properties they have (see ScriptableProperty). /// /// Internal Design from C# to C++ /// /// - Each custom C# view should have it's static constructor called before any JSON file is loaded. /// Static constructors for a class will only run once ( they are run per control type, not per instance). /// Example of running a static constructor: /// System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (typeof(Spin).TypeHandle); /// Inside the static constructor the control should register it's type with the ViewRegistry /// For example: /// /// static Spin() /// { /// ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) ); /// } /// /// The control should also provide a CreateInstance function, which gets passed to the ViewRegistry. /// // Eventually it will be called if DALi Builderfinds a Spin control in a JSON file. /// static CustomView CreateInstance() /// { /// return new Spin(); /// } /// /// /// /// The DALi C++ equivalent of this is /// /// TypeRegistration mType( typeid(Toolkit::Spin), typeid(Toolkit::Control), CreateInstance ); /// /// /// /// public sealed class CustomViewRegistry { /// /// ViewRegistry is a singleton. /// private static CustomViewRegistry instance = null; [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate IntPtr CreateControlDelegate(IntPtr cPtrControlName); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate IntPtr GetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void SetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue); private CreateControlDelegate _createCallback; private SetPropertyDelegate _setPropertyCallback; private GetPropertyDelegate _getPropertyCallback; private PropertyRangeManager _propertyRangeManager; /// /// Maps the name of a custom view to a create instance function /// For example, given a string "Spin", we can get a function used to create the Spin View. /// private Dictionary> _constructorMap; /// /// Lookup table to match C# types to DALi types, used for the automatic property registration. /// private static readonly Dictionary _daliPropertyTypeLookup = new Dictionary { { "float", PropertyType.Float }, { "int", PropertyType.Integer }, { "Int32", PropertyType.Integer }, { "Boolean", PropertyType.Boolean }, { "string", PropertyType.String }, { "Vector2", PropertyType.Vector2 }, { "Vector3", PropertyType.Vector3 }, { "Vector4", PropertyType.Vector4 }, { "Size", PropertyType.Vector2 }, { "Position",PropertyType.Vector3 }, { "Color", PropertyType.Vector4 }, { "PropertyArray", PropertyType.Array }, { "PropertyMap", PropertyType.Map }, // { "Matrix3", PropertyType.MATRIX3 }, commented out until we need to use Matrices from JSON // { "Matrix", PropertyType.MATRIX }, }; private CustomViewRegistry() { _createCallback = new CreateControlDelegate(CreateControl); _getPropertyCallback = new GetPropertyDelegate(GetProperty); _setPropertyCallback = new SetPropertyDelegate(SetProperty); _constructorMap = new Dictionary>(); _propertyRangeManager = new PropertyRangeManager(); } private Tizen.NUI.PropertyType GetDaliPropertyType(string cSharpTypeName) { Tizen.NUI.PropertyType daliType; if (_daliPropertyTypeLookup.TryGetValue(cSharpTypeName, out daliType)) { #if DEBUG_ON Tizen.Log.Debug("NUI", "mapped " + cSharpTypeName + " to dAli type " + daliType); #endif return daliType; } else { #if DEBUG_ON Tizen.Log.Debug("NUI", "Failed to find a mapping between C# property" + cSharpTypeName + " and DALi type"); #endif return PropertyType.None; } } /// /// Called directly from DALi C++ type registry to create a control (view) using no marshalling. /// /// Pointer to the control (views) handle. /// C pointer to the control (view) name. private static IntPtr CreateControl(IntPtr cPtrControlName) { string controlName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(cPtrControlName); #if DEBUG_ON Tizen.Log.Debug("NUI", "Create controlled called from C++ create a " + controlName); #endif Func controlConstructor; // find the control constructor if (Instance._constructorMap.TryGetValue(controlName, out controlConstructor)) { // Create the control CustomView newControl = controlConstructor(); return newControl.GetPtrfromView(); // return pointer to handle } else { throw new global::System.InvalidOperationException("C# View not registererd with ViewRegistry" + controlName); } } private static IntPtr GetProperty(IntPtr controlPtr, IntPtr propertyName) { string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName); return Instance.GetPropertyValue(controlPtr, name); } private static void SetProperty(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue) { string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName); #if DEBUG_ON Tizen.Log.Debug("NUI", "SetControlProperty called for:" + name); #endif Instance.SetPropertyValue(controlPtr, name, propertyValue); } /// 4 public static CustomViewRegistry Instance { get { if (instance == null) { instance = new CustomViewRegistry(); } return instance; } } /// /// The function which registers a view and all it's scriptable properties with DALi's type registry. /// Means the view can be created or configured from a JSON script. /// /// The function uses introspection to scan a views C# properties, then selects the ones with ///[ScriptableProperty] attribute to be registered. /// Example of a Spin view registering itself: /// static Spin() /// { /// ViewRegistry registers control type with DALi type registery /// also uses introspection to find any properties that need to be registered with type registry /// ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) ); /// } /// /// /// 4 public void Register(Func createFunction, System.Type viewType) { // add the mapping between the view name and it's create function _constructorMap.Add(viewType.ToString(), createFunction); // Call into DALi C++ to register the control with the type registry TypeRegistration.RegisterControl(viewType.ToString(), _createCallback); // Cycle through each property in the class foreach (System.Reflection.PropertyInfo propertyInfo in viewType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)) { if (propertyInfo.CanRead) { IEnumerable ie_attrs = propertyInfo.GetCustomAttributes(); List li_attrs = new List(ie_attrs); System.Attribute[] attrs = li_attrs.ToArray(); foreach (System.Attribute attr in attrs) { // If the Scriptable attribute exists, then register it with the type registry. if (attr is ScriptableProperty) { #if DEBUG_ON Tizen.Log.Debug("NUI", "Got a DALi JSON scriptable property = " + propertyInfo.Name + ", of type " + propertyInfo.PropertyType.Name); #endif // first get the attribute type, ( default, or animatable) ScriptableProperty scriptableProp = attr as ScriptableProperty; // we get the start property index, based on the type and it's heirachy, e.g. DateView (70,000)-> Spin (60,000) -> View (50,000) int propertyIndex = _propertyRangeManager.GetPropertyIndex(viewType.ToString(), viewType, scriptableProp.type); // get the enum for the property type... E.g. registering a string property returns Tizen.NUI.PropertyType.String Tizen.NUI.PropertyType propertyType = GetDaliPropertyType(propertyInfo.PropertyType.Name); // Example RegisterProperty("spin","maxValue", 50001, FLOAT, set, get ); // Native call to register the property TypeRegistration.RegisterProperty(viewType.ToString(), propertyInfo.Name, propertyIndex, propertyType, _setPropertyCallback, _getPropertyCallback); } } #if DEBUG_ON Tizen.Log.Debug("NUI", "property name = " + propertyInfo.Name); #endif } } } /// /// Gets a property value from a view. /// private IntPtr GetPropertyValue(IntPtr refObjectPtr, string propertyName) { // Get the C# control that maps to the C++ control View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View; if (view != null) { // call the get property function System.Object val = view.GetType().GetProperty(propertyName).GetAccessors()[0].Invoke(view, null); PropertyValue value = PropertyValue.CreateFromObject(val); return (IntPtr)PropertyValue.getCPtr(value); } else { return IntPtr.Zero; } } /// /// Sets a property value on a view. /// private void SetPropertyValue(IntPtr refObjectPtr, string propertyName, IntPtr propertyValuePtr) { // Get the C# control that maps to the C++ control #if DEBUG_ON Tizen.Log.Debug("NUI", "SetPropertyValue refObjectPtr = {0:X}" + refObjectPtr); #endif PropertyValue propValue = new PropertyValue(propertyValuePtr, false); // Get the C# control that maps to the C++ control View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View; if (view != null) { System.Reflection.PropertyInfo propertyInfo = view.GetType().GetProperty(propertyName); // We know the property name, we know it's type, we just need to convert from a DALi property value to native C# type System.Type type = propertyInfo.PropertyType; bool ok = false; if (type.Equals(typeof(Int32))) { int value = 0; ok = propValue.Get(out value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(bool))) { bool value = false; ok = propValue.Get(out value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(float))) { float value = 0; ok = propValue.Get(out value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(string))) { string value = ""; ok = propValue.Get(out value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(Vector2))) { Vector2 value = new Vector2(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(Vector3))) { Vector3 value = new Vector3(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(Vector4))) { Vector4 value = new Vector4(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(Position))) { Position value = new Position(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, value); } } else if (type.Equals(typeof(Size))) { Size value = new Size(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, new Size(value.Width, value.Height, value.Depth)); }; } else if (type.Equals(typeof(Color))) { // Colors are stored as Vector4's in DALi Color value = new Color(); ok = propValue.Get(value); if (ok) { propertyInfo.SetValue(view, (Color)value); }; } else if (type.Equals(typeof(PropertyMap))) { PropertyMap map = new PropertyMap(); ok = propValue.Get(map); if( ok ) { propertyInfo.SetValue( view, map ); } } else if (type.Equals(typeof(PropertyArray))) { PropertyArray array = new PropertyArray(); ok = propValue.Get(array); if( ok ) { propertyInfo.SetValue( view, array ); } } else { throw new global::System.InvalidOperationException("SetPropertyValue Unimplemented type for Property Value for " + type.FullName ); } if (!ok) { throw new global::System.InvalidOperationException("SetPropertyValue propValue.Get failed"); } } else { throw new global::System.InvalidOperationException("failed to find the control to write a property to: cptr = " + refObjectPtr); } } } }