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