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