9d202156d9ebd1ffe715c6e99f11974195db345f
[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 deefault 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   */ null,
54             /* TV       */ null,
55             /* Wearable */ "Tizen.NUI.Theme.Wearable",
56         };
57
58         private static Theme currentTheme;
59         private static List<Theme> builtinThemes; // First item is default theme of the current profile.
60         private static bool isLoadingDefault = false;
61         private static Profile? currentProfile;
62
63         /// <summary>
64         /// </summary>
65         [EditorBrowsable(EditorBrowsableState.Never)]
66         public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
67
68         internal static Theme CurrentTheme
69         {
70             get
71             {
72                 if (currentTheme == null)
73                 {
74                     currentTheme = DefaultTheme;
75                 }
76                 return currentTheme;
77             }
78             set
79             {
80                 currentTheme = value;
81                 NotifyThemeChanged();
82             }
83         }
84
85         internal static Theme DefaultTheme
86         {
87             get => BuiltinThemes?[0];
88         }
89
90         private static List<Theme> BuiltinThemes
91         {
92             get
93             {
94                 if (builtinThemes == null && !isLoadingDefault)
95                 {
96                     isLoadingDefault = true;
97
98                     // Set the default theme as first item.
99                     builtinThemes = new List<Theme>
100                     {
101                         LoadBuiltinTheme(profileDefaultTheme[(int)CurrentProfile])
102                     };
103
104                     isLoadingDefault = false;
105                 }
106                 return builtinThemes;
107             }
108         }
109
110         private static Profile CurrentProfile
111         {
112             get
113             {
114                 if (currentProfile == null)
115                 {
116                     currentProfile = Profile.Common;
117                     string profileString = "";
118
119                     try
120                     {
121                         Information.TryGetValue<string>("tizen.org/feature/profile", out profileString);
122                         Tizen.Log.Info("NUI", "Profile for initial theme found : " + profileString);
123                     }
124                     catch
125                     {
126                         Tizen.Log.Info("NUI", "Unknown device profile\n");
127                     }
128                     finally
129                     {
130                         if (string.Equals(profileString, "mobile"))
131                         {
132                             currentProfile = Profile.Mobile;
133                         }
134                         else if (string.Equals(profileString, "tv"))
135                         {
136                             currentProfile = Profile.TV;
137                         }
138                         else if (string.Equals(profileString, "wearable"))
139                         {
140                             currentProfile = Profile.Wearable;
141                         }
142                     }
143                 }
144                 return (Profile)currentProfile;
145             }
146         }
147
148         /// <summary>
149         /// Apply them to the NUI.
150         /// This changes look of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
151         /// This also affects all components created afterwards.
152         /// </summary>
153         /// <param name="theme">The theme instance to be applied.</param>
154         /// <exception cref="ArgumentNullException">The given theme is null.</exception>
155         [EditorBrowsable(EditorBrowsableState.Never)]
156         public static void ApplyTheme(Theme theme)
157         {
158             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException($"Invalid theme.");
159
160             if (string.IsNullOrEmpty(newTheme.Id))
161             {
162                 newTheme.Id = "NONAME";
163             }
164
165             CurrentTheme = newTheme;
166         }
167
168         /// <summary>
169         /// Load a style with style name in the current theme.
170         /// For components, the style name is a component name (e.g. Button) in normal case.
171         /// </summary>
172         /// <param name="styleName">The style name</param>
173         [EditorBrowsable(EditorBrowsableState.Never)]
174         public static ViewStyle GetStyle(string styleName)
175         {
176             if (styleName == null)
177             {
178                 throw new ArgumentNullException(nameof(styleName));
179             }
180
181             return CurrentTheme.GetStyle(styleName)?.Clone();
182         }
183
184         /// <summary>
185         /// Load a style for a View in the current theme.
186         /// </summary>
187         /// <param name="viewType">The type of View</param>
188         [EditorBrowsable(EditorBrowsableState.Never)]
189         public static ViewStyle GetStyle(Type viewType)
190         {
191             if (viewType == null)
192             {
193                 throw new ArgumentNullException(nameof(viewType));
194             }
195
196             var currentType = viewType;
197             ViewStyle resultStyle = null;
198
199             do
200             {
201                 if (currentType.Equals(typeof(Tizen.NUI.BaseComponents.View))) break;
202                 resultStyle = GetStyle(currentType.Name)?.Clone();
203                 currentType = currentType.BaseType;
204             }
205             while (resultStyle == null);
206
207             return resultStyle;
208         }
209
210         /// <summary>
211         /// Get a cloned built-in theme.
212         /// </summary>
213         /// <param name="themeId">The built-in theme id.</param>
214         [EditorBrowsable(EditorBrowsableState.Never)]
215         public static Theme GetBuiltinTheme(string themeId)
216         {
217             Theme result = null;
218             int index = BuiltinThemes.FindIndex(x => string.IsNullOrEmpty(x?.Id) && x.Id.Equals(themeId, StringComparison.OrdinalIgnoreCase));
219             if (index > 0)
220             {
221                 result = (Theme)BuiltinThemes[index];
222             }
223             else
224             {
225                 var theme = LoadBuiltinTheme(themeId);
226                 BuiltinThemes.Add(theme);
227                 result = theme;
228             }
229             return (Theme)result?.Clone();
230         }
231
232         private static Theme LoadBuiltinTheme(string id)
233         {
234             if (string.IsNullOrEmpty(id)) return null;
235
236             var loaded = new Theme()
237             {
238                 Id = id,
239             };
240
241             foreach (var project in nuiThemeProjects)
242             {
243                 string path = StyleManager.FrameworkResourcePath + "/Theme/" + project + "_" + id + ".xaml";
244
245                 try
246                 {
247                     loaded.Merge(path);
248                     loaded.Id = id;
249                 }
250                 catch (XamlParseException)
251                 {
252                     Tizen.Log.Info("NUI", $"Could not find \"{path}\".\n");
253                     Tizen.Log.Info("NUI", $"The assemblies used in the file may not be included in the project.\n");
254                 }
255                 catch (Exception)
256                 {
257                     Tizen.Log.Info("NUI", $"Could not load \"{path}\"\n");
258                 }
259             }
260
261             return loaded;
262         }
263
264         private static void NotifyThemeChanged()
265         {
266             ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme.Id));
267         }
268     }
269 }