2 * Copyright(c) 2020 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.Collections.Generic;
19 using System.ComponentModel;
20 using System.Reflection;
22 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding;
30 /// Basically, the Theme is a dictionary of <seealso cref="ViewStyle"/>s that can decorate NUI <seealso cref="View"/>s.
31 /// Each ViewStyle item is identified by a string key that can be matched the <seealso cref="View.StyleName"/>.
34 /// The main purpose of providing Theme is to separate style details from the structure.
35 /// Managing style separately makes it easier to customize the look of application by user context.
36 /// Also since a Theme can be created from xaml file, it can be treated as a resource.
37 /// This enables sharing styles with other applications.
40 [EditorBrowsable(EditorBrowsableState.Never)]
41 public class Theme : BindableObject, IResourcesProvider
43 private readonly Dictionary<string, ViewStyle> map;
44 private string baseTheme;
45 private string resource;
46 private string xamlFile;
49 /// The resource file path that is used in the theme.
51 [EditorBrowsable(EditorBrowsableState.Never)]
52 public string Resource
57 if (resource == value) return;
64 /// <summary>Create an empty theme.</summary>
65 [EditorBrowsable(EditorBrowsableState.Never)]
68 map = new Dictionary<string, ViewStyle>();
71 /// <summary>Create a new theme from the xaml file.</summary>
72 /// <param name="xamlFile">An absolute path to the xaml file.</param>
73 /// <exception cref="ArgumentNullException">Thrown when the given xamlFile is null or empty string.</exception>
74 /// <exception cref="global::System.IO.IOException">Thrown when there are file IO problems.</exception>
75 /// <exception cref="Exception">Thrown when the content of the xaml file is not valid xaml form.</exception>
76 [EditorBrowsable(EditorBrowsableState.Never)]
77 public Theme(string xamlFile) : this()
79 if (string.IsNullOrEmpty(xamlFile))
81 throw new ArgumentNullException(nameof(xamlFile), "The xaml file path cannot be null or empty string");
84 LoadFromXaml(xamlFile);
88 /// Create a new theme from the xaml file with theme resource.
90 /// <param name="xamlFile">An absolute path to the xaml file.</param>
91 /// <param name="themeResource">An absolute path to the theme resource file.</param>
92 /// <exception cref="ArgumentNullException">Thrown when the given xamlFile or themeResource is null or empty string.</exception>
93 /// <exception cref="System.IO.IOException">Thrown when there are file IO problems.</exception>
94 /// <exception cref="Exception">Thrown when the content of the xaml file is not valid xaml form.</exception>
95 [EditorBrowsable(EditorBrowsableState.Never)]
96 public Theme(string xamlFile, string themeResource) : this()
98 if (string.IsNullOrEmpty(xamlFile))
99 throw new ArgumentNullException(nameof(xamlFile), "The xaml file path cannot be null or empty string");
100 if (string.IsNullOrEmpty(themeResource))
101 throw new ArgumentNullException(nameof(themeResource), "The theme resource file path cannot be null or empty string");
103 resource = themeResource;
104 XamlResources.SetAndLoadSource(new Uri(themeResource), themeResource, Assembly.GetAssembly(GetType()), null);
106 LoadFromXaml(xamlFile);
110 /// The string key to identify the Theme.
112 [EditorBrowsable(EditorBrowsableState.Never)]
113 public string Id { get; set; }
116 /// For Xaml use only.
117 /// The bulit-in theme id that will be used as base of this.
118 /// View styles with same key are merged.
120 internal string BasedOn
127 if (string.IsNullOrEmpty(baseTheme)) return;
129 var baseThemeInstance = ThemeManager.GetBuiltinTheme(baseTheme);
131 if (baseThemeInstance != null)
133 foreach (var item in baseThemeInstance)
135 var baseStyle = item.Value?.Clone();
136 if (map.ContainsKey(item.Key))
138 baseStyle.Merge(map[item.Key]);
140 map[item.Key] = baseStyle;
147 [EditorBrowsable(EditorBrowsableState.Never)]
148 public bool IsResourcesCreated { get; } = true;
151 [EditorBrowsable(EditorBrowsableState.Never)]
152 public ResourceDictionary XamlResources { get; set; } = new ResourceDictionary();
155 /// For Xaml use only.
156 /// Note that it is not a normal indexer.
157 /// Setter will merge the new value with existing item.
159 internal ViewStyle this[string styleName]
161 get => map[styleName];
166 map.Remove(styleName);
170 if (map.TryGetValue(styleName, out ViewStyle style) && style != null && style.GetType() == value.GetType())
176 map[styleName] = value;
181 internal int Count => map.Count;
184 /// Get an enumerator of the theme.
186 [EditorBrowsable(EditorBrowsableState.Never)]
187 public IEnumerator<KeyValuePair<string, ViewStyle>> GetEnumerator() => map.GetEnumerator();
190 /// Removes all styles in the theme.
192 [EditorBrowsable(EditorBrowsableState.Never)]
193 public void Clear() => map.Clear();
196 /// Determines whether the theme contains the specified style name.
198 /// <exception cref="ArgumentNullException">The given style name is null.</exception>
199 [EditorBrowsable(EditorBrowsableState.Never)]
200 public bool HasStyle(string styleName) => map.ContainsKey(styleName);
203 /// Removes the style with the specified style name.
205 /// <exception cref="ArgumentNullException">The given style name is null.</exception>
206 [EditorBrowsable(EditorBrowsableState.Never)]
207 public bool RemoveStyle(string styleName) => map.Remove(styleName);
210 /// Gets a style of given style name.
212 /// <param name="styleName">The string key to find a ViewStyle.</param>
213 /// <returns>Founded style instance.</returns>
214 [EditorBrowsable(EditorBrowsableState.Never)]
215 public ViewStyle GetStyle(string styleName) => map.ContainsKey(styleName) ? map[styleName] : null;
218 /// Gets a style of given view type.
220 /// <param name="viewType">The type of View.</param>
221 /// <returns>Founded style instance.</returns>
222 /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
223 [EditorBrowsable(EditorBrowsableState.Never)]
224 public ViewStyle GetStyle(Type viewType)
226 var currentType = viewType ?? throw new ArgumentNullException(nameof(viewType));
227 ViewStyle resultStyle = null;
231 if (currentType.Equals(typeof(View))) break;
232 resultStyle = GetStyle(currentType.FullName);
233 currentType = currentType.BaseType;
235 while (resultStyle == null && currentType != null);
241 /// Adds the specified style name and value to the theme.
242 /// This replace existing value if the theme already has a style with given name.
244 /// <param name="styleName">The style name to add.</param>
245 /// <param name="value">The style instance to add.</param>
246 [EditorBrowsable(EditorBrowsableState.Never)]
247 public void AddStyle(string styleName, ViewStyle value) => map[styleName] = value?.Clone();
251 [EditorBrowsable(EditorBrowsableState.Never)]
252 public object Clone()
254 var result = new Theme()
259 foreach (var item in this)
261 result.AddStyle(item.Key, item.Value);
266 /// <summary>Merge other Theme into this.</summary>
267 /// <param name="xamlFile">An absolute path to the xaml file of the theme.</param>
268 /// <exception cref="ArgumentException">Thrown when the given xamlFile is null or empty string.</exception>
269 /// <exception cref="global::System.IO.IOException">Thrown when there are file IO problems.</exception>
270 /// <exception cref="XamlParseException">Thrown when the content of the xaml file is not valid xaml form.</exception>
271 [EditorBrowsable(EditorBrowsableState.Never)]
272 public void Merge(string xamlFile)
274 Merge(new Theme(xamlFile));
277 /// <summary>Merge other Theme into this.</summary>
278 /// <param name="theme">The Theme.</param>
279 [EditorBrowsable(EditorBrowsableState.Never)]
280 public void Merge(Theme theme)
283 throw new ArgumentNullException(nameof(theme));
285 this.xamlFile = theme.xamlFile;
287 foreach (var item in theme)
289 if (item.Value == null)
291 map[item.Key] = null;
293 else if (map.ContainsKey(item.Key) && !item.Value.SolidNull)
295 map[item.Key].Merge(theme.GetStyle(item.Key));
299 map[item.Key] = theme.GetStyle(item.Key).Clone();
305 /// Internal use only.
307 internal void AddStyleWithoutClone(string styleName, ViewStyle value) => map[styleName] = value;
309 internal void Reload()
311 if (xamlFile == null)
312 throw new InvalidOperationException("Cannot reload without xaml file.");
315 if (Resource != null)
317 XamlResources.Clear();
318 XamlResources.SetAndLoadSource(new Uri(Resource), Resource, Assembly.GetAssembly(GetType()), null);
321 LoadFromXaml(xamlFile);
324 private void LoadFromXaml(string xamlFile)
328 using (var reader = XmlReader.Create(xamlFile))
330 this.xamlFile = xamlFile;
331 XamlLoader.Load(this, reader);
334 catch (System.IO.IOException)
336 Tizen.Log.Error("NUI", $"Could not load \"{xamlFile}\".\n");
341 Tizen.Log.Error("NUI", $"Could not parse \"{xamlFile}\".\n");
342 Tizen.Log.Error("NUI", "Make sure the all used assemblies (e.g. Tizen.NUI.Components) are included in the application project.\n");
343 Tizen.Log.Error("NUI", "Make sure the type and namespace are correct.\n");