[NUI] Add methods to modify base theme for fhub and fix bugs
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Theme / ThemeManager.cs
1 /*
2  * Copyright(c) 2021 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 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Diagnostics.CodeAnalysis;
22 using Tizen.NUI.BaseComponents;
23
24 #pragma warning disable CS0162 // Unreachable code detected: Some lines can be unreachable in TV profile
25 namespace Tizen.NUI
26 {
27     /// <summary>
28     /// This static module provides methods that can manage NUI <see cref="Theme"/>.
29     /// </summary>
30     /// <example>
31     /// To apply custom theme to the application, try <see cref="ApplyTheme(Theme)"/>.
32     /// <code>
33     /// var customTheme = new Theme(res + "customThemeFile.xaml");
34     /// ThemeManager.ApplyTheme(customTheme);
35     /// </code>
36     /// </example>
37     /// <summary></summary>
38     /// <since_tizen> 9 </since_tizen>
39     public static class ThemeManager
40     {
41         /// <summary>
42         /// The default light theme name preloaded in platform.
43         /// </summary>
44         [EditorBrowsable(EditorBrowsableState.Never)]
45         public const string DefaultLightThemeName = "org.tizen.default-light-theme";
46
47         /// <summary>
48         /// The default dark theme name preloaded in platform.
49         /// </summary>
50         [EditorBrowsable(EditorBrowsableState.Never)]
51         public const string DefaultDarkThemeName = "org.tizen.default-dark-theme";
52
53         private static Theme baseTheme; // The base theme. It includes all styles including structures (Size, Position, Policy) of components.
54         private static Theme platformTheme; // The platform theme. This may include color and image information without structure detail.
55         private static Theme userTheme; // The user custom theme.
56         private static Theme themeForUpdate; // platformTheme + userTheme. It is used when the component need to update according to theme change.
57         private static Theme themeForInitialize; // baseTheme + platformTheme + userTheme. It is used when the component is created.
58         private static readonly List<Theme> cachedPlatformThemes = new List<Theme>(); // Themes provided by framework.
59         private static readonly List<string> packages = new List<string>();// This is to store base theme creators by packages.
60         private static bool platformThemeEnabled = false;
61         private static bool isInEventProgress = false;
62
63         static ThemeManager()
64         {
65             if (InitialThemeDisabled) return;
66
67             ExternalThemeManager.Initialize();
68             AddPackageTheme(DefaultThemeCreator.Instance);
69         }
70
71         /// <summary>
72         /// An event invoked when the theme is about to change (not applied to the views yet).
73         /// </summary>
74         [EditorBrowsable(EditorBrowsableState.Never)]
75         public static event EventHandler<ThemeChangedEventArgs> ThemeChanging;
76
77         /// <summary>
78         /// An event invoked after the theme has changed by <see cref="ApplyTheme(Theme)"/>.
79         /// </summary>
80         /// <since_tizen> 9 </since_tizen>
81         public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
82
83         /// <summary>
84         /// Internal one should be called before calling public ThemeChanged
85         /// </summary>
86         internal static WeakEvent<EventHandler<ThemeChangedEventArgs>> ThemeChangedInternal = new WeakEvent<EventHandler<ThemeChangedEventArgs>>();
87
88         /// <summary>
89         /// The current theme Id.
90         /// It returns null when no theme is applied.
91         /// </summary>
92         [EditorBrowsable(EditorBrowsableState.Never)]
93         public static string ThemeId
94         {
95             get => userTheme?.Id;
96         }
97
98         /// <summary>
99         /// The current platform theme Id.
100         /// Note that it returns null when the platform theme is disabled.
101         /// If the <seealso cref="NUIApplication.ThemeOptions.PlatformThemeEnabled"/> is given, it can be one of followings in tizen 6.5:
102         /// <list type="bullet">
103         /// <item>
104         /// <description>org.tizen.default-light-theme</description>
105         /// </item>
106         /// <item>
107         /// <description>org.tizen.default-dark-theme</description>
108         /// </item>
109         /// </list>
110         /// </summary>
111         [EditorBrowsable(EditorBrowsableState.Never)]
112         public static string PlatformThemeId
113         {
114             get => platformTheme?.Id ?? (platformThemeEnabled ? baseTheme?.Id : null);
115         }
116
117         /// <summary>
118         /// To support deprecated StyleManager.
119         /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
120         /// </summary>
121         internal static Theme BaseTheme
122         {
123             get
124             {
125                 if (baseTheme == null)
126                 {
127                     baseTheme = new Theme();
128                     UpdateThemeForInitialize();
129                 }
130                 return baseTheme;
131             }
132             set
133             {
134                 baseTheme = value;
135                 UpdateThemeForInitialize();
136             }
137         }
138
139         /// <summary>
140         /// To support deprecated StyleManager.
141         /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
142         /// </summary>
143         internal static Theme CurrentTheme
144         {
145             get => userTheme ?? baseTheme;
146             set
147             {
148                 userTheme = value;
149                 UpdateThemeForInitialize();
150                 NotifyThemeChanged();
151             }
152         }
153
154         internal static bool PlatformThemeEnabled
155         {
156             get => platformThemeEnabled;
157             set
158             {
159                 if (platformThemeEnabled == value) return;
160
161                 platformThemeEnabled = value;
162
163                 if (platformThemeEnabled)
164                 {
165                     ApplyExternalPlatformTheme(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion);
166                 }
167             }
168         }
169
170         internal static bool ApplicationThemeChangeSensitive { get; set; } = false;
171
172 #if PROFILE_TV
173         internal const bool InitialThemeDisabled = true;
174 #else        
175         internal const bool InitialThemeDisabled = false;
176 #endif
177
178         /// <summary>
179         /// Apply custom theme to the NUI.
180         /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
181         /// This also affects all components created afterwards.
182         /// </summary>
183         /// <param name="theme">The theme instance to be applied.</param>
184         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
185         /// <since_tizen> 9 </since_tizen>
186         public static void ApplyTheme(Theme theme)
187         {
188             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
189
190             if (string.IsNullOrEmpty(newTheme.Id))
191             {
192                 newTheme.Id = "NONAME";
193             }
194
195             if (newTheme.SmallBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl);
196             if (newTheme.BrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl);
197             if (newTheme.LargeBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl);
198
199             userTheme = newTheme;
200             UpdateThemeForInitialize();
201             UpdateThemeForUpdate();
202             NotifyThemeChanged();
203         }
204
205         /// <summary>
206         /// Append a theme to the current theme and apply it.
207         /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
208         /// This also affects all components created afterwards.
209         /// </summary>
210         /// <param name="theme">The theme instance to be appended.</param>
211         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
212         [EditorBrowsable(EditorBrowsableState.Never)]
213         public static void AppendTheme(Theme theme)
214         {
215             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
216
217             if (string.IsNullOrEmpty(newTheme.Id))
218             {
219                 newTheme.Id = "NONAME";
220             }
221
222             if (newTheme.SmallBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl);
223             if (newTheme.BrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl);
224             if (newTheme.LargeBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl);
225
226             if (userTheme == null) userTheme = newTheme;
227             else
228             {
229                 userTheme = (Theme)userTheme.Clone();
230                 userTheme.MergeWithoutClone(newTheme);
231             }
232
233             UpdateThemeForInitialize();
234             UpdateThemeForUpdate();
235             NotifyThemeChanged();
236         }
237
238         /// <summary>
239         /// Append a theme to the current base theme and apply it.
240         /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
241         /// This also affects all components created afterwards.
242         /// </summary>
243         /// <param name="theme">The theme instance to be appended.</param>
244         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
245         [EditorBrowsable(EditorBrowsableState.Never)]
246         public static void AppendBaseTheme(Theme theme)
247         {
248             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
249
250             if (string.IsNullOrEmpty(newTheme.Id))
251             {
252                 newTheme.Id = "NONAME";
253             }
254
255             if (newTheme.SmallBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl);
256             if (newTheme.BrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl);
257             if (newTheme.LargeBrokenImageUrl != null) StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl);
258
259             if (baseTheme == null) baseTheme = newTheme;
260             else
261             {
262                 baseTheme = (Theme)baseTheme.Clone();
263                 baseTheme.MergeWithoutClone(newTheme);
264             }
265
266             UpdateThemeForInitialize();
267             NotifyThemeChanged();
268         }
269
270         /// <summary>
271         /// Change tizen theme.
272         /// User may change this to one of platform installed one.
273         /// Note that this is global theme changing which effects all applications.
274         /// </summary>
275         /// <param name="themeId">The installed theme Id.</param>
276         /// <returns>true on success, false when it failed to find installed theme with given themeId.</returns>
277         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
278         [EditorBrowsable(EditorBrowsableState.Never)]
279         public static bool ApplyPlatformTheme(string themeId)
280         {
281             if (themeId == null) throw new ArgumentNullException(nameof(themeId));
282
283             return ExternalThemeManager.SetTheme(themeId);
284         }
285
286         /// <summary>
287         /// Load a style with style name in the current theme.
288         /// For components, the default style name of a component is a component name with namespace (e.g. Tizen.NUI.Components.Button).
289         /// </summary>
290         /// <param name="styleName">The style name.</param>
291         /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
292         /// <since_tizen> 9 </since_tizen>
293         public static ViewStyle GetStyle(string styleName)
294         {
295             if (styleName == null) throw new ArgumentNullException(nameof(styleName));
296             return GetInitialStyleWithoutClone(styleName)?.Clone();
297         }
298
299         /// <summary>
300         /// Load a style with view type in the current theme.
301         /// If it failed to find a style with the given type, it will try with it's parent type until it succeeds.
302         /// </summary>
303         /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
304         /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
305         /// <since_tizen> 9 </since_tizen>
306         public static ViewStyle GetStyle(Type viewType)
307         {
308             if (viewType == null) throw new ArgumentNullException(nameof(viewType));
309             return GetInitialStyleWithoutClone(viewType)?.Clone();
310         }
311
312         /// <summary>
313         /// Load a platform style with style name in the current theme.
314         /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
315         /// </summary>
316         /// <param name="styleName">The style name.</param>
317         /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
318         [EditorBrowsable(EditorBrowsableState.Never)]
319         public static ViewStyle GetPlatformStyle(string styleName)
320         {
321             if (styleName == null) throw new ArgumentNullException(nameof(styleName));
322             return platformTheme?.GetStyle(styleName)?.Clone();
323         }
324
325         /// <summary>
326         /// Load a platform style with view type in the current theme.
327         /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
328         /// </summary>
329         /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
330         /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
331         [EditorBrowsable(EditorBrowsableState.Never)]
332         public static ViewStyle GetPlatformStyle(Type viewType)
333         {
334             if (viewType == null) throw new ArgumentNullException(nameof(viewType));
335             return platformTheme?.GetStyle(viewType)?.Clone();
336         }
337
338         /// <summary>
339         /// Load a style with style name in the current theme.
340         /// </summary>
341         /// <param name="styleName">The style name.</param>
342         internal static ViewStyle GetUpdateStyleWithoutClone(string styleName) => themeForUpdate?.GetStyle(styleName);
343
344         /// <summary>
345         /// Load a style with View type in the current theme.
346         /// </summary>
347         /// <param name="viewType">The type of View.</param>
348         internal static ViewStyle GetUpdateStyleWithoutClone(Type viewType) => themeForUpdate?.GetStyle(viewType);
349
350         /// <summary>
351         /// Load a initial component style.
352         /// </summary>
353         internal static ViewStyle GetInitialStyleWithoutClone(string styleName) => themeForInitialize?.GetStyle(styleName);
354
355         /// <summary>
356         /// Load a initial component style.
357         /// </summary>
358         internal static ViewStyle GetInitialStyleWithoutClone(Type viewType) => themeForInitialize?.GetStyle(viewType);
359
360         /// <summary>
361         /// Get a platform installed theme.
362         /// </summary>
363         /// <param name="themeId">The theme id.</param>
364         internal static Theme LoadPlatformTheme(string themeId)
365         {
366             Debug.Assert(themeId != null);
367
368             // Check if it is already loaded.
369             int index = cachedPlatformThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
370             if (index >= 0)
371             {
372                 Tizen.Log.Info("NUI", $"Hit cache.");
373                 var found = cachedPlatformThemes[index];
374                 // If the cached is not a full set, update it.
375                 if (found.PackageCount < packages.Count)
376                 {
377                     UpdatePlatformTheme(found);
378                     Tizen.Log.Info("NUI", $"Update cache.");
379                 }
380                 return found;
381             }
382
383             var newTheme = CreatePlatformTheme(themeId);
384             if (newTheme != null)
385             {
386                 cachedPlatformThemes.Add(newTheme);
387                 Tizen.Log.Info("NUI", $"Platform theme has been loaded successfully.");
388             }
389             return newTheme;
390         }
391
392         /// <summary>
393         /// !!! This is for internal use in fhub-nui. Do not open it.
394         /// Set a theme to be used as fallback.
395         /// The fallback theme is set to profile specified theme by default.
396         /// </summary>
397         /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
398         [EditorBrowsable(EditorBrowsableState.Never)]
399         internal static void ApplyFallbackTheme(Theme fallbackTheme)
400         {
401             Debug.Assert(fallbackTheme != null);
402             BaseTheme = (Theme)fallbackTheme?.Clone();
403         }
404
405         /// <summary>
406         /// Apply an external platform theme.
407         /// </summary>
408         /// <param name="id">The external theme id.</param>
409         /// <param name="version">The external theme version.</param>
410         internal static void ApplyExternalPlatformTheme(string id, string version)
411         {
412             if (InitialThemeDisabled) return;
413
414             // If the given theme is invalid, do nothing.
415             if (string.IsNullOrEmpty(id))
416             {
417                 return;
418             }
419
420             // If no platform theme has been applied and the base theme can cover the given one, do nothing.
421             if (platformTheme == null && baseTheme != null && baseTheme.HasSameIdAndVersion(id, version))
422             {
423                 Tizen.Log.Info("NUI", "The base theme can cover platform theme: Skip loading.");
424                 return;
425             }
426
427             // If the given theme is already applied, do nothing.
428             if (platformTheme != null && platformTheme.HasSameIdAndVersion(id, version))
429             {
430                 Tizen.Log.Info("NUI", "Platform theme is already applied: Skip loading.");
431                 return;
432             }
433
434             var loaded = LoadPlatformTheme(id);
435
436             if (loaded != null)
437             {
438                 Tizen.Log.Info("NUI", $"{loaded.Id} has been applied successfully.");
439                 platformTheme = loaded;
440                 UpdateThemeForInitialize();
441                 UpdateThemeForUpdate();
442                 NotifyThemeChanged(true);
443             }
444         }
445
446         internal static void AddPackageTheme(IThemeCreator themeCreator)
447         {
448             string packageName;
449             if (InitialThemeDisabled || packages.Contains(packageName = themeCreator.GetType().Assembly.GetName().Name))
450             {
451                 return;
452             }
453
454             Tizen.Log.Debug("NUI", $"AddPackageTheme({themeCreator.GetType().Assembly.GetName().Name})");
455             packages.Add(packageName);
456
457             // Base theme
458             var packageBaseTheme = themeCreator.Create();
459             Debug.Assert(packageBaseTheme != null);
460
461             if (baseTheme == null) baseTheme = packageBaseTheme;
462             else baseTheme.MergeWithoutClone(packageBaseTheme);
463             baseTheme.PackageCount++;
464
465             if (platformThemeEnabled)
466             {
467                 Tizen.Log.Info("NUI", $"Platform theme is enabled");
468                 if (platformTheme != null)
469                 {
470                     UpdatePlatformTheme(platformTheme);
471                 }
472                 else
473                 {
474                     if (!string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId) && !baseTheme.HasSameIdAndVersion(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion))
475                     {
476                         var loaded = LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
477                         if (loaded != null)
478                         {
479                             platformTheme = loaded;
480                         }
481                     }
482                 }
483                 UpdateThemeForUpdate();
484             }
485             UpdateThemeForInitialize();
486         }
487
488         // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
489         internal static void UpdateThemeForUpdate()
490         {
491             if (userTheme == null)
492             {
493                 themeForUpdate = platformTheme;
494                 return;
495             }
496
497             if (platformTheme == null)
498             {
499                 themeForUpdate = userTheme;
500                 return;
501             }
502
503             themeForUpdate = new Theme();
504             themeForUpdate.Merge(platformTheme);
505             themeForUpdate.MergeWithoutClone(userTheme);
506         }
507
508         // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
509         internal static void UpdateThemeForInitialize()
510         {
511             if (platformTheme == null && userTheme == null)
512             {
513                 themeForInitialize = baseTheme;
514                 return;
515             }
516
517             themeForInitialize = new Theme();
518
519             if (baseTheme != null) themeForInitialize.Merge(baseTheme);
520
521             if (userTheme == null)
522             {
523                 if (platformTheme != null) themeForInitialize.MergeWithoutClone(platformTheme);
524             }
525             else
526             {
527                 if (platformTheme != null) themeForInitialize.Merge(platformTheme);
528                 themeForInitialize.MergeWithoutClone(userTheme);
529             }
530         }
531
532         private static void UpdatePlatformTheme(Theme theme)
533         {
534             var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(theme.Id);
535
536             if (sharedResourcePath == null)
537             {
538                 return;
539             }
540
541             for (var i = theme.PackageCount; i < packages.Count; i++)
542             {
543                 theme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packages[i]));
544             }
545             theme.PackageCount = packages.Count;
546         }
547
548         private static Theme CreatePlatformTheme(string id)
549         {
550             var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(id);
551
552             if (sharedResourcePath == null)
553             {
554                 return null;
555             }
556
557             var newTheme = new Theme()
558             {
559                 Id = id
560             };
561
562             foreach (var packageName in packages)
563             {
564                 newTheme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packageName));
565             }
566             newTheme.PackageCount = packages.Count;
567
568             return newTheme;
569         }
570
571         [SuppressMessage("Microsoft.Design", "CA1031: Do not catch general exception types", Justification = "This method is to handle external resources that may throw an exception but ignorable. This method should not interrupt the main stream.")]
572         private static Theme CreatePlatformTheme(string sharedResourcePath, string assemblyName)
573         {
574             ExternalThemeManager.SharedResourcePath = sharedResourcePath;
575             try
576             {
577                 return new Theme(sharedResourcePath + assemblyName + ".Theme.xaml");
578             }
579             catch (System.IO.FileNotFoundException)
580             {
581                 Tizen.Log.Info("NUI", $"[Ignorable] Current tizen theme does not have NUI theme.");
582             }
583             catch (Exception e)
584             {
585                 Tizen.Log.Info("NUI", $"[Ignorable] {e.GetType().Name} occurred while applying tizen theme to {assemblyName}: {e.Message}");
586             }
587
588             return new Theme();
589         }
590
591         private static void AddToPlatformThemes(Theme theme)
592         {
593             int index = cachedPlatformThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
594             if (index >= 0)
595             {
596                 Tizen.Log.Info("NUI", $"Existing {theme.Id} item is overwritten");
597                 cachedPlatformThemes[index] = theme;
598             }
599             else
600             {
601                 cachedPlatformThemes.Add(theme);
602                 Tizen.Log.Info("NUI", $"New {theme.Id} is saved.");
603             }
604         }
605
606         private static void NotifyThemeChanged(bool platformThemeUpdated = false)
607         {
608             if (isInEventProgress) return;
609             isInEventProgress = true;
610
611             var platformThemeId = PlatformThemeId;
612             var userThemeId = userTheme?.Id;
613             ThemeChanging?.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
614             ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
615             ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
616
617             isInEventProgress = false;
618         }
619     }
620 }
621 #pragma warning restore CS0162 // Unreachable code detected