e20f0f54c0e6d19b0067f989d6b0e6539f191151
[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             //  { "Matrix3", PropertyType.MATRIX3 }, commented out until we need to use Matrices from JSON
168             //  { "Matrix",  PropertyType.MATRIX },
169         };
170
171
172         private CustomViewRegistry()
173         {
174             _createCallback = new CreateControlDelegate(CreateControl);
175             _getPropertyCallback = new GetPropertyDelegate(GetProperty);
176             _setPropertyCallback = new SetPropertyDelegate(SetProperty);
177
178             _constructorMap = new Dictionary<string, Func<CustomView>>();
179             _propertyRangeManager = new PropertyRangeManager();
180         }
181
182         private Tizen.NUI.PropertyType GetDaliPropertyType(string cSharpTypeName)
183         {
184             Tizen.NUI.PropertyType daliType;
185             if (_daliPropertyTypeLookup.TryGetValue(cSharpTypeName, out daliType))
186             {
187 #if DEBUG_ON
188                 Tizen.Log.Debug("NUI", "mapped " + cSharpTypeName + " to dAli type " + daliType);
189 #endif
190                 return daliType;
191             }
192             else
193             {
194 #if DEBUG_ON
195                 Tizen.Log.Debug("NUI", "Failed to find a mapping between C# property" + cSharpTypeName + " and DALi type");
196 #endif
197                 return PropertyType.None;
198             }
199         }
200
201         /// <summary>
202         /// Called directly from DALi C++ type registry to create a control (View) using no marshalling.
203         /// </summary>
204         /// <returns>Pointer to the Control (Views) handle </returns>
205         /// <param name="cPtrControlName"> C pointer to the Control (View) name</param>
206         private static IntPtr CreateControl(IntPtr cPtrControlName)
207         {
208             string controlName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(cPtrControlName);
209 #if DEBUG_ON
210             Tizen.Log.Debug("NUI", "Create controlled called from C++ create a " + controlName);
211 #endif
212             Func<CustomView> controlConstructor;
213
214             // find the control constructor
215             if (Instance._constructorMap.TryGetValue(controlName, out controlConstructor))
216             {
217                 // Create the control
218                 CustomView newControl = controlConstructor();
219                 return newControl.GetPtrfromView();  // return pointer to handle
220             }
221             else
222             {
223                 throw new global::System.InvalidOperationException("C# View not registererd with ViewRegistry" + controlName);
224                 return IntPtr.Zero;
225             }
226         }
227
228         private static IntPtr GetProperty(IntPtr controlPtr, IntPtr propertyName)
229         {
230             string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
231             return Instance.GetPropertyValue(controlPtr, name);
232         }
233
234         private static void SetProperty(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue)
235         {
236             string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
237 #if DEBUG_ON
238             Tizen.Log.Debug("NUI", "SetControlProperty  called for:" + name);
239 #endif
240             Instance.SetPropertyValue(controlPtr, name, propertyValue);
241
242         }
243
244         public static CustomViewRegistry Instance
245         {
246             get
247             {
248                 if (instance == null)
249                 {
250                     instance = new CustomViewRegistry();
251                 }
252                 return instance;
253             }
254         }
255
256         /// <summary>
257         /// Function which registers a view and all it's scriptable properties with DALi's type registry.
258         /// Means the View can be created / configured from a JSON script.
259         ///
260         /// The function uses introspection to scan a Views C# properties, then selects the ones with
261         ///[ScriptableProperty] attribute to be registered.
262         /// Example of a Spin view registering itself
263         ///   static Spin()
264         /// {
265         ///   ViewRegistry registers control type with DALi type registery
266         ///   also uses introspection to find any properties that need to be registered with type registry
267         ///   ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) );
268         /// }
269         ///
270         /// </summary>
271         public void Register(Func<CustomView> createFunction, System.Type viewType)
272         {
273             // add the mapping between the view name and it's create function
274             _constructorMap.Add(viewType.ToString(), createFunction);
275
276             // Call into DALi C++ to register the control with the type registry
277             TypeRegistration.RegisterControl(viewType.ToString(), _createCallback);
278
279             // Cycle through each property in the class
280             foreach (System.Reflection.PropertyInfo propertyInfo in viewType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
281             {
282
283                 if (propertyInfo.CanRead)
284                 {
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
360                 // We know the property name, we know it's type, we just need to convert from a DALi property value to native C# type
361                 System.Type type = propertyInfo.PropertyType;
362                 bool ok = false;
363
364                 if (type.Equals(typeof(Int32)))
365                 {
366                     int value = 0;
367                     ok = propValue.Get(out value);
368                     if (ok)
369                     {
370                         propertyInfo.SetValue(view, value);
371                     }
372                 }
373                 else if (type.Equals(typeof(bool)))
374                 {
375                     bool value = false;
376                     ok = propValue.Get(out value);
377                     if (ok)
378                     {
379                         propertyInfo.SetValue(view, value);
380                     }
381                 }
382                 else if (type.Equals(typeof(float)))
383                 {
384                     float value = 0;
385                     ok = propValue.Get(out value);
386                     if (ok)
387                     {
388                         propertyInfo.SetValue(view, value);
389                     }
390                 }
391                 else if (type.Equals(typeof(string)))
392                 {
393                     string value = "";
394                     ok = propValue.Get(out value);
395                     if (ok)
396                     {
397                         propertyInfo.SetValue(view, value);
398                     }
399                 }
400                 else if (type.Equals(typeof(Vector2)))
401                 {
402                     Vector2 value = new Vector2();
403                     ok = propValue.Get(value);
404                     if (ok)
405                     {
406                         propertyInfo.SetValue(view, value);
407                     }
408                 }
409                 else if (type.Equals(typeof(Vector3)))
410                 {
411                     Vector3 value = new Vector3();
412                     ok = propValue.Get(value);
413                     if (ok)
414                     {
415                         propertyInfo.SetValue(view, value);
416                     }
417                 }
418                 else if (type.Equals(typeof(Vector4)))
419                 {
420                     Vector4 value = new Vector4();
421                     ok = propValue.Get(value);
422
423                     if (ok)
424                     {
425                         propertyInfo.SetValue(view, value);
426                     }
427                 }
428                 else if (type.Equals(typeof(Position)))
429                 {
430                     Position value = new Position();
431                     ok = propValue.Get(value);
432                     if (ok)
433                     {
434                         propertyInfo.SetValue(view, value);
435                     }
436                 }
437                 else if (type.Equals(typeof(Size)))
438                 {
439                     Size value = new Size();
440                     ok = propValue.Get(value);
441                     if (ok)
442                     {
443                         propertyInfo.SetValue(view, new Size(value.Width, value.Height, value.Depth));
444                     };
445                 }
446                 else if (type.Equals(typeof(Color)))
447                 {
448                     // Colors are stored as Vector4's in DALi
449                     Color value = new Color();
450                     ok = propValue.Get(value);
451                     if (ok)
452                     {
453                         propertyInfo.SetValue(view, (Color)value);
454                     };
455                 }
456                 else
457                 {
458                     throw new global::System.InvalidOperationException("SetPropertyValue Unimplemented type for Property Value");
459                 }
460                 if (!ok)
461                 {
462                     throw new global::System.InvalidOperationException("SetPropertyValue propValue.Get failed");
463                 }
464             }
465             else
466             {
467                 throw new global::System.InvalidOperationException("failed to find the control to write a property to: cptr = " + refObjectPtr);
468             }
469
470         }
471
472     }
473 }