2 * Copyright(c) 2022 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.
19 using System.Collections;
20 using System.Collections.Generic;
21 using System.Collections.ObjectModel;
22 using System.Collections.Specialized;
23 using System.ComponentModel;
24 using System.Globalization;
26 using System.Reflection;
27 using System.Runtime.CompilerServices;
29 using Tizen.NUI.Binding.Internals;
32 namespace Tizen.NUI.Binding
34 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
35 [EditorBrowsable(EditorBrowsableState.Never)]
36 public class ResourceDictionary : IResourceDictionary, IDictionary<string, object>
38 static ConditionalWeakTable<Type, ResourceDictionary> s_instances = new ConditionalWeakTable<Type, ResourceDictionary>();
39 readonly Dictionary<string, object> innerDictionary = new Dictionary<string, object>();
40 ResourceDictionary mergedInstance;
44 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
45 [EditorBrowsable(EditorBrowsableState.Never)]
46 public ResourceDictionary()
48 DependencyService.Register<IResourcesLoader, ResourcesLoader>();
52 /// Gets or sets the type of object with which the resource dictionary is merged.
54 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
55 [EditorBrowsable(EditorBrowsableState.Never)]
56 [TypeConverter(typeof(TypeTypeConverter))]
57 [Obsolete("Use Source")]
58 public Type MergedWith
60 get { return mergedWith; }
63 if (mergedWith == value)
67 throw new ArgumentException("MergedWith can not be used with Source");
69 if (!typeof(ResourceDictionary).GetTypeInfo().IsAssignableFrom(value.GetTypeInfo()))
70 throw new ArgumentException("MergedWith should inherit from ResourceDictionary");
73 if (mergedWith == null)
76 mergedInstance = s_instances.GetValue(mergedWith, (key) => (ResourceDictionary)Activator.CreateInstance(key));
77 OnValuesChanged(mergedInstance.ToArray());
82 /// Gets or sets the resource dictionary.
84 /// Internal using, will never open.
85 [EditorBrowsable(EditorBrowsableState.Never)]
86 [TypeConverter(typeof(RDSourceTypeConverter))]
87 public ResourceDictionary Source
95 OnValuesChanged(value.ToArray());
97 foreach (var pair in value)
99 Add(pair.Key, pair.Value);
105 /// To set and load source.
107 /// <param name="value">The source.</param>
108 /// <param name="resourcePath">The resource path.</param>
109 /// <param name="assembly">The assembly.</param>
110 /// <param name="lineInfo">The xml line info.</param>
111 /// Used by the XamlC compiled converter.
112 [EditorBrowsable(EditorBrowsableState.Never)]
113 public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
116 if (mergedWith != null)
117 throw new ArgumentException("Source can not be used with MergedWith");
119 //this will return a type if the RD as an x:Class element, and codebehind
120 var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
122 mergedInstance = s_instances.GetValue(type, (key) => (ResourceDictionary)Activator.CreateInstance(key));
124 mergedInstance = DependencyService.Get<IResourcesLoader>()?.CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
125 OnValuesChanged(mergedInstance.ToArray());
128 ICollection<ResourceDictionary> _mergedDictionaries;
130 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
131 [EditorBrowsable(EditorBrowsableState.Never)]
132 public ICollection<ResourceDictionary> MergedDictionaries
136 if (_mergedDictionaries == null)
138 var col = new ObservableCollection<ResourceDictionary>();
139 col.CollectionChanged += MergedDictionaries_CollectionChanged;
140 _mergedDictionaries = col;
142 return _mergedDictionaries;
146 IList<ResourceDictionary> _collectionTrack;
148 void MergedDictionaries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
150 // Move() isn't exposed by ICollection
151 if (e.Action == NotifyCollectionChangedAction.Move)
154 _collectionTrack = _collectionTrack ?? new List<ResourceDictionary>();
155 // Collection has been cleared
156 if (e.Action == NotifyCollectionChangedAction.Reset)
158 foreach (var dictionary in _collectionTrack)
159 dictionary.ValuesChanged -= Item_ValuesChanged;
161 _collectionTrack.Clear();
166 if (e.NewItems != null)
168 foreach (var item in e.NewItems)
170 var rd = (ResourceDictionary)item;
171 _collectionTrack.Add(rd);
172 rd.ValuesChanged += Item_ValuesChanged;
173 OnValuesChanged(rd.ToArray());
178 if (e.OldItems != null)
180 foreach (var item in e.OldItems)
182 var rd = (ResourceDictionary)item;
183 rd.ValuesChanged -= Item_ValuesChanged;
184 _collectionTrack.Remove(rd);
189 void Item_ValuesChanged(object sender, ResourcesChangedEventArgs e)
191 OnValuesChanged(e.Values.ToArray());
194 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
196 ((ICollection<KeyValuePair<string, object>>)innerDictionary).Add(item);
197 OnValuesChanged(item);
200 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
201 [EditorBrowsable(EditorBrowsableState.Never)]
204 innerDictionary.Clear();
207 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
209 return ((ICollection<KeyValuePair<string, object>>)innerDictionary).Contains(item)
210 || (mergedInstance != null && mergedInstance.Contains(item));
213 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
215 ((ICollection<KeyValuePair<string, object>>)innerDictionary).CopyTo(array, arrayIndex);
218 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
219 [EditorBrowsable(EditorBrowsableState.Never)]
222 get { return innerDictionary.Count; }
225 bool ICollection<KeyValuePair<string, object>>.IsReadOnly
227 get { return ((ICollection<KeyValuePair<string, object>>)innerDictionary).IsReadOnly; }
230 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
232 return ((ICollection<KeyValuePair<string, object>>)innerDictionary).Remove(item);
235 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
236 [EditorBrowsable(EditorBrowsableState.Never)]
237 public void Add(string key, object value)
239 if (ContainsKey(key))
240 throw new ArgumentException($"A resource with the key '{key}' is already present in the ResourceDictionary.");
241 innerDictionary.Add(key, value);
242 OnValueChanged(key, value);
245 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
246 [EditorBrowsable(EditorBrowsableState.Never)]
247 public bool ContainsKey(string key)
249 return innerDictionary.ContainsKey(key);
253 /// Gets or sets the value according to index.
255 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
256 [EditorBrowsable(EditorBrowsableState.Never)]
257 [IndexerName("Item")]
258 public object this[string index]
262 if (innerDictionary.ContainsKey(index))
263 return innerDictionary[index];
264 if (mergedInstance != null && mergedInstance.ContainsKey(index))
265 return mergedInstance[index];
266 if (MergedDictionaries != null)
267 foreach (var dict in MergedDictionaries.Reverse())
268 if (dict.ContainsKey(index))
270 throw new KeyNotFoundException($"The resource '{index}' is not present in the dictionary.");
274 innerDictionary[index] = value;
275 OnValueChanged(index, value);
279 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
280 [EditorBrowsable(EditorBrowsableState.Never)]
281 public ICollection<string> Keys
283 get { return innerDictionary.Keys; }
286 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
287 [EditorBrowsable(EditorBrowsableState.Never)]
288 public bool Remove(string key)
290 return innerDictionary.Remove(key);
293 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
294 [EditorBrowsable(EditorBrowsableState.Never)]
295 public ICollection<object> Values
297 get { return innerDictionary.Values; }
300 IEnumerator IEnumerable.GetEnumerator()
302 return GetEnumerator();
305 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
306 [EditorBrowsable(EditorBrowsableState.Never)]
307 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
309 return innerDictionary.GetEnumerator();
312 internal IEnumerable<KeyValuePair<string, object>> MergedResources
316 if (MergedDictionaries != null)
317 foreach (var r in MergedDictionaries.Reverse().SelectMany(x => x.MergedResources))
319 if (mergedInstance != null)
320 foreach (var r in mergedInstance.MergedResources)
322 foreach (var r in innerDictionary)
327 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
328 [EditorBrowsable(EditorBrowsableState.Never)]
329 public bool TryGetValue(string key, out object value)
331 return innerDictionary.TryGetValue(key, out value)
332 || (mergedInstance != null && mergedInstance.TryGetValue(key, out value))
333 || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
336 bool TryGetMergedDictionaryValue(string key, out object value)
338 foreach (var dictionary in MergedDictionaries.Reverse())
339 if (dictionary.TryGetValue(key, out value))
346 event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
348 add { ValuesChanged += value; }
349 remove { ValuesChanged -= value; }
352 internal void Add(XamlStyle style)
354 if (string.IsNullOrEmpty(style.Class))
355 Add(style.TargetType.FullName, style);
358 IList<XamlStyle> classes;
360 if (!TryGetValue(XamlStyle.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<XamlStyle>) == null)
361 classes = new List<XamlStyle>();
363 this[XamlStyle.StyleClassPrefix + style.Class] = classes;
367 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
368 [EditorBrowsable(EditorBrowsableState.Never)]
369 public void Add(ResourceDictionary mergedResourceDictionary)
371 MergedDictionaries.Add(mergedResourceDictionary);
374 void OnValueChanged(string key, object value)
376 OnValuesChanged(new KeyValuePair<string, object>(key, value));
379 void OnValuesChanged(params KeyValuePair<string, object>[] values)
381 if (values == null || values.Length == 0)
383 ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
386 event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
388 [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Core.XamlC.RDSourceTypeConverter")]
389 internal class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter
391 object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
393 if (serviceProvider == null)
394 throw new ArgumentNullException(nameof(serviceProvider));
396 var targetRD = (serviceProvider.GetService(typeof(Xaml.IProvideValueTarget)) as Xaml.IProvideValueTarget)?.TargetObject as ResourceDictionary;
397 if (targetRD == null)
400 var rootObjectType = (serviceProvider.GetService(typeof(Xaml.IRootObjectProvider)) as Xaml.IRootObjectProvider)?.RootObject.GetType();
401 if (rootObjectType == null)
404 var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
405 var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
406 var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
407 var resourcePath = GetResourcePath(uri, rootTargetPath);
409 targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
413 internal static string GetResourcePath(Uri uri, string rootTargetPath)
415 //need a fake scheme so it's not seen as file:// uri, and the forward slashes are valid on all plats
416 var resourceUri = uri.OriginalString.StartsWith("/", StringComparison.Ordinal)
417 ? new Uri($"pack://{uri.OriginalString}", UriKind.Absolute)
418 : new Uri($"pack:///{rootTargetPath}/../{uri.OriginalString}", UriKind.Absolute);
420 //drop the leading '/'
421 return resourceUri.AbsolutePath.Substring(1);
424 object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
426 throw new NotImplementedException();
429 public override object ConvertFromInvariantString(string value)
431 throw new NotImplementedException();