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