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 13 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
32 changes: 14 additions & 18 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,15 @@ private string GetThemePath(string themeName)

#endregion

#region Load & Change
#region Get & Change Theme

public ThemeData GetCurrentTheme()
{
var themes = GetAvailableThemes();
return themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme) ?? 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 +405,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 Down Expand Up @@ -591,7 +593,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
{
AutoDropShadow(useDropShadowEffect);
}
SetBlurForWindow(GetCurrentTheme(), backdropType);
SetBlurForWindow(_settings.Theme, backdropType);

if (!BlurEnabled)
{
Expand All @@ -610,7 +612,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 +900,5 @@ private static bool IsBlurTheme()
}

#endregion

#region Classes

public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);

#endregion
}
}
24 changes: 21 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 @@ -347,6 +347,24 @@ 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></returns>
public void 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;
}
}
13 changes: 13 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 @@ -356,6 +359,16 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", M

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 void SetCurrentTheme(ThemeData theme)
{
Theme.ChangeTheme(theme.FileNameWithoutExtension);
_ = _theme.RefreshFrameAsync();
Copy link
Preview

Copilot AI Apr 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider awaiting RefreshFrameAsync() or properly handling its exceptions to prevent silent failures in theme refresh.

Suggested change
_ = _theme.RefreshFrameAsync();
try
{
await _theme.RefreshFrameAsync();
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Log.Error($"Failed to refresh theme: {ex.Message}");
}

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jack251970 is this applicable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjw24 I think we can remove _ = _theme.RefreshFrameAsync(); here since ChangeTheme already calls this function.

Copy link
Member Author

@Jack251970 Jack251970 Apr 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, changing theme from Appreance Page and Sys plugin still work well

}

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
47 changes: 11 additions & 36 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,7 +76,7 @@ private Result CreateThemeResult(Theme.ThemeData theme, int score, IList<int> hi
Score = score,
Action = c =>
{
SelectedTheme = theme;
_context.API.SetCurrentTheme(theme);
_context.API.ReQuery();
return false;
}
Expand Down
Loading