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