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