Merge pull request #3420 from Jack251970/theme_change_api

New API Function from Theme & Improve Theme Model
This commit is contained in:
Jack Ye 2025-04-08 20:31:27 +08:00 committed by GitHub
commit 8cc9f2de69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 151 additions and 67 deletions

View file

@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure;
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 @@ namespace Flow.Launcher.Core.Resource
#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 @@ namespace Flow.Launcher.Core.Resource
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 @@ namespace Flow.Launcher.Core.Resource
private ResourceDictionary GetCurrentResourceDictionary()
{
return GetResourceDictionary(GetCurrentTheme());
return GetResourceDictionary(_settings.Theme);
}
private ThemeData GetThemeDataFromPath(string path)
@ -383,9 +379,20 @@ namespace Flow.Launcher.Core.Resource
#endregion
#region Load & Change
#region Get & Change Theme
public List<ThemeData> LoadAvailableThemes()
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<ThemeData> GetAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
@ -403,7 +410,7 @@ namespace Flow.Launcher.Core.Resource
public bool ChangeTheme(string theme = null)
{
if (string.IsNullOrEmpty(theme))
theme = GetCurrentTheme();
theme = _settings.Theme;
string path = GetThemePath(theme);
try
@ -426,7 +433,7 @@ namespace Flow.Launcher.Core.Resource
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 @@ namespace Flow.Launcher.Core.Resource
{
AutoDropShadow(useDropShadowEffect);
}
SetBlurForWindow(GetCurrentTheme(), backdropType);
SetBlurForWindow(_settings.Theme, backdropType);
if (!BlurEnabled)
{
@ -610,7 +617,7 @@ namespace Flow.Launcher.Core.Resource
// 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 @@ namespace Flow.Launcher.Core.Resource
}
#endregion
#region Classes
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
#endregion
}
}

View file

@ -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;
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 @@ namespace Flow.Launcher.Plugin
public void StopLoadingBar();
/// <summary>
/// Get all available themes
/// </summary>
/// <returns></returns>
public List<ThemeData> GetAvailableThemes();
/// <summary>
/// Get the current theme
/// </summary>
/// <returns></returns>
public ThemeData GetCurrentTheme();
/// <summary>
/// Set the current theme
/// </summary>
/// <param name="theme"></param>
/// <returns>
/// True if the theme is set successfully, false otherwise.
/// </returns>
public bool SetCurrentTheme(ThemeData theme);
/// Save all Flow's plugins caches
/// </summary>
void SavePluginCaches();

View file

@ -0,0 +1,77 @@
using System;
namespace Flow.Launcher.Plugin.SharedModels;
/// <summary>
/// Theme data model
/// </summary>
public class ThemeData
{
/// <summary>
/// Theme file name without extension
/// </summary>
public string FileNameWithoutExtension { get; private init; }
/// <summary>
/// Theme name
/// </summary>
public string Name { get; private init; }
/// <summary>
/// Indicates whether the theme supports dark mode
/// </summary>
public bool? IsDark { get; private init; }
/// <summary>
/// Indicates whether the theme supports blur effects
/// </summary>
public bool? HasBlur { get; private init; }
/// <summary>
/// Theme data constructor
/// </summary>
public ThemeData(string fileNameWithoutExtension, string name, bool? isDark = null, bool? hasBlur = null)
{
FileNameWithoutExtension = fileNameWithoutExtension;
Name = name;
IsDark = isDark;
HasBlur = hasBlur;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public static bool operator !=(ThemeData left, ThemeData right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is not ThemeData other)
return false;
return FileNameWithoutExtension == other.FileNameWithoutExtension &&
Name == other.Name;
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(FileNameWithoutExtension, Name);
}
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}

View file

@ -40,6 +40,9 @@ namespace Flow.Launcher
private readonly Internationalization _translater;
private readonly MainViewModel _mainVM;
private Theme _theme;
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
private readonly object _saveSettingsLock = new();
#region Constructor
@ -360,6 +363,13 @@ namespace Flow.Launcher
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync,
Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
public List<ThemeData> 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)

View file

@ -12,6 +12,7 @@ using Flow.Launcher.Helper;
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<Theme.ThemeData> _themes;
public List<Theme.ThemeData> Themes => _themes ??= _theme.LoadAvailableThemes();
private List<ThemeData> _themes;
public List<ThemeData> 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();
}
}

View file

@ -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 @@ namespace Flow.Launcher.Plugin.Sys
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<Theme>();
#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<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();
#endregion
public ThemeSelector(PluginInitContext context)
{
_context = context;
@ -44,28 +17,30 @@ namespace Flow.Launcher.Plugin.Sys
public List<Result> 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<int> highlightData)
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList<int> 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 @@ namespace Flow.Launcher.Plugin.Sys
Score = score,
Action = c =>
{
SelectedTheme = theme;
_context.API.ReQuery();
if (_context.API.SetCurrentTheme(theme))
{
_context.API.ReQuery();
}
return false;
}
};