1192cb110856a3d7d597b737df55398a28b7a7ef
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Theme / ThemeManager.cs
1 /*
2  * Copyright(c) 2020 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17 extern alias TizenSystemInformation;
18 using TizenSystemInformation.Tizen.System;
19 using System;
20 using System.Collections.Generic;
21 using System.ComponentModel;
22 using Tizen.NUI.Xaml;
23 using Tizen.NUI.BaseComponents;
24
25 namespace Tizen.NUI
26 {
27     /// <summary></summary>
28     [EditorBrowsable(EditorBrowsableState.Never)]
29     public static class ThemeManager
30     {
31         private enum Profile
32         {
33             Common = 0,
34             Mobile = 1,
35             TV = 2,
36             Wearable = 3
37         }
38
39         private static readonly string[] nuiThemeProjects =
40         {
41             "Tizen.NUI",
42             "Tizen.NUI.Components",
43             "Tizen.NUI.Wearable"
44         };
45
46         /// <summary>
47         /// Table that indicates default theme id by device profile.
48         /// Note that, the fallback of null value is Common value.
49         /// </summary>
50         private static readonly string[] profileDefaultTheme =
51         {
52             /* Common   */ "Tizen.NUI.Theme.Common",
53             /* Mobile   */ "Tizen.NUI.Theme.Common",
54             /* TV       */ null,
55             /* Wearable */ "Tizen.NUI.Theme.Wearable",
56         };
57
58         private static Theme currentTheme;
59         private static Theme defaultTheme;
60         private static bool isLoadingDefault = false;
61         private static Profile? currentProfile;
62         private static List<Theme> builtinThemes = new List<Theme>(); // Themes provided by framework.
63         internal static List<Theme> customThemes = new List<Theme>(); // Themes registered by user.
64
65         static ThemeManager() {}
66
67         /// <summary>
68         /// </summary>
69         [EditorBrowsable(EditorBrowsableState.Never)]
70         public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
71
72         /// <summary>
73         /// Internal one should be called before calling public ThemeChanged
74         /// </summary>
75         internal static event EventHandler<ThemeChangedEventArgs> ThemeChangedInternal;
76
77         internal static Theme CurrentTheme
78         {
79             get
80             {
81                 if (currentTheme == null)
82                 {
83                     currentTheme = DefaultTheme;
84                 }
85                 return currentTheme;
86             }
87             set
88             {
89                 currentTheme = value;
90                 NotifyThemeChanged();
91             }
92         }
93
94         internal static Theme DefaultTheme
95         {
96             get
97             {
98                 if (defaultTheme == null && !isLoadingDefault)
99                 {
100                     isLoadingDefault = true;
101                     defaultTheme = LoadBuiltinTheme(profileDefaultTheme[(int)CurrentProfile]);
102                     isLoadingDefault = false;
103                 }
104                 return defaultTheme;
105             }
106             set => defaultTheme = (Theme)value?.Clone();
107         }
108
109         internal static bool ThemeApplied => (CurrentTheme.Count > 0 || DefaultTheme.Count > 0);
110
111         private static Profile CurrentProfile
112         {
113             get
114             {
115                 if (currentProfile == null)
116                 {
117                     currentProfile = Profile.Common;
118                     string profileString = "";
119
120                     try
121                     {
122                         Information.TryGetValue<string>("tizen.org/feature/profile", out profileString);
123                         Tizen.Log.Info("NUI", "Profile for initial theme found : " + profileString);
124                     }
125                     catch
126                     {
127                         Tizen.Log.Info("NUI", "Unknown device profile\n");
128                     }
129                     finally
130                     {
131                         if (string.Equals(profileString, "mobile"))
132                         {
133                             currentProfile = Profile.Mobile;
134                         }
135                         else if (string.Equals(profileString, "tv"))
136                         {
137                             currentProfile = Profile.TV;
138                         }
139                         else if (string.Equals(profileString, "wearable"))
140                         {
141                             currentProfile = Profile.Wearable;
142                         }
143                     }
144                 }
145                 return (Profile)currentProfile;
146             }
147         }
148
149         /// <summary>
150         /// Set a theme to be used as fallback.
151         /// The fallback theme is set to profile specified theme by default.
152         /// </summary>
153         /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
154         /// <exception cref="ArgumentNullException">The given theme is null.</exception>
155         [EditorBrowsable(EditorBrowsableState.Never)]
156         public static void ApplyFallbackTheme(Theme fallbackTheme)
157         {
158             DefaultTheme = fallbackTheme ?? throw new ArgumentNullException("Invalid theme.");
159         }
160
161         /// <summary>
162         /// Apply theme to the NUI.
163         /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
164         /// This also affects all components created afterwards.
165         /// </summary>
166         /// <param name="theme">The theme instance to be applied.</param>
167         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
168         [EditorBrowsable(EditorBrowsableState.Never)]
169         public static void ApplyTheme(Theme theme)
170         {
171             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException("Invalid theme.");
172
173             if (string.IsNullOrEmpty(newTheme.Id))
174             {
175                 newTheme.Id = "NONAME";
176             }
177
178             CurrentTheme = newTheme;
179         }
180
181         /// <summary>
182         /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager.
183         /// Please use <seealso cref="ApplyTheme(Theme)"/> instead.
184         ///
185         /// Apply theme to the NUI using theme id.
186         /// The id of theme should be either a registered custom theme or a built-in theme.
187         /// You can register custom theme using <seealso cref="RegisterTheme(Theme)"/>.
188         /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
189         /// This also affects all components created afterwards.
190         /// </summary>
191         /// <param name="themeId">The theme Id.</param>
192         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
193         [EditorBrowsable(EditorBrowsableState.Never)]
194         public static void ApplyTheme(string themeId)
195         {
196             if (themeId == null) throw new ArgumentNullException("Invalid themeId");
197
198             int index = customThemes.FindIndex(x => x.Id.Equals(themeId, StringComparison.OrdinalIgnoreCase));
199             if (index >= 0)
200             {
201                 CurrentTheme = customThemes[index];
202                 return;
203             }
204             
205             index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
206             if (index >= 0)
207             {
208                 CurrentTheme = builtinThemes[index];
209             }
210             else
211             {
212                 Tizen.Log.Info("NUI", $"No Theme found with given id : {themeId}");
213             }
214         }
215
216         /// <summary>
217         /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager.
218         ///
219         /// Register a custom theme that can be used as an id when calling <seealso cref="ApplyTheme(string)"/>.
220         /// </summary>
221         /// <param name="theme">The theme instance.</param>
222         /// <exception cref="ArgumentException">Thrown when the given theme is null or invalid.</exception>
223         [EditorBrowsable(EditorBrowsableState.Never)]
224         public static void RegisterTheme(Theme theme)
225         {
226             if (theme == null || string.IsNullOrEmpty(theme.Id)) throw new ArgumentException("Invalid theme.");
227
228             int index = customThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
229             if (index >= 0)
230             {
231                 customThemes[index] = (Theme)theme.Clone();
232             }
233             else
234             {
235                 customThemes.Add((Theme)theme.Clone());
236             }
237         }
238
239         /// <summary>
240         /// Load a style with style name in the current theme.
241         /// For components, the style name is a component name (e.g. Button) in normal case.
242         /// </summary>
243         /// <param name="styleName">The style name.</param>
244         /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
245         [EditorBrowsable(EditorBrowsableState.Never)]
246         public static ViewStyle GetStyle(string styleName)
247         {
248             if (styleName == null) throw new ArgumentNullException("Invalid style name");
249
250             if (!ThemeApplied) return null;
251
252             return (CurrentTheme.GetStyle(styleName) ?? DefaultTheme.GetStyle(styleName))?.Clone();
253         }
254
255         /// <summary>
256         /// Load a style with View type in the current theme.
257         /// </summary>
258         /// <param name="viewType">The type of View.</param>
259         /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
260         [EditorBrowsable(EditorBrowsableState.Never)]
261         public static ViewStyle GetStyle(Type viewType)
262         {
263             if (viewType == null) throw new ArgumentNullException("Invalid viewType");
264
265             if (!ThemeApplied) return null;
266
267             return (CurrentTheme.GetStyle(viewType) ?? DefaultTheme.GetStyle(viewType))?.Clone();
268         }
269
270         /// <summary>
271         /// Get a cloned built-in theme.
272         /// </summary>
273         /// <param name="themeId">The built-in theme id.</param>
274         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
275         [EditorBrowsable(EditorBrowsableState.Never)]
276         public static Theme GetBuiltinTheme(string themeId)
277         {
278             if (themeId == null) throw new ArgumentNullException("Invalid themeId");
279
280             Theme result = null;
281             int index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
282             if (index >= 0)
283             {
284                 result = builtinThemes[index];
285             }
286             else
287             {
288                 var theme = LoadBuiltinTheme(themeId);
289                 builtinThemes.Add(theme);
290                 result = theme;
291             }
292             return (Theme)result?.Clone();
293         }
294
295         private static Theme LoadBuiltinTheme(string id)
296         {
297             var loaded = new Theme()
298             {
299                 Id = id,
300             };
301
302             if (string.IsNullOrEmpty(id)) return loaded;
303
304             foreach (var project in nuiThemeProjects)
305             {
306                 string path = StyleManager.FrameworkResourcePath + "/Theme/" + project + "_" + id + ".xaml";
307
308                 try
309                 {
310                     loaded.Merge(path);
311                     loaded.Id = id;
312                     Tizen.Log.Info("NUI", $"Done to load \"{path}\".\n");
313                 }
314                 catch (Exception e)
315                 {
316                     Tizen.Log.Debug("NUI", $"Could not load \"{path}\"\n");
317                     Tizen.Log.Debug("NUI", "Message: " + e + "\n");
318                 }
319             }
320
321             return loaded;
322         }
323
324         private static void NotifyThemeChanged()
325         {
326             ThemeChangedInternal?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
327             ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
328         }
329     }
330 }