diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index e5980b62fa3..59e76e2d2e9 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; using Microsoft.Win32; namespace Flow.Launcher.Core.Resource @@ -81,11 +82,6 @@ public Theme(IPublicAPI publicAPI, Settings settings) #region Theme Resources - public string GetCurrentTheme() - { - return _settings.Theme; - } - private void MakeSureThemeDirectoriesExist() { foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir))) @@ -127,7 +123,7 @@ public void UpdateFonts() try { // Load a ResourceDictionary for the specified theme. - var themeName = GetCurrentTheme(); + var themeName = _settings.Theme; var dict = GetThemeResourceDictionary(themeName); // Apply font settings to the theme resource. @@ -330,7 +326,7 @@ private ResourceDictionary GetResourceDictionary(string theme) private ResourceDictionary GetCurrentResourceDictionary() { - return GetResourceDictionary(GetCurrentTheme()); + return GetResourceDictionary(_settings.Theme); } private ThemeData GetThemeDataFromPath(string path) @@ -383,9 +379,20 @@ private string GetThemePath(string themeName) #endregion - #region Load & Change + #region Get & Change Theme + + public ThemeData GetCurrentTheme() + { + var themes = GetAvailableThemes(); + var matchingTheme = themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme); + if (matchingTheme == null) + { + Log.Warn($"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme."); + } + return matchingTheme ?? themes.FirstOrDefault(); + } - public List LoadAvailableThemes() + public List GetAvailableThemes() { List themes = new List(); foreach (var themeDirectory in _themeDirectories) @@ -403,7 +410,7 @@ public List LoadAvailableThemes() public bool ChangeTheme(string theme = null) { if (string.IsNullOrEmpty(theme)) - theme = GetCurrentTheme(); + theme = _settings.Theme; string path = GetThemePath(theme); try @@ -426,7 +433,7 @@ public bool ChangeTheme(string theme = null) BlurEnabled = IsBlurTheme(); - // Can only apply blur but here also apply drop shadow effect to avoid possible drop shadow effect issues + // Apply blur and drop shadow effect so that we do not need to call it again _ = RefreshFrameAsync(); return true; @@ -591,7 +598,7 @@ await Application.Current.Dispatcher.InvokeAsync(() => { AutoDropShadow(useDropShadowEffect); } - SetBlurForWindow(GetCurrentTheme(), backdropType); + SetBlurForWindow(_settings.Theme, backdropType); if (!BlurEnabled) { @@ -610,7 +617,7 @@ await Application.Current.Dispatcher.InvokeAsync(() => // Get the actual backdrop type and drop shadow effect settings var (backdropType, _) = GetActualValue(); - SetBlurForWindow(GetCurrentTheme(), backdropType); + SetBlurForWindow(_settings.Theme, backdropType); }, DispatcherPriority.Render); } @@ -898,11 +905,5 @@ private static bool IsBlurTheme() } #endregion - - #region Classes - - public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); - - #endregion } } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index a3020b60725..273676bfbd6 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -1,6 +1,4 @@ -using Flow.Launcher.Plugin.SharedModels; -using JetBrains.Annotations; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -9,6 +7,8 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Media; +using Flow.Launcher.Plugin.SharedModels; +using JetBrains.Annotations; namespace Flow.Launcher.Plugin { @@ -352,6 +352,26 @@ public interface IPublicAPI public void StopLoadingBar(); /// + /// Get all available themes + /// + /// + public List GetAvailableThemes(); + + /// + /// Get the current theme + /// + /// + public ThemeData GetCurrentTheme(); + + /// + /// Set the current theme + /// + /// + /// + /// True if the theme is set successfully, false otherwise. + /// + public bool SetCurrentTheme(ThemeData theme); + /// Save all Flow's plugins caches /// void SavePluginCaches(); diff --git a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs new file mode 100644 index 00000000000..cb389c21fbc --- /dev/null +++ b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs @@ -0,0 +1,77 @@ +using System; + +namespace Flow.Launcher.Plugin.SharedModels; + +/// +/// Theme data model +/// +public class ThemeData +{ + /// + /// Theme file name without extension + /// + public string FileNameWithoutExtension { get; private init; } + + /// + /// Theme name + /// + public string Name { get; private init; } + + /// + /// Indicates whether the theme supports dark mode + /// + public bool? IsDark { get; private init; } + + /// + /// Indicates whether the theme supports blur effects + /// + public bool? HasBlur { get; private init; } + + /// + /// Theme data constructor + /// + public ThemeData(string fileNameWithoutExtension, string name, bool? isDark = null, bool? hasBlur = null) + { + FileNameWithoutExtension = fileNameWithoutExtension; + Name = name; + IsDark = isDark; + HasBlur = hasBlur; + } + + /// + public static bool operator ==(ThemeData left, ThemeData right) + { + if (left is null && right is null) + return true; + if (left is null || right is null) + return false; + return left.Equals(right); + } + + /// + public static bool operator !=(ThemeData left, ThemeData right) + { + return !(left == right); + } + + /// + public override bool Equals(object obj) + { + if (obj is not ThemeData other) + return false; + return FileNameWithoutExtension == other.FileNameWithoutExtension && + Name == other.Name; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(FileNameWithoutExtension, Name); + } + + /// + public override string ToString() + { + return Name; + } +} diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index a523f90bb8f..b67ef6ab6e5 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -40,6 +40,9 @@ public class PublicAPIInstance : IPublicAPI, IRemovable private readonly Internationalization _translater; private readonly MainViewModel _mainVM; + private Theme _theme; + private Theme Theme => _theme ??= Ioc.Default.GetRequiredService(); + private readonly object _saveSettingsLock = new(); #region Constructor @@ -360,6 +363,13 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + public List GetAvailableThemes() => Theme.GetAvailableThemes(); + + public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme(); + + public bool SetCurrentTheme(ThemeData theme) => + Theme.ChangeTheme(theme.FileNameWithoutExtension); + private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new(); public void RemovePluginCaches(string cacheDirectory) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 6e2488fe1d1..f78704ef2d5 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -12,6 +12,7 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.ViewModel; using ModernWpf; using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager; @@ -28,25 +29,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; - private List _themes; - public List Themes => _themes ??= _theme.LoadAvailableThemes(); + private List _themes; + public List Themes => _themes ??= App.API.GetAvailableThemes(); - private Theme.ThemeData _selectedTheme; - public Theme.ThemeData SelectedTheme + private ThemeData _selectedTheme; + public ThemeData SelectedTheme { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme()); + get => _selectedTheme ??= Themes.Find(v => v == App.API.GetCurrentTheme()); set { _selectedTheme = value; - _theme.ChangeTheme(value.FileNameWithoutExtension); + App.API.SetCurrentTheme(value); // Update UI state OnPropertyChanged(nameof(BackdropType)); OnPropertyChanged(nameof(IsBackdropEnabled)); OnPropertyChanged(nameof(IsDropShadowEnabled)); OnPropertyChanged(nameof(DropShadowEffect)); - - _ = _theme.RefreshFrameAsync(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index 31faeba5231..f8aeaeafdff 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Core.Resource; +using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Sys { @@ -11,32 +10,6 @@ public class ThemeSelector private readonly PluginInitContext _context; - // Do not initialize it in the constructor, because it will cause null reference in - // var dicts = Application.Current.Resources.MergedDictionaries; line of Theme - private Theme theme = null; - private Theme Theme => theme ??= Ioc.Default.GetRequiredService(); - - #region Theme Selection - - // Theme select codes simplified from SettingsPaneThemeViewModel.cs - - private Theme.ThemeData _selectedTheme; - public Theme.ThemeData SelectedTheme - { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme()); - set - { - _selectedTheme = value; - Theme.ChangeTheme(value.FileNameWithoutExtension); - - _ = Theme.RefreshFrameAsync(); - } - } - - private List Themes => Theme.LoadAvailableThemes(); - - #endregion - public ThemeSelector(PluginInitContext context) { _context = context; @@ -44,28 +17,30 @@ public ThemeSelector(PluginInitContext context) public List Query(Query query) { + var themes = _context.API.GetAvailableThemes(); + var selectedTheme = _context.API.GetCurrentTheme(); + var search = query.SecondToEndSearch; if (string.IsNullOrWhiteSpace(search)) { - return Themes.Select(CreateThemeResult) + return themes.Select(x => CreateThemeResult(x, selectedTheme)) .OrderBy(x => x.Title) .ToList(); } - return Themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) + return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) - .Select(x => CreateThemeResult(x.theme, x.matchResult.Score, x.matchResult.MatchData)) + .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) .OrderBy(x => x.Title) .ToList(); } - private Result CreateThemeResult(Theme.ThemeData theme) => CreateThemeResult(theme, 0, null); + private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); - private Result CreateThemeResult(Theme.ThemeData theme, int score, IList highlightData) + private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) { - string themeName = theme.Name; string title; - if (theme == SelectedTheme) + if (theme == selectedTheme) { title = $"{theme.Name} ★"; // Set current theme to the top @@ -101,8 +76,10 @@ private Result CreateThemeResult(Theme.ThemeData theme, int score, IList hi Score = score, Action = c => { - SelectedTheme = theme; - _context.API.ReQuery(); + if (_context.API.SetCurrentTheme(theme)) + { + _context.API.ReQuery(); + } return false; } };