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