2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Reflection;
20 using System.Runtime.InteropServices;
21 using System.Collections.Generic;
22 using Tizen.NUI.BaseComponents;
23 using System.ComponentModel;
28 /// Adds this attribute to any property belonging to a view (control) you want to be scriptable from JSON.
33 /// class MyView : public CustomView
35 /// [ScriptableProperty()]
36 /// public int MyProperty
40 /// return _myProperty;
44 /// _myProperty = value;
49 /// Internally the following occurs for property registration ( this only occurs once per Type, not per Instance):
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).
57 /// The DALi C# example:
59 /// class MyView : public CustomView
62 /// [ScriptableProperty()]
63 /// public double Hours
65 /// get { return seconds / 3600; }
66 /// set { seconds = value * 3600; }
70 /// Equivalent code in DALi C++:
72 /// class MyControl : public Control
78 /// HOURS = Control::CONTROL_PROPERTY_END_INDEX + 1
83 /// in MyControl-impl.cpp
85 /// DALI_TYPE_REGISTRATION_BEGIN( Toolkit::MyControl, Toolkit::Control, Create );
86 /// DALI_PROPERTY_REGISTRATION( Toolkit, MyControl, "Hours", INTEGER, DISABLED )
87 /// DALI_TYPE_REGISTRATION_END()
91 /// <since_tizen> 3 </since_tizen>
92 [AttributeUsage(AttributeTargets.Property)]
93 public class ScriptableProperty : System.Attribute
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;
100 /// <since_tizen> 3 </since_tizen>
101 public ScriptableProperty(ScriptableType type = ScriptableType.Default)
107 /// The enum of ScriptableType
109 /// <since_tizen> 3 </since_tizen>
110 public enum ScriptableType
113 /// Read Writable, non-animatable property, event thread only.
115 /// <since_tizen> 3 </since_tizen>
116 Default, // Read Writable, non-animatable property, event thread only
117 // Animatable // Animatable property, Currently disabled, UK
121 /// ScriptableType: Read Writable, non-animatable property, event thread only.
123 [EditorBrowsable(EditorBrowsableState.Never)]
124 public ScriptableType Type => type;
128 /// View the Registry singleton.
129 /// Used for registering controls and any scriptable properties they have (see ScriptableProperty).
131 /// Internal Design from C# to C++
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
142 /// ViewRegistry.Instance.Register(CreateInstance, typeof(Spin) );
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()
149 /// return new Spin();
154 /// The DALi C++ equivalent of this is
156 /// TypeRegistration mType( typeid(Toolkit::Spin), typeid(Toolkit::Control), CreateInstance );
161 /// <since_tizen> 3 </since_tizen>
162 public sealed class CustomViewRegistry
166 /// Lookup table to match C# types to DALi types, used for the automatic property registration.
168 private static readonly Dictionary<string, Tizen.NUI.PropertyType> daliPropertyTypeLookup
169 = new Dictionary<string, Tizen.NUI.PropertyType>
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 },
189 /// ViewRegistry is a singleton.
191 private static CustomViewRegistry instance = null;
193 private CreateControlDelegate createCallback;
194 private SetPropertyDelegate setPropertyCallback;
195 private GetPropertyDelegate getPropertyCallback;
196 private PropertyRangeManager propertyRangeManager;
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.
202 private Dictionary<String, Func<CustomView>> constructorMap;
204 private CustomViewRegistry()
206 createCallback = new CreateControlDelegate(CreateControl);
207 getPropertyCallback = new GetPropertyDelegate(GetProperty);
208 setPropertyCallback = new SetPropertyDelegate(SetProperty);
210 constructorMap = new Dictionary<string, Func<CustomView>>();
211 propertyRangeManager = new PropertyRangeManager();
214 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
215 private delegate IntPtr CreateControlDelegate(IntPtr cPtrControlName);
217 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
218 private delegate IntPtr GetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName);
220 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
221 private delegate void SetPropertyDelegate(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue);
223 /// <since_tizen> 3 </since_tizen>
224 public static CustomViewRegistry Instance
228 if (instance == null)
230 instance = new CustomViewRegistry();
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.
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:
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) );
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)
255 if (null == viewType)
257 throw new ArgumentNullException(nameof(viewType));
260 // add the mapping between the view name and it's create function
261 constructorMap.Add(viewType.ToString(), createFunction);
263 // Call into DALi C++ to register the control with the type registry
264 TypeRegistration.RegisterControl(viewType.ToString(), createCallback);
266 // Cycle through each property in the class
267 foreach (System.Reflection.PropertyInfo propertyInfo in viewType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
269 if (propertyInfo.CanRead)
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();
275 foreach (System.Attribute attr in attrs)
277 // If the Scriptable attribute exists, then register it with the type registry.
278 if (attr is ScriptableProperty)
280 NUILog.Debug("Got a DALi JSON scriptable property = " + propertyInfo.Name + ", of type " + propertyInfo.PropertyType.Name);
282 // first get the attribute type, ( default, or animatable)
283 ScriptableProperty scriptableProp = attr as ScriptableProperty;
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);
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);
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);
296 NUILog.Debug("property name = " + propertyInfo.Name);
302 /// Called directly from DALi C++ type registry to create a control (view) using no marshalling.
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)
308 string controlName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(cPtrControlName);
310 NUILog.Debug("Create controlled called from C++ create a " + controlName);
312 Func<CustomView> controlConstructor;
314 // find the control constructor
315 if (Instance.constructorMap.TryGetValue(controlName, out controlConstructor))
317 // Create the control
318 CustomView newControl = controlConstructor();
319 if (newControl != null)
321 return newControl.GetPtrfromView(); // return pointer to handle
330 throw new global::System.InvalidOperationException("C# View not registered with ViewRegistry" + controlName);
334 private static IntPtr GetProperty(IntPtr controlPtr, IntPtr propertyName)
336 string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
337 return Instance.GetPropertyValue(controlPtr, name);
340 private static void SetProperty(IntPtr controlPtr, IntPtr propertyName, IntPtr propertyValue)
342 string name = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(propertyName);
344 NUILog.Debug("SetControlProperty called for:" + name);
346 Instance.SetPropertyValue(controlPtr, name, propertyValue);
349 private Tizen.NUI.PropertyType GetDaliPropertyType(string cSharpTypeName)
351 Tizen.NUI.PropertyType daliType;
352 if (daliPropertyTypeLookup.TryGetValue(cSharpTypeName, out daliType))
354 NUILog.Debug("mapped " + cSharpTypeName + " to dAli type " + daliType);
360 NUILog.Debug("Failed to find a mapping between C# property" + cSharpTypeName + " and DALi type");
362 return PropertyType.None;
367 /// Gets a property value from a view.
369 private IntPtr GetPropertyValue(IntPtr refObjectPtr, string propertyName)
371 // Get the C# control that maps to the C++ control
372 View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View;
375 // call the get property function
376 System.Object val = view.GetType().GetProperty(propertyName).GetAccessors()[0].Invoke(view, null);
378 PropertyValue value = PropertyValue.CreateFromObject(val);
379 IntPtr ptr = (IntPtr)PropertyValue.getCPtr(value);
391 /// Sets a property value on a view.
393 private void SetPropertyValue(IntPtr refObjectPtr, string propertyName, IntPtr propertyValuePtr)
395 // Get the C# control that maps to the C++ control
396 NUILog.Debug("SetPropertyValue refObjectPtr = {0:X}" + refObjectPtr);
398 PropertyValue propValue = new PropertyValue(propertyValuePtr, false);
400 // Get the C# control that maps to the C++ control
401 View view = Registry.GetManagedBaseHandleFromRefObject(refObjectPtr) as View;
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;
409 if (type.Equals(typeof(Int32)))
412 ok = propValue.Get(out value);
415 propertyInfo.SetValue(view, value);
418 else if (type.Equals(typeof(bool)))
421 ok = propValue.Get(out value);
424 propertyInfo.SetValue(view, value);
427 else if (type.Equals(typeof(float)))
430 ok = propValue.Get(out value);
433 propertyInfo.SetValue(view, value);
436 else if (type.Equals(typeof(string)))
439 ok = propValue.Get(out value);
442 propertyInfo.SetValue(view, value);
445 else if (type.Equals(typeof(Vector2)))
447 Vector2 value = new Vector2();
448 ok = propValue.Get(value);
451 propertyInfo.SetValue(view, value);
455 else if (type.Equals(typeof(Vector3)))
457 Vector3 value = new Vector3();
458 ok = propValue.Get(value);
461 propertyInfo.SetValue(view, value);
465 else if (type.Equals(typeof(Vector4)))
467 Vector4 value = new Vector4();
468 ok = propValue.Get(value);
472 propertyInfo.SetValue(view, value);
476 else if (type.Equals(typeof(Position)))
478 Position value = new Position();
479 ok = propValue.Get(value);
482 propertyInfo.SetValue(view, value);
486 else if (type.Equals(typeof(Size)))
488 Size value = new Size();
489 ok = propValue.Get(value);
492 Size sz = new Size(value.Width, value.Height, value.Depth);
493 propertyInfo.SetValue(view, sz);
498 else if (type.Equals(typeof(Color)))
500 // Colors are stored as Vector4's in DALi
501 Color value = new Color();
502 ok = propValue.Get(value);
505 propertyInfo.SetValue(view, (Color)value);
509 else if (type.Equals(typeof(PropertyMap)))
511 PropertyMap map = new PropertyMap();
512 ok = propValue.Get(map);
515 propertyInfo.SetValue(view, map);
519 else if (type.Equals(typeof(PropertyArray)))
521 PropertyArray array = new PropertyArray();
522 ok = propValue.Get(array);
525 propertyInfo.SetValue(view, array);
531 throw new global::System.InvalidOperationException("SetPropertyValue Unimplemented type for Property Value for " + type.FullName);
535 throw new global::System.InvalidOperationException("SetPropertyValue propValue.Get failed");
540 throw new global::System.InvalidOperationException("failed to find the control to write a property to: cptr = " + refObjectPtr);