Skip to content

New API Function from Theme & Improve Theme Model #3420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions Flow.Launcher.Core/Resource/Theme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -330,7 +326,7 @@ private ResourceDictionary GetResourceDictionary(string theme)

private ResourceDictionary GetCurrentResourceDictionary()
{
return GetResourceDictionary(GetCurrentTheme());
return GetResourceDictionary(_settings.Theme);
}

private ThemeData GetThemeDataFromPath(string path)
Expand Down Expand Up @@ -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<ThemeData> LoadAvailableThemes()
public List<ThemeData> GetAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
Expand All @@ -403,7 +410,7 @@ public List<ThemeData> LoadAvailableThemes()
public bool ChangeTheme(string theme = null)
{
if (string.IsNullOrEmpty(theme))
theme = GetCurrentTheme();
theme = _settings.Theme;

string path = GetThemePath(theme);
try
Expand All @@ -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;
Expand Down Expand Up @@ -591,7 +598,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
{
AutoDropShadow(useDropShadowEffect);
}
SetBlurForWindow(GetCurrentTheme(), backdropType);
SetBlurForWindow(_settings.Theme, backdropType);

if (!BlurEnabled)
{
Expand All @@ -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);
}

Expand Down Expand Up @@ -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
}
}
26 changes: 23 additions & 3 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -352,6 +352,26 @@ public interface IPublicAPI
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);

/// Load image from path. Support local, remote and data:image url.
/// If image path is missing, it will return a missing icon.
/// </summary>
Expand Down
77 changes: 77 additions & 0 deletions Flow.Launcher.Plugin/SharedModels/ThemeData.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public class PublicAPIInstance : IPublicAPI
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
Expand Down Expand Up @@ -370,6 +373,13 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "",
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);

public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) =>
ImageLoader.LoadAsync(path, loadFullImage, cacheImage);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}

Expand Down
51 changes: 14 additions & 37 deletions Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -11,61 +10,37 @@ 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<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;
}

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
Expand Down Expand Up @@ -101,8 +76,10 @@ private Result CreateThemeResult(Theme.ThemeData theme, int score, IList<int> hi
Score = score,
Action = c =>
{
SelectedTheme = theme;
_context.API.ReQuery();
if (_context.API.SetCurrentTheme(theme))
{
_context.API.ReQuery();
}
return false;
}
};
Expand Down
Loading