/* * Copyright(c) 2020 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ extern alias TizenSystemInformation; using TizenSystemInformation.Tizen.System; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using Tizen.NUI.Xaml; using Tizen.NUI.BaseComponents; namespace Tizen.NUI { /// [EditorBrowsable(EditorBrowsableState.Never)] public static class ThemeManager { private enum Profile { Common = 0, Mobile = 1, TV = 2, Wearable = 3 } private static readonly string[] nuiThemeProjects = { "Tizen.NUI", "Tizen.NUI.Components", "Tizen.NUI.Wearable" }; /// /// Table that indicates default theme id by device profile. /// Note that, the fallback of null value is Common value. /// private static readonly string[] profileDefaultTheme = { /* Common */ "Tizen.NUI.Theme.Common", /* Mobile */ "Tizen.NUI.Theme.Common", /* TV */ null, /* Wearable */ "Tizen.NUI.Theme.Wearable", }; private static Theme currentTheme; private static Theme defaultTheme; private static bool isLoadingDefault = false; private static Profile? currentProfile; private static List builtinThemes = new List(); // Themes provided by framework. internal static List customThemes = new List(); // Themes registered by user. static ThemeManager() {} /// /// [EditorBrowsable(EditorBrowsableState.Never)] public static event EventHandler ThemeChanged; /// /// Internal one should be called before calling public ThemeChanged /// internal static event EventHandler ThemeChangedInternal; internal static Theme CurrentTheme { get { if (currentTheme == null) { currentTheme = DefaultTheme; } return currentTheme; } set { currentTheme = value; NotifyThemeChanged(); } } internal static Theme DefaultTheme { get { if (defaultTheme == null && !isLoadingDefault) { isLoadingDefault = true; defaultTheme = LoadBuiltinTheme(profileDefaultTheme[(int)CurrentProfile]); isLoadingDefault = false; } return defaultTheme; } set => defaultTheme = (Theme)value?.Clone(); } internal static bool ThemeApplied => (CurrentTheme.Count > 0 || DefaultTheme.Count > 0); private static Profile CurrentProfile { get { if (currentProfile == null) { currentProfile = Profile.Common; string profileString = ""; try { Information.TryGetValue("tizen.org/feature/profile", out profileString); Tizen.Log.Info("NUI", "Profile for initial theme found : " + profileString); } catch { Tizen.Log.Info("NUI", "Unknown device profile\n"); } finally { if (string.Equals(profileString, "mobile")) { currentProfile = Profile.Mobile; } else if (string.Equals(profileString, "tv")) { currentProfile = Profile.TV; } else if (string.Equals(profileString, "wearable")) { currentProfile = Profile.Wearable; } } } return (Profile)currentProfile; } } /// /// Set a theme to be used as fallback. /// The fallback theme is set to profile specified theme by default. /// /// The theme instance to be applied as a fallback. /// The given theme is null. [EditorBrowsable(EditorBrowsableState.Never)] public static void ApplyFallbackTheme(Theme fallbackTheme) { DefaultTheme = fallbackTheme ?? throw new ArgumentNullException("Invalid theme."); } /// /// Apply theme to the NUI. /// This will change the appreance of the existing components with property on. /// This also affects all components created afterwards. /// /// The theme instance to be applied. /// Thrown when the given theme is null. [EditorBrowsable(EditorBrowsableState.Never)] public static void ApplyTheme(Theme theme) { var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException("Invalid theme."); if (string.IsNullOrEmpty(newTheme.Id)) { newTheme.Id = "NONAME"; } CurrentTheme = newTheme; } /// /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager. /// Please use instead. /// /// Apply theme to the NUI using theme id. /// The id of theme should be either a registered custom theme or a built-in theme. /// You can register custom theme using . /// This will change the appreance of the existing components with property on. /// This also affects all components created afterwards. /// /// The theme Id. /// Thrown when the given themeId is null. [EditorBrowsable(EditorBrowsableState.Never)] public static void ApplyTheme(string themeId) { if (themeId == null) throw new ArgumentNullException("Invalid themeId"); int index = customThemes.FindIndex(x => x.Id.Equals(themeId, StringComparison.OrdinalIgnoreCase)); if (index >= 0) { CurrentTheme = customThemes[index]; return; } index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase)); if (index >= 0) { CurrentTheme = builtinThemes[index]; } else { Tizen.Log.Info("NUI", $"No Theme found with given id : {themeId}"); } } /// /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager. /// /// Register a custom theme that can be used as an id when calling . /// /// The theme instance. /// Thrown when the given theme is null or invalid. [EditorBrowsable(EditorBrowsableState.Never)] public static void RegisterTheme(Theme theme) { if (theme == null || string.IsNullOrEmpty(theme.Id)) throw new ArgumentException("Invalid theme."); int index = customThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase)); if (index >= 0) { customThemes[index] = (Theme)theme.Clone(); } else { customThemes.Add((Theme)theme.Clone()); } } /// /// Load a style with style name in the current theme. /// For components, the style name is a component name (e.g. Button) in normal case. /// /// The style name. /// Thrown when the given styleName is null. [EditorBrowsable(EditorBrowsableState.Never)] public static ViewStyle GetStyle(string styleName) { if (styleName == null) throw new ArgumentNullException("Invalid style name"); if (!ThemeApplied) return null; return (CurrentTheme.GetStyle(styleName) ?? DefaultTheme.GetStyle(styleName))?.Clone(); } /// /// Load a style with View type in the current theme. /// /// The type of View. /// Thrown when the given viewType is null. [EditorBrowsable(EditorBrowsableState.Never)] public static ViewStyle GetStyle(Type viewType) { if (viewType == null) throw new ArgumentNullException("Invalid viewType"); if (!ThemeApplied) return null; return (CurrentTheme.GetStyle(viewType) ?? DefaultTheme.GetStyle(viewType))?.Clone(); } /// /// Get a cloned built-in theme. /// /// The built-in theme id. /// Thrown when the given themeId is null. [EditorBrowsable(EditorBrowsableState.Never)] public static Theme GetBuiltinTheme(string themeId) { if (themeId == null) throw new ArgumentNullException("Invalid themeId"); Theme result = null; int index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase)); if (index >= 0) { result = builtinThemes[index]; } else { var theme = LoadBuiltinTheme(themeId); builtinThemes.Add(theme); result = theme; } return (Theme)result?.Clone(); } private static Theme LoadBuiltinTheme(string id) { var loaded = new Theme() { Id = id, }; if (string.IsNullOrEmpty(id)) return loaded; foreach (var project in nuiThemeProjects) { string path = StyleManager.FrameworkResourcePath + "/Theme/" + project + "_" + id + ".xaml"; if (!File.Exists(path)) { Tizen.Log.Info("NUI", $"\"{path}\" is not found in this profile.\n"); continue; } try { loaded.Merge(path); loaded.Id = id; Tizen.Log.Info("NUI", $"Done to load \"{path}\".\n"); } catch (Exception e) { Tizen.Log.Debug("NUI", $"Could not load \"{path}\"\n"); Tizen.Log.Debug("NUI", "Message: " + e + "\n"); } } return loaded; } private static void NotifyThemeChanged() { ThemeChangedInternal?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id)); ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id)); } } }