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;
30 namespace Tizen.NUI.Binding
32 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
33 [EditorBrowsable(EditorBrowsableState.Never)]
34 public class ResourceDictionary : IResourceDictionary, IDictionary<string, object>
36 static ConditionalWeakTable<Type, ResourceDictionary> s_instances = new ConditionalWeakTable<Type, ResourceDictionary>();
37 readonly Dictionary<string, object> innerDictionary = new Dictionary<string, object>();
38 ResourceDictionary mergedInstance;
42 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
43 [EditorBrowsable(EditorBrowsableState.Never)]
44 public ResourceDictionary()
46 DependencyService.Register<IResourcesLoader, ResourcesLoader>();
50 /// Gets or sets the type of object with which the resource dictionary is merged.
52 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
53 [EditorBrowsable(EditorBrowsableState.Never)]
54 [TypeConverter(typeof(TypeTypeConverter))]
55 [Obsolete("Use Source")]
56 public Type MergedWith
58 get { return mergedWith; }
61 if (mergedWith == value)
65 throw new ArgumentException("MergedWith can not be used with Source");
67 if (!typeof(ResourceDictionary).GetTypeInfo().IsAssignableFrom(value.GetTypeInfo()))
68 throw new ArgumentException("MergedWith should inherit from ResourceDictionary");
71 if (mergedWith == null)
74 mergedInstance = s_instances.GetValue(mergedWith, (key) => (ResourceDictionary)Activator.CreateInstance(key));
75 OnValuesChanged(mergedInstance.ToArray());
80 /// Gets or sets the URI of the merged resource dictionary.
82 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
83 [EditorBrowsable(EditorBrowsableState.Never)]
84 [TypeConverter(typeof(RDSourceTypeConverter))]
87 get { return source; }
92 throw new InvalidOperationException("Source can only be set from XAML."); //through the RDSourceTypeConverter
97 /// To set and load source.
99 /// <param name="value">The source.</param>
100 /// <param name="resourcePath">The resource path.</param>
101 /// <param name="assembly">The assembly.</param>
102 /// <param name="lineInfo">The xml line info.</param>
103 /// Used by the XamlC compiled converter.
104 [EditorBrowsable(EditorBrowsableState.Never)]
105 public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
108 if (mergedWith != null)
109 throw new ArgumentException("Source can not be used with MergedWith");
111 //this will return a type if the RD as an x:Class element, and codebehind
112 var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
114 mergedInstance = s_instances.GetValue(type, (key) => (ResourceDictionary)Activator.CreateInstance(key));
116 mergedInstance = DependencyService.Get<IResourcesLoader>()?.CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
117 OnValuesChanged(mergedInstance.ToArray());
120 ICollection<ResourceDictionary> _mergedDictionaries;
122 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
123 [EditorBrowsable(EditorBrowsableState.Never)]
124 public ICollection<ResourceDictionary> MergedDictionaries
128 if (_mergedDictionaries == null)
130 var col = new ObservableCollection<ResourceDictionary>();
131 col.CollectionChanged += MergedDictionaries_CollectionChanged;
132 _mergedDictionaries = col;
134 return _mergedDictionaries;
138 IList<ResourceDictionary> _collectionTrack;
140 void MergedDictionaries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
142 // Move() isn't exposed by ICollection
143 if (e.Action == NotifyCollectionChangedAction.Move)
146 _collectionTrack = _collectionTrack ?? new List<ResourceDictionary>();
147 // Collection has been cleared
148 if (e.Action == NotifyCollectionChangedAction.Reset)
150 foreach (var dictionary in _collectionTrack)
151 dictionary.ValuesChanged -= Item_ValuesChanged;
153 _collectionTrack.Clear();
158 if (e.NewItems != null)
160 foreach (var item in e.NewItems)
162 var rd = (ResourceDictionary)item;
163 _collectionTrack.Add(rd);
164 rd.ValuesChanged += Item_ValuesChanged;
165 OnValuesChanged(rd.ToArray());
170 if (e.OldItems != null)
172 foreach (var item in e.OldItems)
174 var rd = (ResourceDictionary)item;
175 rd.ValuesChanged -= Item_ValuesChanged;
176 _collectionTrack.Remove(rd);
181 void Item_ValuesChanged(object sender, ResourcesChangedEventArgs e)
183 OnValuesChanged(e.Values.ToArray());
186 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
188 ((ICollection<KeyValuePair<string, object>>)innerDictionary).Add(item);
189 OnValuesChanged(item);
192 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
193 [EditorBrowsable(EditorBrowsableState.Never)]
196 innerDictionary.Clear();
199 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
201 return ((ICollection<KeyValuePair<string, object>>)innerDictionary).Contains(item)
202 || (mergedInstance != null && mergedInstance.Contains(item));
205 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
207 ((ICollection<KeyValuePair<string, object>>)innerDictionary).CopyTo(array, arrayIndex);
210 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
211 [EditorBrowsable(EditorBrowsableState.Never)]
214 get { return innerDictionary.Count; }
217 bool ICollection<KeyValuePair<string, object>>.IsReadOnly
219 get { return ((ICollection<KeyValuePair<string, object>>)innerDictionary).IsReadOnly; }
222 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
224 return ((ICollection<KeyValuePair<string, object>>)innerDictionary).Remove(item);
227 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
228 [EditorBrowsable(EditorBrowsableState.Never)]
229 public void Add(string key, object value)
231 if (ContainsKey(key))
232 throw new ArgumentException($"A resource with the key '{key}' is already present in the ResourceDictionary.");
233 innerDictionary.Add(key, value);
234 OnValueChanged(key, value);
237 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
238 [EditorBrowsable(EditorBrowsableState.Never)]
239 public bool ContainsKey(string key)
241 return innerDictionary.ContainsKey(key);
245 /// Gets or sets the value according to index.
247 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
248 [EditorBrowsable(EditorBrowsableState.Never)]
249 [IndexerName("Item")]
250 public object this[string index]
254 if (innerDictionary.ContainsKey(index))
255 return innerDictionary[index];
256 if (mergedInstance != null && mergedInstance.ContainsKey(index))
257 return mergedInstance[index];
258 if (MergedDictionaries != null)
259 foreach (var dict in MergedDictionaries.Reverse())
260 if (dict.ContainsKey(index))
262 throw new KeyNotFoundException($"The resource '{index}' is not present in the dictionary.");
266 innerDictionary[index] = value;
267 OnValueChanged(index, value);
271 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
272 [EditorBrowsable(EditorBrowsableState.Never)]
273 public ICollection<string> Keys
275 get { return innerDictionary.Keys; }
278 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
279 [EditorBrowsable(EditorBrowsableState.Never)]
280 public bool Remove(string key)
282 return innerDictionary.Remove(key);
285 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
286 [EditorBrowsable(EditorBrowsableState.Never)]
287 public ICollection<object> Values
289 get { return innerDictionary.Values; }
292 IEnumerator IEnumerable.GetEnumerator()
294 return GetEnumerator();
297 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
298 [EditorBrowsable(EditorBrowsableState.Never)]
299 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
301 return innerDictionary.GetEnumerator();
304 internal IEnumerable<KeyValuePair<string, object>> MergedResources
308 if (MergedDictionaries != null)
309 foreach (var r in MergedDictionaries.Reverse().SelectMany(x => x.MergedResources))
311 if (mergedInstance != null)
312 foreach (var r in mergedInstance.MergedResources)
314 foreach (var r in innerDictionary)
319 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
320 [EditorBrowsable(EditorBrowsableState.Never)]
321 public bool TryGetValue(string key, out object value)
323 return innerDictionary.TryGetValue(key, out value)
324 || (mergedInstance != null && mergedInstance.TryGetValue(key, out value))
325 || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
328 bool TryGetMergedDictionaryValue(string key, out object value)
330 foreach (var dictionary in MergedDictionaries.Reverse())
331 if (dictionary.TryGetValue(key, out value))
338 event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
340 add { ValuesChanged += value; }
341 remove { ValuesChanged -= value; }
344 internal void Add(XamlStyle style)
346 if (string.IsNullOrEmpty(style.Class))
347 Add(style.TargetType.FullName, style);
350 IList<XamlStyle> classes;
352 if (!TryGetValue(XamlStyle.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<XamlStyle>) == null)
353 classes = new List<XamlStyle>();
355 this[XamlStyle.StyleClassPrefix + style.Class] = classes;
359 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
360 [EditorBrowsable(EditorBrowsableState.Never)]
361 public void Add(ResourceDictionary mergedResourceDictionary)
363 MergedDictionaries.Add(mergedResourceDictionary);
366 void OnValueChanged(string key, object value)
368 OnValuesChanged(new KeyValuePair<string, object>(key, value));
371 void OnValuesChanged(params KeyValuePair<string, object>[] values)
373 if (values == null || values.Length == 0)
375 ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
378 event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
380 [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Core.XamlC.RDSourceTypeConverter")]
381 internal class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter
383 object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
385 if (serviceProvider == null)
386 throw new ArgumentNullException(nameof(serviceProvider));
388 var targetRD = (serviceProvider.GetService(typeof(Xaml.IProvideValueTarget)) as Xaml.IProvideValueTarget)?.TargetObject as ResourceDictionary;
389 if (targetRD == null)
392 var rootObjectType = (serviceProvider.GetService(typeof(Xaml.IRootObjectProvider)) as Xaml.IRootObjectProvider)?.RootObject.GetType();
393 if (rootObjectType == null)
396 var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
397 var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
398 var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
399 var resourcePath = GetResourcePath(uri, rootTargetPath);
401 targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
405 internal static string GetResourcePath(Uri uri, string rootTargetPath)
407 //need a fake scheme so it's not seen as file:// uri, and the forward slashes are valid on all plats
408 var resourceUri = uri.OriginalString.StartsWith("/", StringComparison.Ordinal)
409 ? new Uri($"pack://{uri.OriginalString}", UriKind.Absolute)
410 : new Uri($"pack:///{rootTargetPath}/../{uri.OriginalString}", UriKind.Absolute);
412 //drop the leading '/'
413 return resourceUri.AbsolutePath.Substring(1);
416 object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
418 throw new NotImplementedException();
421 public override object ConvertFromInvariantString(string value)
423 throw new NotImplementedException();