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