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 Tizen.Log.Info("NUI", $"must not be shown in PROFILE_TV");
58 ExternalThemeManager.Initialize();
60 AddPackageTheme(DefaultThemeCreator.Instance);
64 /// An event invoked after the theme has changed by <see cref="ApplyTheme(Theme)"/>.
66 /// <since_tizen> 9 </since_tizen>
67 public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
70 /// Internal one should be called before calling public ThemeChanged
72 internal static WeakEvent<EventHandler<ThemeChangedEventArgs>> ThemeChangedInternal = new WeakEvent<EventHandler<ThemeChangedEventArgs>>();
75 /// The current theme Id.
76 /// It returns null when no theme is applied.
78 [EditorBrowsable(EditorBrowsableState.Never)]
79 public static string ThemeId
85 /// The current platform theme Id.
86 /// Note that it returns null when the platform theme is disabled.
87 /// If the <seealso cref="NUIApplication.ThemeOptions.PlatformThemeEnabled"/> is given, it can be one of followings in tizen 6.5:
88 /// <list type="bullet">
90 /// <description>org.tizen.default-light-theme</description>
93 /// <description>org.tizen.default-dark-theme</description>
97 [EditorBrowsable(EditorBrowsableState.Never)]
98 public static string PlatformThemeId
100 get => platformTheme?.Id ?? (platformThemeEnabled ? baseTheme.Id : null);
104 /// To support deprecated StyleManager.
105 /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
107 internal static Theme BaseTheme
113 UpdateThemeForInitialize();
118 /// To support deprecated StyleManager.
119 /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
121 internal static Theme CurrentTheme
123 get => userTheme ?? baseTheme;
127 UpdateThemeForInitialize();
128 NotifyThemeChanged();
132 internal static bool PlatformThemeEnabled
134 get => platformThemeEnabled;
137 if (platformThemeEnabled == value) return;
139 platformThemeEnabled = value;
141 if (platformThemeEnabled)
143 ApplyExternalPlatformTheme(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion);
148 internal static bool ApplicationThemeChangeSensitive { get; set; } = false;
151 /// Apply custom theme to the NUI.
152 /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
153 /// This also affects all components created afterwards.
155 /// <param name="theme">The theme instance to be applied.</param>
156 /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
157 /// <since_tizen> 9 </since_tizen>
158 public static void ApplyTheme(Theme theme)
160 var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
162 if (string.IsNullOrEmpty(newTheme.Id))
164 newTheme.Id = "NONAME";
167 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl ?? "");
168 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl ?? "");
169 StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl ?? "");
171 userTheme = newTheme;
172 UpdateThemeForInitialize();
173 UpdateThemeForUpdate();
174 NotifyThemeChanged();
178 /// Change tizen theme.
179 /// User may change this to one of platform installed one.
181 /// <param name="themeId">The installed theme Id.</param>
182 /// <returns>true on success, false when it failed to find installed theme with given themeId.</returns>
183 /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
184 [EditorBrowsable(EditorBrowsableState.Never)]
185 public static bool ApplyPlatformTheme(string themeId)
187 if (themeId == null) throw new ArgumentNullException(nameof(themeId));
189 return ExternalThemeManager.SetTheme(themeId);
193 /// Load a style with style name in the current theme.
194 /// For components, the default style name of a component is a component name with namespace (e.g. Tizen.NUI.Components.Button).
196 /// <param name="styleName">The style name.</param>
197 /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
198 /// <since_tizen> 9 </since_tizen>
199 public static ViewStyle GetStyle(string styleName)
201 if (styleName == null) throw new ArgumentNullException(nameof(styleName));
202 return GetInitialStyleWithoutClone(styleName)?.Clone();
206 /// Load a style with view type in the current theme.
207 /// If it failed to find a style with the given type, it will try with it's parent type until it succeeds.
209 /// <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>
210 /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
211 /// <since_tizen> 9 </since_tizen>
212 public static ViewStyle GetStyle(Type viewType)
214 if (viewType == null) throw new ArgumentNullException(nameof(viewType));
215 return GetInitialStyleWithoutClone(viewType)?.Clone();
219 /// Load a platform style with style name in the current theme.
220 /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
222 /// <param name="styleName">The style name.</param>
223 /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
224 [EditorBrowsable(EditorBrowsableState.Never)]
225 public static ViewStyle GetPlatformStyle(string styleName)
227 if (styleName == null) throw new ArgumentNullException(nameof(styleName));
228 return platformTheme?.GetStyle(styleName)?.Clone();
232 /// Load a platform style with view type in the current theme.
233 /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
235 /// <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>
236 /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
237 [EditorBrowsable(EditorBrowsableState.Never)]
238 public static ViewStyle GetPlatformStyle(Type viewType)
240 if (viewType == null) throw new ArgumentNullException(nameof(viewType));
241 return platformTheme?.GetStyle(viewType)?.Clone();
245 /// Load a style with style name in the current theme.
247 /// <param name="styleName">The style name.</param>
248 internal static ViewStyle GetUpdateStyleWithoutClone(string styleName) => themeForUpdate?.GetStyle(styleName);
251 /// Load a style with View type in the current theme.
253 /// <param name="viewType">The type of View.</param>
254 internal static ViewStyle GetUpdateStyleWithoutClone(Type viewType) => themeForUpdate?.GetStyle(viewType);
257 /// Load a initial component style.
259 internal static ViewStyle GetInitialStyleWithoutClone(string styleName) => themeForInitialize.GetStyle(styleName);
262 /// Load a initial component style.
264 internal static ViewStyle GetInitialStyleWithoutClone(Type viewType) => themeForInitialize.GetStyle(viewType);
267 /// Get a platform installed theme.
269 /// <param name="themeId">The theme id.</param>
270 internal static Theme LoadPlatformTheme(string themeId)
272 Debug.Assert(themeId != null);
274 // Check if it is already loaded.
275 int index = cachedPlatformThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
278 Tizen.Log.Info("NUI", $"Hit cache.");
279 var found = cachedPlatformThemes[index];
280 // If the cached is not a full set, update it.
281 if (found.PackageCount < packages.Count)
283 UpdatePlatformTheme(found);
284 Tizen.Log.Info("NUI", $"Update cache.");
289 var newTheme = CreatePlatformTheme(themeId);
290 if (newTheme != null)
292 cachedPlatformThemes.Add(newTheme);
293 Tizen.Log.Info("NUI", $"Platform theme has been loaded successfully.");
299 /// !!! This is for internal use in fhub-nui. Do not open it.
300 /// Set a theme to be used as fallback.
301 /// The fallback theme is set to profile specified theme by default.
303 /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
304 [EditorBrowsable(EditorBrowsableState.Never)]
305 internal static void ApplyFallbackTheme(Theme fallbackTheme)
307 Debug.Assert(fallbackTheme != null);
308 BaseTheme = (Theme)fallbackTheme?.Clone();
312 /// Apply an external platform theme.
314 /// <param name="id">The external theme id.</param>
315 /// <param name="version">The external theme version.</param>
316 internal static void ApplyExternalPlatformTheme(string id, string version)
318 #if ExternalThemeEnabled
319 Debug.Assert(baseTheme != null);
321 // If the given theme is invalid, do nothing.
322 if (string.IsNullOrEmpty(id))
327 // If no platform theme has been applied and the base theme can cover the given one, do nothing.
328 if (platformTheme == null && baseTheme.HasSameIdAndVersion(id, version))
330 Tizen.Log.Info("NUI", "The base theme can cover platform theme: Skip loading.");
334 // If the given theme is already applied, do nothing.
335 if (platformTheme != null && platformTheme.HasSameIdAndVersion(id, version))
337 Tizen.Log.Info("NUI", "Platform theme is already applied: Skip loading.");
341 var loaded = LoadPlatformTheme(id);
345 Tizen.Log.Info("NUI", $"{loaded.Id} has been applied successfully.");
346 platformTheme = loaded;
347 UpdateThemeForInitialize();
348 UpdateThemeForUpdate();
349 NotifyThemeChanged(true);
354 internal static void AddPackageTheme(IThemeCreator themeCreator)
357 Tizen.Log.Info("NUI", $"PROFILE_TV AddPackageTheme()");
359 baseTheme = themeCreator.Create();
362 if (packages.Contains(themeCreator))
367 Tizen.Log.Info("NUI", $"AddPackageTheme({themeCreator.GetType().Assembly.GetName().Name})");
368 packages.Add(themeCreator);
371 var packageBaseTheme = themeCreator.Create();
372 Debug.Assert(packageBaseTheme != null);
374 if (baseTheme == null) baseTheme = packageBaseTheme;
375 else baseTheme.MergeWithoutClone(packageBaseTheme);
376 baseTheme.PackageCount++;
378 #if ExternalThemeEnabled
379 if (platformThemeEnabled)
381 Tizen.Log.Info("NUI", $"Platform theme is enabled");
382 if (platformTheme != null)
384 UpdatePlatformTheme(platformTheme);
388 if (!string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId) && !baseTheme.HasSameIdAndVersion(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion))
390 var loaded = LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
393 platformTheme = loaded;
397 UpdateThemeForUpdate();
400 UpdateThemeForInitialize();
403 internal static void Preload()
405 #if ExternalThemeEnabled
406 Debug.Assert(baseTheme != null);
408 if (string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId)) return;
410 LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
414 // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
415 internal static void UpdateThemeForUpdate()
417 if (userTheme == null)
419 themeForUpdate = platformTheme;
423 if (platformTheme == null)
425 themeForUpdate = userTheme;
429 themeForUpdate = new Theme();
430 themeForUpdate.Merge(platformTheme);
431 themeForUpdate.MergeWithoutClone(userTheme);
434 // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
435 internal static void UpdateThemeForInitialize()
437 if (platformTheme == null && userTheme == null)
439 themeForInitialize = baseTheme;
443 themeForInitialize = new Theme();
444 themeForInitialize.Merge(baseTheme);
446 if (userTheme == null)
448 if (platformTheme != null) themeForInitialize.MergeWithoutClone(platformTheme);
452 if (platformTheme != null) themeForInitialize.Merge(platformTheme);
453 themeForInitialize.MergeWithoutClone(userTheme);
457 private static void UpdatePlatformTheme(Theme theme)
459 var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(theme.Id);
461 if (sharedResourcePath == null)
466 for (var i = theme.PackageCount; i < packages.Count; i++)
468 theme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packages[i].GetType().Assembly.GetName().Name));
470 theme.PackageCount = packages.Count;
473 private static Theme CreatePlatformTheme(string id)
475 var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(id);
477 if (sharedResourcePath == null)
482 var newTheme = new Theme()
487 foreach (var packageCreator in packages)
489 newTheme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packageCreator.GetType().Assembly.GetName().Name));
491 newTheme.PackageCount = packages.Count;
496 [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.")]
497 private static Theme CreatePlatformTheme(string sharedResourcePath, string assemblyName)
499 ExternalThemeManager.SharedResourcePath = sharedResourcePath;
502 return new Theme(sharedResourcePath + assemblyName + ".Theme.xaml");
504 catch (System.IO.FileNotFoundException)
506 Tizen.Log.Info("NUI", $"[Ignorable] Current tizen theme does not have NUI theme.");
510 Tizen.Log.Info("NUI", $"[Ignorable] {e.GetType().Name} occurred while applying tizen theme to {assemblyName}: {e.Message}");
516 private static void AddToPlatformThemes(Theme theme)
518 int index = cachedPlatformThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
521 Tizen.Log.Info("NUI", $"Existing {theme.Id} item is overwritten");
522 cachedPlatformThemes[index] = theme;
526 cachedPlatformThemes.Add(theme);
527 Tizen.Log.Info("NUI", $"New {theme.Id} is saved.");
531 private static void NotifyThemeChanged(bool platformThemeUpdated = false)
533 Debug.Assert(baseTheme != null);
535 var platformThemeId = PlatformThemeId;
536 var userThemeId = userTheme?.Id;
537 ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
538 ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));