22using System . Collections . Generic ;
33using System . IO ;
44using System . Linq ;
5+ using System . Xml ;
56using System . Runtime . InteropServices ;
67using System . Windows ;
78using System . Windows . Controls ;
@@ -17,6 +18,10 @@ namespace Flow.Launcher.Core.Resource
1718{
1819 public class Theme
1920 {
21+ private const string ThemeMetadataNamePrefix = "Name:" ;
22+ private const string ThemeMetadataIsDarkPrefix = "IsDark:" ;
23+ private const string ThemeMetadataHasBlurPrefix = "HasBlur:" ;
24+
2025 private const int ShadowExtraMargin = 32 ;
2126
2227 private readonly List < string > _themeDirectories = new List < string > ( ) ;
@@ -79,14 +84,14 @@ public bool ChangeTheme(string theme)
7984 {
8085 if ( string . IsNullOrEmpty ( path ) )
8186 throw new DirectoryNotFoundException ( "Theme path can't be found <{path}>" ) ;
82-
87+
8388 // reload all resources even if the theme itself hasn't changed in order to pickup changes
8489 // to things like fonts
8590 UpdateResourceDictionary ( GetResourceDictionary ( theme ) ) ;
86-
91+
8792 Settings . Theme = theme ;
8893
89-
94+
9095 //always allow re-loading default theme, in case of failure of switching to a new theme from default theme
9196 if ( _oldTheme != theme || theme == defaultTheme )
9297 {
@@ -148,7 +153,7 @@ private ResourceDictionary GetThemeResourceDictionary(string theme)
148153 public ResourceDictionary GetResourceDictionary ( string theme )
149154 {
150155 var dict = GetThemeResourceDictionary ( theme ) ;
151-
156+
152157 if ( dict [ "QueryBoxStyle" ] is Style queryBoxStyle &&
153158 dict [ "QuerySuggestionBoxStyle" ] is Style querySuggestionBoxStyle )
154159 {
@@ -187,7 +192,7 @@ public ResourceDictionary GetResourceDictionary(string theme)
187192
188193 Setter [ ] setters = { fontFamily , fontStyle , fontWeight , fontStretch } ;
189194 Array . ForEach (
190- new [ ] { resultItemStyle , resultItemSelectedStyle , resultHotkeyItemStyle , resultHotkeyItemSelectedStyle } , o
195+ new [ ] { resultItemStyle , resultItemSelectedStyle , resultHotkeyItemStyle , resultHotkeyItemSelectedStyle } , o
191196 => Array . ForEach ( setters , p => o . Setters . Add ( p ) ) ) ;
192197 }
193198
@@ -219,17 +224,53 @@ private ResourceDictionary GetCurrentResourceDictionary( )
219224 return GetResourceDictionary ( Settings . Theme ) ;
220225 }
221226
222- public List < string > LoadAvailableThemes ( )
227+ public List < ThemeData > LoadAvailableThemes ( )
223228 {
224- List < string > themes = new List < string > ( ) ;
229+ List < ThemeData > themes = new List < ThemeData > ( ) ;
225230 foreach ( var themeDirectory in _themeDirectories )
226231 {
227- themes . AddRange (
228- Directory . GetFiles ( themeDirectory )
229- . Where ( filePath => filePath . EndsWith ( Extension ) && ! filePath . EndsWith ( "Base.xaml" ) )
230- . ToList ( ) ) ;
232+ var filePaths = Directory
233+ . GetFiles ( themeDirectory )
234+ . Where ( filePath => filePath . EndsWith ( Extension ) && ! filePath . EndsWith ( "Base.xaml" ) )
235+ . Select ( GetThemeDataFromPath ) ;
236+ themes . AddRange ( filePaths ) ;
231237 }
232- return themes . OrderBy ( o => o ) . ToList ( ) ;
238+
239+ return themes . OrderBy ( o => o . Name ) . ToList ( ) ;
240+ }
241+
242+ private ThemeData GetThemeDataFromPath ( string path )
243+ {
244+ using var reader = XmlReader . Create ( path ) ;
245+ reader . Read ( ) ;
246+
247+ var extensionlessName = Path . GetFileNameWithoutExtension ( path ) ;
248+
249+ if ( reader . NodeType is not XmlNodeType . Comment )
250+ return new ThemeData ( extensionlessName , extensionlessName ) ;
251+
252+ var commentLines = reader . Value . Trim ( ) . Split ( '\n ' ) . Select ( v => v . Trim ( ) ) ;
253+
254+ var name = extensionlessName ;
255+ bool ? isDark = null ;
256+ bool ? hasBlur = null ;
257+ foreach ( var line in commentLines )
258+ {
259+ if ( line . StartsWith ( ThemeMetadataNamePrefix , StringComparison . OrdinalIgnoreCase ) )
260+ {
261+ name = line . Remove ( 0 , ThemeMetadataNamePrefix . Length ) . Trim ( ) ;
262+ }
263+ else if ( line . StartsWith ( ThemeMetadataIsDarkPrefix , StringComparison . OrdinalIgnoreCase ) )
264+ {
265+ isDark = bool . Parse ( line . Remove ( 0 , ThemeMetadataIsDarkPrefix . Length ) . Trim ( ) ) ;
266+ }
267+ else if ( line . StartsWith ( ThemeMetadataHasBlurPrefix , StringComparison . OrdinalIgnoreCase ) )
268+ {
269+ hasBlur = bool . Parse ( line . Remove ( 0 , ThemeMetadataHasBlurPrefix . Length ) . Trim ( ) ) ;
270+ }
271+ }
272+
273+ return new ThemeData ( extensionlessName , name , isDark , hasBlur ) ;
233274 }
234275
235276 private string GetThemePath ( string themeName )
@@ -407,5 +448,7 @@ private void SetWindowAccent(Window w, AccentState state)
407448 Marshal . FreeHGlobal ( accentPtr ) ;
408449 }
409450 #endregion
451+
452+ public record ThemeData ( string FileNameWithoutExtension , string Name , bool ? IsDark = null , bool ? HasBlur = null ) ;
410453 }
411454}
0 commit comments