Revert "[NUI] Unsubscribe theme changed event when the view is not on window" (#2265)
[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                 if (defaultTheme == null && !isLoadingDefault)
100                 {
101                     isLoadingDefault = true;
102                     defaultTheme = LoadBuiltinTheme(profileDefaultTheme[(int)CurrentProfile]);
103                     isLoadingDefault = false;
104                 }
105                 return defaultTheme;
106             }
107             set => defaultTheme = (Theme)value?.Clone();
108         }
109
110         internal static bool ThemeApplied => (CurrentTheme.Count > 0 || DefaultTheme.Count > 0);
111
112         private static Profile CurrentProfile
113         {
114             get
115             {
116                 if (currentProfile == null)
117                 {
118                     currentProfile = Profile.Common;
119                     string profileString = "";
120
121                     try
122                     {
123                         Information.TryGetValue<string>("tizen.org/feature/profile", out profileString);
124                         Tizen.Log.Info("NUI", "Profile for initial theme found : " + profileString);
125                     }
126                     catch
127                     {
128                         Tizen.Log.Info("NUI", "Unknown device profile\n");
129                     }
130                     finally
131                     {
132                         if (string.Equals(profileString, "mobile"))
133                         {
134                             currentProfile = Profile.Mobile;
135                         }
136                         else if (string.Equals(profileString, "tv"))
137                         {
138                             currentProfile = Profile.TV;
139                         }
140                         else if (string.Equals(profileString, "wearable"))
141                         {
142                             currentProfile = Profile.Wearable;
143                         }
144                     }
145                 }
146                 return (Profile)currentProfile;
147             }
148         }
149
150         /// <summary>
151         /// Set a theme to be used as fallback.
152         /// The fallback theme is set to profile specified theme by default.
153         /// </summary>
154         /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
155         /// <exception cref="ArgumentNullException">The given theme is null.</exception>
156         [EditorBrowsable(EditorBrowsableState.Never)]
157         public static void ApplyFallbackTheme(Theme fallbackTheme)
158         {
159             DefaultTheme = fallbackTheme ?? throw new ArgumentNullException("Invalid theme.");
160         }
161
162         /// <summary>
163         /// Apply theme to the NUI.
164         /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
165         /// This also affects all components created afterwards.
166         /// </summary>
167         /// <param name="theme">The theme instance to be applied.</param>
168         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
169         [EditorBrowsable(EditorBrowsableState.Never)]
170         public static void ApplyTheme(Theme theme)
171         {
172             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException("Invalid theme.");
173
174             if (string.IsNullOrEmpty(newTheme.Id))
175             {
176                 newTheme.Id = "NONAME";
177             }
178
179             CurrentTheme = newTheme;
180         }
181
182         /// <summary>
183         /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager.
184         /// Please use <seealso cref="ApplyTheme(Theme)"/> instead.
185         ///
186         /// Apply theme to the NUI using theme id.
187         /// The id of theme should be either a registered custom theme or a built-in theme.
188         /// You can register custom theme using <seealso cref="RegisterTheme(Theme)"/>.
189         /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
190         /// This also affects all components created afterwards.
191         /// </summary>
192         /// <param name="themeId">The theme Id.</param>
193         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
194         [EditorBrowsable(EditorBrowsableState.Never)]
195         public static void ApplyTheme(string themeId)
196         {
197             if (themeId == null) throw new ArgumentNullException("Invalid themeId");
198
199             int index = customThemes.FindIndex(x => x.Id.Equals(themeId, StringComparison.OrdinalIgnoreCase));
200             if (index >= 0)
201             {
202                 CurrentTheme = customThemes[index];
203                 return;
204             }
205             
206             index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
207             if (index >= 0)
208             {
209                 CurrentTheme = builtinThemes[index];
210             }
211             else
212             {
213                 Tizen.Log.Info("NUI", $"No Theme found with given id : {themeId}");
214             }
215         }
216
217         /// <summary>
218         /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager.
219         ///
220         /// Register a custom theme that can be used as an id when calling <seealso cref="ApplyTheme(string)"/>.
221         /// </summary>
222         /// <param name="theme">The theme instance.</param>
223         /// <exception cref="ArgumentException">Thrown when the given theme is null or invalid.</exception>
224         [EditorBrowsable(EditorBrowsableState.Never)]
225         public static void RegisterTheme(Theme theme)
226         {
227             if (theme == null || string.IsNullOrEmpty(theme.Id)) throw new ArgumentException("Invalid theme.");
228
229             int index = customThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
230             if (index >= 0)
231             {
232                 customThemes[index] = (Theme)theme.Clone();
233             }
234             else
235             {
236                 customThemes.Add((Theme)theme.Clone());
237             }
238         }
239
240         /// <summary>
241         /// Load a style with style name in the current theme.
242         /// For components, the style name is a component name (e.g. Button) in normal case.
243         /// </summary>
244         /// <param name="styleName">The style name.</param>
245         /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
246         [EditorBrowsable(EditorBrowsableState.Never)]
247         public static ViewStyle GetStyle(string styleName)
248         {
249             if (styleName == null) throw new ArgumentNullException("Invalid style name");
250
251             if (!ThemeApplied) return null;
252
253             return (CurrentTheme.GetStyle(styleName) ?? DefaultTheme.GetStyle(styleName))?.Clone();
254         }
255
256         /// <summary>
257         /// Load a style with View type in the current theme.
258         /// </summary>
259         /// <param name="viewType">The type of View.</param>
260         /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
261         [EditorBrowsable(EditorBrowsableState.Never)]
262         public static ViewStyle GetStyle(Type viewType)
263         {
264             if (viewType == null) throw new ArgumentNullException("Invalid viewType");
265
266             if (!ThemeApplied) return null;
267
268             return (CurrentTheme.GetStyle(viewType) ?? DefaultTheme.GetStyle(viewType))?.Clone();
269         }
270
271         /// <summary>
272         /// Get a cloned built-in theme.
273         /// </summary>
274         /// <param name="themeId">The built-in theme id.</param>
275         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public static Theme GetBuiltinTheme(string themeId)
278         {
279             if (themeId == null) throw new ArgumentNullException("Invalid themeId");
280
281             Theme result = null;
282             int index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
283             if (index >= 0)
284             {
285                 result = builtinThemes[index];
286             }
287             else
288             {
289                 var theme = LoadBuiltinTheme(themeId);
290                 builtinThemes.Add(theme);
291                 result = theme;
292             }
293             return (Theme)result?.Clone();
294         }
295
296         private static Theme LoadBuiltinTheme(string id)
297         {
298             var loaded = new Theme()
299             {
300                 Id = id,
301             };
302
303             if (string.IsNullOrEmpty(id)) return loaded;
304
305             foreach (var project in nuiThemeProjects)
306             {
307                 string path = StyleManager.FrameworkResourcePath + "/Theme/" + project + "_" + id + ".xaml";
308
309                 if (!File.Exists(path))
310                 {
311                     Tizen.Log.Info("NUI", $"\"{path}\" is not found in this profile.\n");
312                     continue;
313                 }
314
315                 try
316                 {
317                     loaded.Merge(path);
318                     loaded.Id = id;
319                     Tizen.Log.Info("NUI", $"Done to load \"{path}\".\n");
320                 }
321                 catch (Exception e)
322                 {
323                     Tizen.Log.Debug("NUI", $"Could not load \"{path}\"\n");
324                     Tizen.Log.Debug("NUI", "Message: " + e + "\n");
325                 }
326             }
327
328             return loaded;
329         }
330
331         private static void NotifyThemeChanged()
332         {
333             ThemeChangedInternal?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
334             ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
335         }
336     }
337 }