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