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