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.
19 #define ExternalThemeEnabled
23 using System.Collections.Generic;
24 using System.ComponentModel;
25 using System.Diagnostics;
26 using System.Diagnostics.CodeAnalysis;
27 using Tizen.NUI.BaseComponents;
32 /// This static module provides methods that can manage NUI <see cref="Theme"/>.
35 /// To apply custom theme to the application, try <see cref="ApplyTheme(Theme)"/>.
37 /// var customTheme = new Theme(res + "customThemeFile.xaml");
38 /// ThemeManager.ApplyTheme(customTheme);
41 /// <summary></summary>
42 /// <since_tizen> 9 </since_tizen>
43 public static class ThemeManager
45 private static Theme baseTheme; // The base theme. It includes all styles including structures (Size, Position, Policy) of components.
46 private static Theme platformTheme; // The platform theme. This may include color and image information without structure detail.
47 private static Theme userTheme; // The user custom theme.
48 private static Theme themeForUpdate; // platformTheme + userTheme. It is used when the component need to update according to theme change.
49 private static Theme themeForInitialize; // baseTheme + platformTheme + userTheme. It is used when the component is created.
50 private static readonly List<Theme> cachedPlatformThemes = new List<Theme>(); // Themes provided by framework.
51 private static readonly List<IThemeCreator> packages = new List<IThemeCreator>();// This is to store base theme creators by packages.
52 private static bool platformThemeEnabled = false;
56 #if ExternalThemeEnabled
57 ExternalThemeManager.Initialize();
58 AddPackageTheme(DefaultThemeCreator.Instance);
63 /// An event invoked after the theme has changed by <see cref="ApplyTheme(Theme)"/>.
65 /// <since_tizen> 9 </since_tizen>
66 public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
69 /// Internal one should be called before calling public ThemeChanged
71 internal static WeakEvent<EventHandler<ThemeChangedEventArgs>> ThemeChangedInternal = new WeakEvent<EventHandler<ThemeChangedEventArgs>>();
74 /// The current theme Id.
75 /// It returns null when no theme is applied.
77 [EditorBrowsable(EditorBrowsableState.Never)]
78 public static string ThemeId
84 /// The current platform theme Id.
85 /// Note that it returns null when the platform theme is disabled.
86 /// If the <seealso cref="NUIApplication.ThemeOptions.PlatformThemeEnabled"/> is given, it can be one of followings in tizen 6.5:
87 /// <list type="bullet">
89 /// <description>org.tizen.default-light-theme</description>
92 /// <description>org.tizen.default-dark-theme</description>
96 [EditorBrowsable(EditorBrowsableState.Never)]
97 public static string PlatformThemeId
99 get => platformTheme?.Id ?? (platformThemeEnabled ? baseTheme.Id : null);
103 /// To support deprecated StyleManager.
104 /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
106 internal static Theme BaseTheme
112 UpdateThemeForInitialize();
117 /// To support deprecated StyleManager.
118 /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
120 internal static Theme CurrentTheme
122 get => userTheme ?? baseTheme;
126 UpdateThemeForInitialize();
127 NotifyThemeChanged();
131 internal static bool PlatformThemeEnabled
133 get => platformThemeEnabled;
136 if (platformThemeEnabled == value) return;
138 platformThemeEnabled = value;
140 if (platformThemeEnabled)
142 ApplyExternalPlatformTheme(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion);
147 internal static bool ApplicationThemeChangeSensitive { get; set; } = false;
150 /// Apply custom theme to the NUI.
151 /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
152 /// This also affects all components created afterwards.
154 /// <param name="theme">The theme instance to be applied.</param>
155 /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
156 /// <since_tizen> 9 </since_tizen>
157 public static void ApplyTheme(Theme theme)
159 var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
161 if (string.IsNullOrEmpty(newTheme.Id))
163 newTheme.Id = "NONAME";
166 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl ?? "");
167 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl ?? "");
168 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl ?? "");
170 userTheme = newTheme;
171 UpdateThemeForInitialize();
172 UpdateThemeForUpdate();
173 NotifyThemeChanged();
177 /// Change tizen theme.
178 /// User may change this to one of platform installed one.
180 /// <param name="themeId">The installed theme Id.</param>
181 /// <returns>true on success, false when it failed to find installed theme with given themeId.</returns>
182 /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
183 [EditorBrowsable(EditorBrowsableState.Never)]
184 public static bool ApplyPlatformTheme(string themeId)
186 if (themeId == null) throw new ArgumentNullException(nameof(themeId));
188 return ExternalThemeManager.SetTheme(themeId);
192 /// Load a style with style name in the current theme.
193 /// For components, the default style name of a component is a component name with namespace (e.g. Tizen.NUI.Components.Button).
195 /// <param name="styleName">The style name.</param>
196 /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
197 /// <since_tizen> 9 </since_tizen>
198 public static ViewStyle GetStyle(string styleName)
200 if (styleName == null) throw new ArgumentNullException(nameof(styleName));
201 return GetInitialStyleWithoutClone(styleName)?.Clone();
205 /// Load a style with view type in the current theme.
206 /// If it failed to find a style with the given type, it will try with it's parent type until it succeeds.
208 /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
209 /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
210 /// <since_tizen> 9 </since_tizen>
211 public static ViewStyle GetStyle(Type viewType)
213 if (viewType == null) throw new ArgumentNullException(nameof(viewType));
214 return GetInitialStyleWithoutClone(viewType)?.Clone();
218 /// Load a platform style with style name in the current theme.
219 /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
221 /// <param name="styleName">The style name.</param>
222 /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
223 [EditorBrowsable(EditorBrowsableState.Never)]
224 public static ViewStyle GetPlatformStyle(string styleName)
226 if (styleName == null) throw new ArgumentNullException(nameof(styleName));
227 return platformTheme?.GetStyle(styleName)?.Clone();
231 /// Load a platform style with view type in the current theme.
232 /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
234 /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
235 /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
236 [EditorBrowsable(EditorBrowsableState.Never)]
237 public static ViewStyle GetPlatformStyle(Type viewType)
239 if (viewType == null) throw new ArgumentNullException(nameof(viewType));
240 return platformTheme?.GetStyle(viewType)?.Clone();
244 /// Load a style with style name in the current theme.
246 /// <param name="styleName">The style name.</param>
247 internal static ViewStyle GetUpdateStyleWithoutClone(string styleName) => themeForUpdate?.GetStyle(styleName);
250 /// Load a style with View type in the current theme.
252 /// <param name="viewType">The type of View.</param>
253 internal static ViewStyle GetUpdateStyleWithoutClone(Type viewType) => themeForUpdate?.GetStyle(viewType);
256 /// Load a initial component style.
258 internal static ViewStyle GetInitialStyleWithoutClone(string styleName) => themeForInitialize.GetStyle(styleName);
261 /// Load a initial component style.
263 internal static ViewStyle GetInitialStyleWithoutClone(Type viewType) => themeForInitialize.GetStyle(viewType);
266 /// Get a platform installed theme.
268 /// <param name="themeId">The theme id.</param>
269 internal static Theme LoadPlatformTheme(string themeId)
271 Debug.Assert(themeId != null);
273 // Check if it is already loaded.
274 int index = cachedPlatformThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
277 Tizen.Log.Info("NUI", $"Hit cache.");
278 var found = cachedPlatformThemes[index];
279 // If the cached is not a full set, update it.
280 if (found.PackageCount < packages.Count)
282 UpdatePlatformTheme(found);
283 Tizen.Log.Info("NUI", $"Update cache.");
288 var newTheme = CreatePlatformTheme(themeId);
289 if (newTheme != null)
291 cachedPlatformThemes.Add(newTheme);
292 Tizen.Log.Info("NUI", $"Platform theme has been loaded successfully.");
298 /// !!! This is for internal use in fhub-nui. Do not open it.
299 /// Set a theme to be used as fallback.
300 /// The fallback theme is set to profile specified theme by default.
302 /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
303 [EditorBrowsable(EditorBrowsableState.Never)]
304 internal static void ApplyFallbackTheme(Theme fallbackTheme)
306 Debug.Assert(fallbackTheme != null);
307 BaseTheme = (Theme)fallbackTheme?.Clone();
311 /// Apply an external platform theme.
313 /// <param name="id">The external theme id.</param>
314 /// <param name="version">The external theme version.</param>
315 internal static void ApplyExternalPlatformTheme(string id, string version)
317 #if ExternalThemeEnabled
318 Debug.Assert(baseTheme != null);
320 // If the given theme is invalid, do nothing.
321 if (string.IsNullOrEmpty(id))
326 // If no platform theme has been applied and the base theme can cover the given one, do nothing.
327 if (platformTheme == null && baseTheme.HasSameIdAndVersion(id, version))
329 Tizen.Log.Info("NUI", "The base theme can cover platform theme: Skip loading.");
333 // If the given theme is already applied, do nothing.
334 if (platformTheme != null && platformTheme.HasSameIdAndVersion(id, version))
336 Tizen.Log.Info("NUI", "Platform theme is already applied: Skip loading.");
340 var loaded = LoadPlatformTheme(id);
344 Tizen.Log.Info("NUI", $"{loaded.Id} has been applied successfully.");
345 platformTheme = loaded;
346 UpdateThemeForInitialize();
347 UpdateThemeForUpdate();
348 NotifyThemeChanged(true);
353 internal static void AddPackageTheme(IThemeCreator themeCreator)
356 // for tv profile, set empty theme and just return here!
358 baseTheme = themeCreator.Create();
361 if (packages.Contains(themeCreator))
366 Tizen.Log.Info("NUI", $"AddPackageTheme({themeCreator.GetType().Assembly.GetName().Name})");
367 packages.Add(themeCreator);
370 var packageBaseTheme = themeCreator.Create();
371 Debug.Assert(packageBaseTheme != null);
373 if (baseTheme == null) baseTheme = packageBaseTheme;
374 else baseTheme.MergeWithoutClone(packageBaseTheme);
375 baseTheme.PackageCount++;
377 #if ExternalThemeEnabled
378 if (platformThemeEnabled)
380 Tizen.Log.Info("NUI", $"Platform theme is enabled");
381 if (platformTheme != null)
383 UpdatePlatformTheme(platformTheme);
387 if (!string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId) && !baseTheme.HasSameIdAndVersion(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion))
389 var loaded = LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
392 platformTheme = loaded;
396 UpdateThemeForUpdate();
399 UpdateThemeForInitialize();
402 internal static void Preload()
404 #if ExternalThemeEnabled
405 Debug.Assert(baseTheme != null);
407 if (string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId)) return;
409 LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
413 // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
414 internal static void UpdateThemeForUpdate()
416 if (userTheme == null)
418 themeForUpdate = platformTheme;
422 if (platformTheme == null)
424 themeForUpdate = userTheme;
428 themeForUpdate = new Theme();
429 themeForUpdate.Merge(platformTheme);
430 themeForUpdate.MergeWithoutClone(userTheme);
433 // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
434 internal static void UpdateThemeForInitialize()
436 if (platformTheme == null && userTheme == null)
438 themeForInitialize = baseTheme;
442 themeForInitialize = new Theme();
443 themeForInitialize.Merge(baseTheme);
445 if (userTheme == null)
447 if (platformTheme != null) themeForInitialize.MergeWithoutClone(platformTheme);
451 if (platformTheme != null) themeForInitialize.Merge(platformTheme);
452 themeForInitialize.MergeWithoutClone(userTheme);
456 private static void UpdatePlatformTheme(Theme theme)
458 var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(theme.Id);
460 if (sharedResourcePath == null)
465 for (var i = theme.PackageCount; i < packages.Count; i++)
467 theme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packages[i].GetType().Assembly.GetName().Name));
469 theme.PackageCount = packages.Count;
472 private static Theme CreatePlatformTheme(string id)
474 var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(id);
476 if (sharedResourcePath == null)
481 var newTheme = new Theme()
486 foreach (var packageCreator in packages)
488 newTheme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packageCreator.GetType().Assembly.GetName().Name));
490 newTheme.PackageCount = packages.Count;
495 [SuppressMessage("Microsoft.Design", "CA1031: Do not catch general exception types", Justification = "This method is to handle external resources that may throw an exception but ignorable. This method should not interrupt the main stream.")]
496 private static Theme CreatePlatformTheme(string sharedResourcePath, string assemblyName)
498 ExternalThemeManager.SharedResourcePath = sharedResourcePath;
501 return new Theme(sharedResourcePath + assemblyName + ".Theme.xaml");
503 catch (System.IO.FileNotFoundException)
505 Tizen.Log.Info("NUI", $"[Ignorable] Current tizen theme does not have NUI theme.");
509 Tizen.Log.Info("NUI", $"[Ignorable] {e.GetType().Name} occurred while applying tizen theme to {assemblyName}: {e.Message}");
515 private static void AddToPlatformThemes(Theme theme)
517 int index = cachedPlatformThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
520 Tizen.Log.Info("NUI", $"Existing {theme.Id} item is overwritten");
521 cachedPlatformThemes[index] = theme;
525 cachedPlatformThemes.Add(theme);
526 Tizen.Log.Info("NUI", $"New {theme.Id} is saved.");
530 private static void NotifyThemeChanged(bool platformThemeUpdated = false)
532 Debug.Assert(baseTheme != null);
534 var platformThemeId = PlatformThemeId;
535 var userThemeId = userTheme?.Id;
536 ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
537 ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));