Release 4.0.0-preview1-00235
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / CustomViewRegistry.cs
1
2 using System.Reflection;
3 using System;
4 using System.Runtime.InteropServices;
5 using System.Collections.Generic;
6 using Tizen.NUI.BaseComponents;
7
8 namespace Tizen.NUI
9 {
10     /// <summary>
11     /// Adds this attribute to any property belonging to a view (control) you want to be scriptable from JSON.
12     /// </summary>
13     /// <remarks>
14     /// Example:
15     ///
16     /// class MyView : public CustomView
17     /// {
18     ///  [ScriptableProperty()]
19     ///  public int MyProperty
20     ///  {
21     ///   get
22     ///   {
23     ///     return _myProperty;
24     ///   }
25     ///   set
26     ///   {
27     ///    _myProperty = value;
28     ///   }
29     ///  }
30     /// }
31     ///
32     /// Internally the following occurs for property registration ( this only occurs once per Type, not per Instance):
33     ///
34     /// - The controls static constructor should call ViewRegistry.Register() (only called once for the lifecycle of the app).
35     /// - Within Register() the code will introspect the Controls properties, looking for the ScriptableProperty() attribute.
36     /// - For every property with the ScriptableProperty() attribute, TypeRegistration.RegisterProperty is called.
37     /// - TypeRegistration.RegisterProperty calls in to DALi C++ Code Dali::CSharpTypeRegistry::RegisterProperty().
38     /// - 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).
39     ///
40     ///  The DALi C# example:
41     ///
42     ///  class MyView : public CustomView
43     ///  {
44     ///
45     ///    [ScriptableProperty()]
46     ///    public double Hours
47     ///    {
48     ///     get { return seconds / 3600; }
49     ///     set { seconds = value * 3600; }
50     ///    }
51     ///  }
52     ///
53     ///  Equivalent code in DALi C++:
54     ///  in MyControl.h
55     ///  class MyControl : public Control
56     ///  {
57     ///    struct Property
58     ///    {
59     ///      enum
60     ///      {
61     ///        HOURS =  Control::CONTROL_PROPERTY_END_INDEX + 1
62     ///      }
63     ///    }
64     ///  }
65     ///
66     /// in MyControl-impl.cpp
67     ///
68     /// DALI_TYPE_REGISTRATION_BEGIN( Toolkit::MyControl, Toolkit::Control, Create );
69     /// DALI_PROPERTY_REGISTRATION( Toolkit, MyControl, "Hours",  INTEGER, DISABLED                     )
70     /// DALI_TYPE_REGISTRATION_END()
71     /// </remarks>
72     ///
73     ///
74     public class ScriptableProperty : System.Attribute
75     {
76         public enum ScriptableType
77         {
78             Default,    // Read Writable, non-animatable property, event thread only
79                         //  Animatable // Animatable property, Currently disabled, UK
80         }
81         public readonly ScriptableType type;
82
83         public ScriptableProperty(ScriptableType type = ScriptableType.Default)
84         {
85             this.type = type;
86         }
87     }
88
89     /// <summary>
90     /// View the Registry singleton.
91     /// Used for registering controls and any scriptable properties they have (see ScriptableProperty).
92     ///
93     /// Internal Design from C# to C++
94     ///
95     /// - Each custom C# view should have it's static constructor called before any JSON file is loaded.
96     /// Static constructors for a class will only run once ( they are run per control type, not per instance).
97     /// Example of running a static constructor:
98     ///      System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (typeof(Spin).TypeHandle);
99     /// Inside the static constructor the control should register it's type with the ViewRegistry
100     /// For example:
101     ///
102     ///  static Spin()
103     ///  {
104     ///     ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) );
105     ///  }
106     ///
107     ///  The control should also provide a CreateInstance function, which gets passed to the ViewRegistry.
108     ///  // Eventually it will be called if DALi Builderfinds a Spin control in a JSON file.
109     ///  static CustomView CreateInstance()
110     ///  {
111     ///    return new Spin();
112     ///  }
113     ///
114     ///
115     ///
116     /// The DALi C++ equivalent of this is
117     ///
118     ///  TypeRegistration mType( typeid(Toolkit::Spin), typeid(Toolkit::Control), CreateInstance );
119     ///
120     ///
121     ///
122     /// </summary>
123     public sealed class CustomViewRegistry
124     {
125         /// <summary>
126         /// ViewRegistry is a singleton.
127         /// </summary>
128         private static CustomViewRegistry instance = null;
129
130         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
131         private delegate IntPtr CreateControlDelegate(IntPtr cPtrControlName);
132
133         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
134         private delegate IntPtr GetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName);
135
136         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
137         private delegate void SetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue);
138
139         private CreateControlDelegate _createCallback;
140         private SetPropertyDelegate _setPropertyCallback;
141         private GetPropertyDelegate _getPropertyCallback;
142         private PropertyRangeManager _propertyRangeManager;
143
144         ///<summary>
145         // Maps the name of a custom view to a create instance function
146         /// For example, given a string "Spin", we can get a function used to create the Spin View.
147         ///</summary>
148         private Dictionary<String, Func<CustomView>> _constructorMap;
149
150         /// <summary>
151         /// Lookup table to match C# types to DALi types, used for the automatic property registration.
152         /// </summary>
153         private static readonly Dictionary<string, Tizen.NUI.PropertyType> _daliPropertyTypeLookup
154         = new Dictionary<string, Tizen.NUI.PropertyType>
155         {
156       { "float",   PropertyType.Float },
157       { "int",     PropertyType.Integer },
158       { "Int32",   PropertyType.Integer },
159       { "Boolean", PropertyType.Boolean },
160       { "string",  PropertyType.String },
161       { "Vector2", PropertyType.Vector2 },
162       { "Vector3", PropertyType.Vector3 },
163       { "Vector4", PropertyType.Vector4 },
164       { "Size",    PropertyType.Vector2 },
165       { "Position",PropertyType.Vector3 },
166       { "Color",   PropertyType.Vector4 },
167       { "PropertyArray", PropertyType.Array },
168       { "PropertyMap",   PropertyType.Map },
169             //  { "Matrix3", PropertyType.MATRIX3 }, commented out until we need to use Matrices from JSON
170             //  { "Matrix",  PropertyType.MATRIX },
171         };
172
173
174         private CustomViewRegistry()
175         {
176             _createCallback = new CreateControlDelegate(CreateControl);
177             _getPropertyCallback = new GetPropertyDelegate(GetProperty);
178             _setPropertyCallback = new SetPropertyDelegate(SetProperty);
179
180             _constructorMap = new Dictionary<string, Func<CustomView>>();
181             _propertyRangeManager = new PropertyRangeManager();
182         }
183
184         private Tizen.NUI.PropertyType GetDaliPropertyType(string cSharpTypeName)
185         {
186             Tizen.NUI.PropertyType daliType;
187             if (_daliPropertyTypeLookup.TryGetValue(cSharpTypeName, out daliType))
188             {
189 #if DEBUG_ON
190                 Tizen.Log.Debug("NUI", "mapped " + cSharpTypeName + " to dAli type " + daliType);
191 #endif
192                 return daliType;
193             }
194             else
195             {
196 #if DEBUG_ON
197                 Tizen.Log.Debug("NUI", "Failed to find a mapping between C# property" + cSharpTypeName + " and DALi type");
198 #endif
199                 return PropertyType.None;
200             }
201         }
202
203         /// <summary>
204         /// Called directly from DALi C++ type registry to create a control (view) using no marshalling.
205         /// </summary>
206         /// <returns>Pointer to the control (views) handle.</returns>
207         /// <param name="cPtrControlName">C pointer to the control (view) name.</param>
208         private static IntPtr CreateControl(IntPtr cPtrControlName)
209         {
210             string controlName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(cPtrControlName);
211 #if DEBUG_ON
212             Tizen.Log.Debug("NUI", "Create controlled called from C++ create a " + controlName);
213 #endif
214             Func<CustomView> controlConstructor;
215
216             // find the control constructor
217             if (Instance._constructorMap.TryGetValue(controlName, out controlConstructor))
218             {
219                 // Create the control
220                 CustomView newControl = controlConstructor();
221                 return newControl.GetPtrfromView();  // return pointer to handle
222             }
223             else
224             {
225                 throw new global::System.InvalidOperationException("C# View not registererd with ViewRegistry" + controlName);
226             }
227         }
228
229         private static IntPtr GetProperty(IntPtr controlPtr, IntPtr propertyName)
230         {
231             string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
232             return Instance.GetPropertyValue(controlPtr, name);
233         }
234
235         private static void SetProperty(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue)
236         {
237             string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
238 #if DEBUG_ON
239             Tizen.Log.Debug("NUI", "SetControlProperty  called for:" + name);
240 #endif
241             Instance.SetPropertyValue(controlPtr, name, propertyValue);
242
243         }
244
245         public static CustomViewRegistry Instance
246         {
247             get
248             {
249                 if (instance == null)
250                 {
251                     instance = new CustomViewRegistry();
252                 }
253                 return instance;
254             }
255         }
256
257         /// <summary>
258         /// The function which registers a view and all it's scriptable properties with DALi's type registry.
259         /// Means the view can be created or configured from a JSON script.
260         ///
261         /// The function uses introspection to scan a views C# properties, then selects the ones with
262         ///[ScriptableProperty] attribute to be registered.
263         /// Example of a Spin view registering itself:
264         ///   static Spin()
265         /// {
266         ///   ViewRegistry registers control type with DALi type registery
267         ///   also uses introspection to find any properties that need to be registered with type registry
268         ///   ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) );
269         /// }
270         ///
271         /// </summary>
272         public void Register(Func<CustomView> createFunction, System.Type viewType)
273         {
274             // add the mapping between the view name and it's create function
275             _constructorMap.Add(viewType.ToString(), createFunction);
276
277             // Call into DALi C++ to register the control with the type registry
278             TypeRegistration.RegisterControl(viewType.ToString(), _createCallback);
279
280             // Cycle through each property in the class
281             foreach (System.Reflection.PropertyInfo propertyInfo in viewType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
282             {
283                 if (propertyInfo.CanRead)
284                 {
285                     IEnumerable<Attribute> ie_attrs = propertyInfo.GetCustomAttributes<Attribute>();
286                     List<Attribute> li_attrs = new List<Attribute>(ie_attrs);
287                     System.Attribute[] attrs = li_attrs.ToArray();
288
289                     foreach (System.Attribute attr in attrs)
290                     {
291                         // If the Scriptable attribute exists, then register it with the type registry.
292                         if (attr is ScriptableProperty)
293                         {
294 #if DEBUG_ON
295                             Tizen.Log.Debug("NUI", "Got a DALi JSON scriptable property = " + propertyInfo.Name + ", of type " + propertyInfo.PropertyType.Name);
296 #endif
297                             // first get the attribute type, ( default, or animatable)
298                             ScriptableProperty scriptableProp = attr as ScriptableProperty;
299
300                             // 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)
301                             int propertyIndex = _propertyRangeManager.GetPropertyIndex(viewType.ToString(), viewType, scriptableProp.type);
302
303                             // get the enum for the property type... E.g. registering a string property returns Tizen.NUI.PropertyType.String
304                             Tizen.NUI.PropertyType propertyType = GetDaliPropertyType(propertyInfo.PropertyType.Name);
305
306                             // Example   RegisterProperty("spin","maxValue", 50001, FLOAT, set, get );
307                             // Native call to register the property
308                             TypeRegistration.RegisterProperty(viewType.ToString(), propertyInfo.Name, propertyIndex, propertyType, _setPropertyCallback, _getPropertyCallback);
309                         }
310                     }
311 #if DEBUG_ON
312                     Tizen.Log.Debug("NUI", "property name = " + propertyInfo.Name);
313 #endif
314                 }
315             }
316         }
317
318         /// <summary>
319         /// Gets a property value from a view.
320         ///
321         /// </summary>
322         private IntPtr GetPropertyValue(IntPtr refObjectPtr, string propertyName)
323         {
324             // Get the C# control that maps to the C++ control
325             View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View;
326             if (view != null)
327             {
328                 // call the get property function
329                 System.Object val = view.GetType().GetProperty(propertyName).GetAccessors()[0].Invoke(view, null);
330
331                 PropertyValue value = PropertyValue.CreateFromObject(val);
332
333                 return (IntPtr)PropertyValue.getCPtr(value);
334             }
335             else
336             {
337                 return IntPtr.Zero;
338             }
339         }
340
341         /// <summary>
342         /// Sets a property value on a view.
343         ///
344         /// </summary>
345         private void SetPropertyValue(IntPtr refObjectPtr, string propertyName, IntPtr propertyValuePtr)
346         {
347             // Get the C# control that maps to the C++ control
348 #if DEBUG_ON
349             Tizen.Log.Debug("NUI", "SetPropertyValue   refObjectPtr = {0:X}" + refObjectPtr);
350 #endif
351             PropertyValue propValue = new PropertyValue(propertyValuePtr, false);
352
353             // Get the C# control that maps to the C++ control
354             View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View;
355             if (view != null)
356             {
357                 System.Reflection.PropertyInfo propertyInfo = view.GetType().GetProperty(propertyName);
358                 // We know the property name, we know it's type, we just need to convert from a DALi property value to native C# type
359                 System.Type type = propertyInfo.PropertyType;
360                 bool ok = false;
361
362                 if (type.Equals(typeof(Int32)))
363                 {
364                     int value = 0;
365                     ok = propValue.Get(out value);
366                     if (ok)
367                     {
368                         propertyInfo.SetValue(view, value);
369                     }
370                 }
371                 else if (type.Equals(typeof(bool)))
372                 {
373                     bool value = false;
374                     ok = propValue.Get(out value);
375                     if (ok)
376                     {
377                         propertyInfo.SetValue(view, value);
378                     }
379                 }
380                 else if (type.Equals(typeof(float)))
381                 {
382                     float value = 0;
383                     ok = propValue.Get(out value);
384                     if (ok)
385                     {
386                         propertyInfo.SetValue(view, value);
387                     }
388                 }
389                 else if (type.Equals(typeof(string)))
390                 {
391                     string value = "";
392                     ok = propValue.Get(out value);
393                     if (ok)
394                     {
395                         propertyInfo.SetValue(view, value);
396                     }
397                 }
398                 else if (type.Equals(typeof(Vector2)))
399                 {
400                     Vector2 value = new Vector2();
401                     ok = propValue.Get(value);
402                     if (ok)
403                     {
404                         propertyInfo.SetValue(view, value);
405                     }
406                 }
407                 else if (type.Equals(typeof(Vector3)))
408                 {
409                     Vector3 value = new Vector3();
410                     ok = propValue.Get(value);
411                     if (ok)
412                     {
413                         propertyInfo.SetValue(view, value);
414                     }
415                 }
416                 else if (type.Equals(typeof(Vector4)))
417                 {
418                     Vector4 value = new Vector4();
419                     ok = propValue.Get(value);
420
421                     if (ok)
422                     {
423                         propertyInfo.SetValue(view, value);
424                     }
425                 }
426                 else if (type.Equals(typeof(Position)))
427                 {
428                     Position value = new Position();
429                     ok = propValue.Get(value);
430                     if (ok)
431                     {
432                         propertyInfo.SetValue(view, value);
433                     }
434                 }
435                 else if (type.Equals(typeof(Size)))
436                 {
437                     Size value = new Size();
438                     ok = propValue.Get(value);
439                     if (ok)
440                     {
441                         propertyInfo.SetValue(view, new Size(value.Width, value.Height, value.Depth));
442                     };
443                 }
444                 else if (type.Equals(typeof(Color)))
445                 {
446                     // Colors are stored as Vector4's in DALi
447                     Color value = new Color();
448                     ok = propValue.Get(value);
449                     if (ok)
450                     {
451                         propertyInfo.SetValue(view, (Color)value);
452                     };
453                 }
454                 else if (type.Equals(typeof(PropertyMap)))
455                 {
456                     PropertyMap map = new PropertyMap();
457                     ok = propValue.Get(map);
458                     if( ok )
459                     {
460                         propertyInfo.SetValue( view, map );
461                     }
462                 }
463                 else if (type.Equals(typeof(PropertyArray)))
464                 {
465                     PropertyArray array = new PropertyArray();
466                     ok = propValue.Get(array);
467                     if( ok )
468                     {
469                         propertyInfo.SetValue( view, array );
470                     }
471                 }
472                 else
473                 {
474                     throw new global::System.InvalidOperationException("SetPropertyValue Unimplemented type for Property Value for " + type.FullName );
475                 }
476                 if (!ok)
477                 {
478                     throw new global::System.InvalidOperationException("SetPropertyValue propValue.Get failed");
479                 }
480             }
481             else
482             {
483                 throw new global::System.InvalidOperationException("failed to find the control to write a property to: cptr = " + refObjectPtr);
484             }
485
486         }
487
488     }
489 }