Merge branch 'dev' into 250320BookmarkFavicon

This commit is contained in:
Jack Ye 2025-04-02 22:18:16 +08:00 committed by GitHub
commit 2c6fdc0b2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
135 changed files with 6308 additions and 3384 deletions

View file

@ -1,14 +1,14 @@
using Flow.Launcher.Infrastructure.Logger; using System;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
@ -42,8 +42,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal IEnumerable<PluginPair> Setup() internal IEnumerable<PluginPair> Setup()
{ {
// If no plugin is using the language, return empty list
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase))) if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
{
return new List<PluginPair>(); return new List<PluginPair>();
}
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath)) if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
{ {
@ -55,24 +58,55 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
var noRuntimeMessage = string.Format( var noRuntimeMessage = string.Format(
InternationalizationManager.Instance.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"), API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
Language, Language,
EnvName, EnvName,
Environment.NewLine Environment.NewLine
); );
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{ {
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName); var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
string selectedFile;
selectedFile = GetFileFromDialog(msg, FileDialogFilter); var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
if (!string.IsNullOrEmpty(selectedFile)) if (!string.IsNullOrEmpty(selectedFile))
{
PluginsSettingsFilePath = selectedFile; PluginsSettingsFilePath = selectedFile;
}
// Nothing selected because user pressed cancel from the file dialog window // Nothing selected because user pressed cancel from the file dialog window
if (string.IsNullOrEmpty(selectedFile)) else
InstallEnvironment(); {
var forceDownloadMessage = string.Format(
API.GetTranslation("runtimeExecutableInvalidChooseDownload"),
Language,
EnvName,
Environment.NewLine
);
// Let users select valid path or choose to download
while (string.IsNullOrEmpty(selectedFile))
{
if (API.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// Continue select file
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
}
else
{
// User selected no, break the loop
break;
}
}
if (!string.IsNullOrEmpty(selectedFile))
{
PluginsSettingsFilePath = selectedFile;
}
else
{
InstallEnvironment();
}
}
} }
else else
{ {
@ -85,7 +119,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
else else
{ {
API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
Log.Error("PluginsLoader", Log.Error("PluginsLoader",
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment"); $"{Language}Environment");
@ -98,13 +132,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath) private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
{ {
if (expectedPath == currentPath) if (expectedPath == currentPath) return;
return;
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s)); FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
InstallEnvironment(); InstallEnvironment();
} }
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata); internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
@ -116,13 +148,16 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
foreach (var metadata in PluginMetadataList) foreach (var metadata in PluginMetadataList)
{ {
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
{
metadata.AssemblyName = string.Empty;
pluginPairs.Add(CreatePluginPair(filePath, metadata)); pluginPairs.Add(CreatePluginPair(filePath, metadata));
}
} }
return pluginPairs; return pluginPairs;
} }
private string GetFileFromDialog(string title, string filter = "") private static string GetFileFromDialog(string title, string filter = "")
{ {
var dlg = new OpenFileDialog var dlg = new OpenFileDialog
{ {
@ -136,7 +171,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
var result = dlg.ShowDialog(); var result = dlg.ShowDialog();
return result == DialogResult.OK ? dlg.FileName : string.Empty; return result == DialogResult.OK ? dlg.FileName : string.Empty;
} }
/// <summary> /// <summary>
@ -179,31 +213,33 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
else else
{ {
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName)) if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
{
settings.PluginSettings.PythonExecutablePath settings.PluginSettings.PythonExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath); = GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
}
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName)) if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
{
settings.PluginSettings.NodeExecutablePath settings.PluginSettings.NodeExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath); = GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
}
} }
} }
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName) private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
{ {
if (string.IsNullOrEmpty(filePath)) if (string.IsNullOrEmpty(filePath)) return false;
return false;
// DataLocation.PortableDataPath returns the current portable path, this determines if an out // DataLocation.PortableDataPath returns the current portable path, this determines if an out
// of date path is also a portable path. // of date path is also a portable path.
var portableAppEnvLocation = $"UserData\\{DataLocation.PluginEnvironments}\\{pluginEnvironmentName}"; var portableAppEnvLocation = Path.Combine("UserData", DataLocation.PluginEnvironments, pluginEnvironmentName);
return filePath.Contains(portableAppEnvLocation); return filePath.Contains(portableAppEnvLocation);
} }
private static bool IsUsingRoamingPath(string filePath) private static bool IsUsingRoamingPath(string filePath)
{ {
if (string.IsNullOrEmpty(filePath)) if (string.IsNullOrEmpty(filePath)) return false;
return false;
return filePath.StartsWith(DataLocation.RoamingDataPath); return filePath.StartsWith(DataLocation.RoamingDataPath);
} }
@ -213,8 +249,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
var index = filePath.IndexOf(DataLocation.PluginEnvironments); var index = filePath.IndexOf(DataLocation.PluginEnvironments);
// get the substring after "Environments" because we can not determine it dynamically // get the substring after "Environments" because we can not determine it dynamically
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count()); var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}"; return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
} }
} }
} }

View file

@ -1,10 +1,10 @@
using Droplex; using System.Collections.Generic;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Core.ExternalPlugins.Environments namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
@ -22,7 +22,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string FileDialogFilter => "Python|pythonw.exe"; internal override string FileDialogFilter => "Python|pythonw.exe";
internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; } internal override string PluginsSettingsFilePath
{
get => PluginSettings.PythonExecutablePath;
set => PluginSettings.PythonExecutablePath = value;
}
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Droplex;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO; using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0"); internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe"); internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; } internal override string PluginsSettingsFilePath
{
get => PluginSettings.NodeExecutablePath;
set => PluginSettings.NodeExecutablePath = value;
}
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Droplex;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO; using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0"); internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe"); internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; } internal override string PluginsSettingsFilePath
{
get => PluginSettings.NodeExecutablePath;
set => PluginSettings.NodeExecutablePath = value;
}
internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,28 +1,16 @@
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Microsoft.IO; using Microsoft.IO;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using CheckBox = System.Windows.Controls.CheckBox;
using Control = System.Windows.Controls.Control;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using UserControl = System.Windows.Controls.UserControl;
using System.Windows.Documents;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -42,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin
private int RequestId { get; set; } private int RequestId { get; set; }
private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json"); private string SettingPath => Path.Combine(Context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "Settings.json");
public override List<Result> LoadContextMenus(Result selectedResult) public override List<Result> LoadContextMenus(Result selectedResult)
{ {

View file

@ -1,32 +1,15 @@
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Microsoft.IO;
using System.Windows;
using System.Windows.Controls;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
using CheckBox = System.Windows.Controls.CheckBox;
using Control = System.Windows.Controls.Control; using Control = System.Windows.Controls.Control;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using UserControl = System.Windows.Controls.UserControl;
using System.Windows.Documents;
using static System.Windows.Forms.LinkLabel;
using Droplex;
using System.Windows.Forms;
using Microsoft.VisualStudio.Threading;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -44,8 +27,7 @@ namespace Flow.Launcher.Core.Plugin
private string SettingConfigurationPath => private string SettingConfigurationPath =>
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory, private string SettingDirectory => Context.CurrentPluginMetadata.PluginSettingsDirectoryPath;
Context.CurrentPluginMetadata.Name);
private string SettingPath => Path.Combine(SettingDirectory, "Settings.json"); private string SettingPath => Path.Combine(SettingDirectory, "Settings.json");
@ -166,13 +148,5 @@ namespace Flow.Launcher.Core.Plugin
{ {
return Settings.CreateSettingPanel(); return Settings.CreateSettingPanel();
} }
public void DeletePluginSettingsDirectory()
{
if (Directory.Exists(SettingDirectory))
{
Directory.Delete(SettingDirectory, true);
}
}
} }
} }

View file

@ -1,18 +1,14 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Forms;
using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using CheckBox = System.Windows.Controls.CheckBox;
using ComboBox = System.Windows.Controls.ComboBox; #nullable enable
using Control = System.Windows.Controls.Control;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using UserControl = System.Windows.Controls.UserControl;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -23,51 +19,72 @@ namespace Flow.Launcher.Core.Plugin
public required string SettingPath { get; init; } public required string SettingPath { get; init; }
public Dictionary<string, FrameworkElement> SettingControls { get; } = new(); public Dictionary<string, FrameworkElement> SettingControls { get; } = new();
public IReadOnlyDictionary<string, object> Inner => Settings; public IReadOnlyDictionary<string, object?> Inner => Settings;
protected ConcurrentDictionary<string, object> Settings { get; set; } protected ConcurrentDictionary<string, object?> Settings { get; set; } = null!;
public required IPublicAPI API { get; init; } public required IPublicAPI API { get; init; }
private JsonStorage<ConcurrentDictionary<string, object>> _storage; private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
// maybe move to resource? private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
private static readonly Thickness settingControlMargin = new(0, 9, 18, 9); private static readonly Thickness SettingPanelItemLeftMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftMargin");
private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9); private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0); private static readonly Thickness SettingPanelItemLeftTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftTopBottomMargin");
private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9); private static readonly double SettingPanelTextBoxMinWidth = (double)Application.Current.FindResource("SettingPanelTextBoxMinWidth");
private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9); private static readonly double SettingPanelPathTextBoxWidth = (double)Application.Current.FindResource("SettingPanelPathTextBoxWidth");
private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0); private static readonly double SettingPanelAreaTextBoxMinHeight = (double)Application.Current.FindResource("SettingPanelAreaTextBoxMinHeight");
private static readonly Thickness settingDescMargin = new(0, 2, 0, 0);
private static readonly Thickness settingSepMargin = new(0, 0, 0, 2);
public async Task InitializeAsync() public async Task InitializeAsync()
{ {
_storage = new JsonStorage<ConcurrentDictionary<string, object>>(SettingPath); if (Settings == null)
Settings = await _storage.LoadAsync();
if (Configuration == null)
{ {
return; _storage = new JsonStorage<ConcurrentDictionary<string, object?>>(SettingPath);
Settings = await _storage.LoadAsync();
// Because value type of settings dictionary is object which causes them to be JsonElement when loading from json files,
// we need to convert it to the correct type
foreach (var (key, value) in Settings)
{
if (value is not JsonElement jsonElement) continue;
Settings[key] = jsonElement.ValueKind switch
{
JsonValueKind.String => jsonElement.GetString() ?? value,
JsonValueKind.True => jsonElement.GetBoolean(),
JsonValueKind.False => jsonElement.GetBoolean(),
JsonValueKind.Null => null,
_ => value
};
}
} }
if (Configuration == null) return;
foreach (var (type, attributes) in Configuration.Body) foreach (var (type, attributes) in Configuration.Body)
{ {
if (attributes.Name == null) // Skip if the setting does not have attributes or name
{ if (attributes?.Name == null) continue;
continue;
}
if (!Settings.ContainsKey(attributes.Name)) // Skip if the setting does not have attributes or name
if (!NeedSaveInSettings(type)) continue;
// If need save in settings, we need to make sure the setting exists in the settings file
if (Settings.ContainsKey(attributes.Name)) continue;
if (type == "checkbox")
{
// If can parse the default value to bool, use it, otherwise use false
Settings[attributes.Name] = bool.TryParse(attributes.DefaultValue, out var value) && value;
}
else
{ {
Settings[attributes.Name] = attributes.DefaultValue; Settings[attributes.Name] = attributes.DefaultValue;
} }
} }
} }
public void UpdateSettings(IReadOnlyDictionary<string, object> settings) public void UpdateSettings(IReadOnlyDictionary<string, object> settings)
{ {
if (settings == null || settings.Count == 0) if (settings == null || settings.Count == 0) return;
return;
foreach (var (key, value) in settings) foreach (var (key, value) in settings)
{ {
@ -78,19 +95,23 @@ namespace Flow.Launcher.Core.Plugin
switch (control) switch (control)
{ {
case TextBox textBox: case TextBox textBox:
textBox.Dispatcher.Invoke(() => textBox.Text = value as string ?? string.Empty); var text = value as string ?? string.Empty;
textBox.Dispatcher.Invoke(() => textBox.Text = text);
break; break;
case PasswordBox passwordBox: case PasswordBox passwordBox:
passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string ?? string.Empty); var password = value as string ?? string.Empty;
passwordBox.Dispatcher.Invoke(() => passwordBox.Password = password);
break; break;
case ComboBox comboBox: case ComboBox comboBox:
comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value);
break; break;
case CheckBox checkBox: case CheckBox checkBox:
checkBox.Dispatcher.Invoke(() => var isChecked = value is bool boolValue
checkBox.IsChecked = value is bool isChecked ? boolValue
? isChecked // If can parse the default value to bool, use it, otherwise use false
: bool.Parse(value as string ?? string.Empty)); : value is string stringValue && bool.TryParse(stringValue, out var boolValueFromString)
&& boolValueFromString;
checkBox.Dispatcher.Invoke(() =>checkBox.IsChecked = isChecked);
break; break;
} }
} }
@ -108,7 +129,7 @@ namespace Flow.Launcher.Core.Plugin
{ {
_storage.Save(); _storage.Save();
} }
public bool NeedCreateSettingPanel() public bool NeedCreateSettingPanel()
{ {
// If there are no settings or the settings configuration is empty, return null // If there are no settings or the settings configuration is empty, return null
@ -118,329 +139,350 @@ namespace Flow.Launcher.Core.Plugin
public Control CreateSettingPanel() public Control CreateSettingPanel()
{ {
// No need to check if NeedCreateSettingPanel is true because CreateSettingPanel will only be called if it's true // No need to check if NeedCreateSettingPanel is true because CreateSettingPanel will only be called if it's true
// if (!NeedCreateSettingPanel()) return null;
var settingWindow = new UserControl(); // Create main grid with two columns (Column 1: Auto, Column 2: *)
var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center }; var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
mainPanel.ColumnDefinitions.Add(new ColumnDefinition()
ColumnDefinition gridCol1 = new ColumnDefinition();
ColumnDefinition gridCol2 = new ColumnDefinition();
gridCol1.Width = new GridLength(70, GridUnitType.Star);
gridCol2.Width = new GridLength(30, GridUnitType.Star);
mainPanel.ColumnDefinitions.Add(gridCol1);
mainPanel.ColumnDefinitions.Add(gridCol2);
settingWindow.Content = mainPanel;
int rowCount = 0;
foreach (var (type, attribute) in Configuration.Body)
{ {
Separator sep = new Separator(); Width = new GridLength(0, GridUnitType.Auto)
sep.VerticalAlignment = VerticalAlignment.Top; });
sep.Margin = settingSepMargin; mainPanel.ColumnDefinitions.Add(new ColumnDefinition()
sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ {
var panel = new StackPanel Width = new GridLength(1, GridUnitType.Star)
});
// Iterate over each setting and create one row for it
var rowCount = 0;
foreach (var (type, attributes) in Configuration!.Body)
{
// Skip if the setting does not have attributes or name
if (attributes?.Name == null) continue;
// Add a new row to the main grid
mainPanel.RowDefinitions.Add(new RowDefinition()
{ {
Orientation = Orientation.Vertical, Height = new GridLength(0, GridUnitType.Auto)
VerticalAlignment = VerticalAlignment.Center, });
Margin = settingLabelPanelMargin
};
RowDefinition gridRow = new RowDefinition();
mainPanel.RowDefinitions.Add(gridRow);
var name = new TextBlock()
{
Text = attribute.Label,
VerticalAlignment = VerticalAlignment.Center,
Margin = settingLabelMargin,
TextWrapping = TextWrapping.WrapWithOverflow
};
var desc = new TextBlock()
{
Text = attribute.Description,
FontSize = 12,
VerticalAlignment = VerticalAlignment.Center,
Margin = settingDescMargin,
TextWrapping = TextWrapping.WrapWithOverflow
};
desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
if (attribute.Description == null) /* if no description, hide */
desc.Visibility = Visibility.Collapsed;
if (type != "textBlock") /* if textBlock, hide desc */
{
panel.Children.Add(name);
panel.Children.Add(desc);
}
Grid.SetColumn(panel, 0);
Grid.SetRow(panel, rowCount);
// State controls for column 0 and 1
StackPanel? panel = null;
FrameworkElement contentControl; FrameworkElement contentControl;
// If the type is textBlock, separator, or checkbox, we do not need to create a panel
if (type != "textBlock" && type != "separator" && type != "checkbox")
{
// Create a panel to hold the label and description
panel = new StackPanel
{
Margin = SettingPanelItemTopBottomMargin,
Orientation = Orientation.Vertical,
VerticalAlignment = VerticalAlignment.Center
};
// Create a text block for name
var name = new TextBlock()
{
Text = attributes.Label,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
};
// Create a text block for description
TextBlock? desc = null;
if (attributes.Description != null)
{
desc = new TextBlock()
{
Text = attributes.Description,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
};
desc.SetResourceReference(TextBlock.StyleProperty, "SettingPanelTextBlockDescriptionStyle"); // for theme change
}
// Add the name and description to the panel
panel.Children.Add(name);
if (desc != null) panel.Children.Add(desc);
}
switch (type) switch (type)
{ {
case "textBlock": case "textBlock":
{
contentControl = new TextBlock
{ {
Text = attribute.Description.Replace("\\r\\n", "\r\n"), contentControl = new TextBlock
Margin = settingTextBlockMargin, {
Padding = new Thickness(0, 0, 0, 0), Text = attributes.Description?.Replace("\\r\\n", "\r\n") ?? string.Empty,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
TextAlignment = TextAlignment.Left, VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.Wrap Margin = SettingPanelItemTopBottomMargin,
}; TextAlignment = TextAlignment.Left,
TextWrapping = TextWrapping.Wrap
};
Grid.SetColumn(contentControl, 0); break;
Grid.SetColumnSpan(contentControl, 2); }
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "input": case "input":
{
var textBox = new TextBox()
{ {
Text = Settings[attribute.Name] as string ?? string.Empty, var textBox = new TextBox()
Margin = settingControlMargin, {
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, MinWidth = SettingPanelTextBoxMinWidth,
ToolTip = attribute.Description HorizontalAlignment = HorizontalAlignment.Left,
}; VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
};
textBox.TextChanged += (_, _) => textBox.TextChanged += (_, _) =>
{ {
Settings[attribute.Name] = textBox.Text; Settings[attributes.Name] = textBox.Text;
}; };
contentControl = textBox; contentControl = textBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); break;
Grid.SetColumn(sep, 0); }
Grid.SetColumnSpan(sep, 2);
break;
}
case "inputWithFileBtn": case "inputWithFileBtn":
case "inputWithFolderBtn": case "inputWithFolderBtn":
{
var textBox = new TextBox()
{ {
Margin = new Thickness(10, 0, 0, 0), var textBox = new TextBox()
Text = Settings[attribute.Name] as string ?? string.Empty,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
ToolTip = attribute.Description
};
textBox.TextChanged += (_, _) =>
{
Settings[attribute.Name] = textBox.Text;
};
var Btn = new System.Windows.Controls.Button()
{
Margin = new Thickness(10, 0, 0, 0), Content = "Browse"
};
Btn.Click += (_, _) =>
{
using CommonDialog dialog = type switch
{ {
"inputWithFolderBtn" => new FolderBrowserDialog(), Width = SettingPanelPathTextBoxWidth,
_ => new OpenFileDialog(), HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
}; };
if (dialog.ShowDialog() != DialogResult.OK) return;
var path = dialog switch textBox.TextChanged += (_, _) =>
{ {
FolderBrowserDialog folderDialog => folderDialog.SelectedPath, Settings[attributes.Name] = textBox.Text;
OpenFileDialog fileDialog => fileDialog.FileName,
}; };
textBox.Text = path;
Settings[attribute.Name] = path;
};
var dockPanel = new DockPanel() { Margin = settingControlMargin }; var Btn = new Button()
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin,
Content = API.GetTranslation("select")
};
DockPanel.SetDock(Btn, Dock.Right); Btn.Click += (_, _) =>
dockPanel.Children.Add(Btn); {
dockPanel.Children.Add(textBox); using System.Windows.Forms.CommonDialog dialog = type switch
contentControl = dockPanel; {
Grid.SetColumn(contentControl, 1); "inputWithFolderBtn" => new System.Windows.Forms.FolderBrowserDialog(),
Grid.SetRow(contentControl, rowCount); _ => new System.Windows.Forms.OpenFileDialog(),
if (rowCount != 0) };
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
Grid.SetColumn(sep, 0); {
Grid.SetColumnSpan(sep, 2); return;
}
break; var path = dialog switch
} {
System.Windows.Forms.FolderBrowserDialog folderDialog => folderDialog.SelectedPath,
System.Windows.Forms.OpenFileDialog fileDialog => fileDialog.FileName,
_ => throw new System.NotImplementedException()
};
textBox.Text = path;
Settings[attributes.Name] = path;
};
var stackPanel = new StackPanel()
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemTopBottomMargin,
Orientation = Orientation.Horizontal
};
// Create a stack panel to wrap the button and text box
stackPanel.Children.Add(textBox);
stackPanel.Children.Add(Btn);
contentControl = stackPanel;
break;
}
case "textarea": case "textarea":
{
var textBox = new TextBox()
{ {
Height = 120, var textBox = new TextBox()
Margin = settingControlMargin, {
VerticalAlignment = VerticalAlignment.Center, MinHeight = SettingPanelAreaTextBoxMinHeight,
TextWrapping = TextWrapping.WrapWithOverflow, HorizontalAlignment = HorizontalAlignment.Stretch,
AcceptsReturn = true, VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, Margin = SettingPanelItemLeftTopBottomMargin,
Text = Settings[attribute.Name] as string ?? string.Empty, TextWrapping = TextWrapping.WrapWithOverflow,
ToolTip = attribute.Description AcceptsReturn = true,
}; Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
};
textBox.TextChanged += (sender, _) => textBox.TextChanged += (sender, _) =>
{ {
Settings[attribute.Name] = ((TextBox)sender).Text; Settings[attributes.Name] = ((TextBox)sender).Text;
}; };
contentControl = textBox; contentControl = textBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); break;
Grid.SetColumn(sep, 0); }
Grid.SetColumnSpan(sep, 2);
break;
}
case "passwordBox": case "passwordBox":
{
var passwordBox = new PasswordBox()
{ {
Margin = settingControlMargin, var passwordBox = new PasswordBox()
Password = Settings[attribute.Name] as string ?? string.Empty, {
PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, MinWidth = SettingPanelTextBoxMinWidth,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Left,
ToolTip = attribute.Description VerticalAlignment = VerticalAlignment.Center,
}; Margin = SettingPanelItemLeftTopBottomMargin,
Password = Settings[attributes.Name] as string ?? string.Empty,
PasswordChar = attributes.passwordChar == default ? '*' : attributes.passwordChar,
ToolTip = attributes.Description,
};
passwordBox.PasswordChanged += (sender, _) => passwordBox.PasswordChanged += (sender, _) =>
{ {
Settings[attribute.Name] = ((PasswordBox)sender).Password; Settings[attributes.Name] = ((PasswordBox)sender).Password;
}; };
contentControl = passwordBox; contentControl = passwordBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); break;
Grid.SetColumn(sep, 0); }
Grid.SetColumnSpan(sep, 2);
break;
}
case "dropdown": case "dropdown":
{
var comboBox = new System.Windows.Controls.ComboBox()
{ {
ItemsSource = attribute.Options, var comboBox = new ComboBox()
SelectedItem = Settings[attribute.Name], {
Margin = settingControlMargin, ItemsSource = attributes.Options,
HorizontalAlignment = System.Windows.HorizontalAlignment.Right, SelectedItem = Settings[attributes.Name],
ToolTip = attribute.Description HorizontalAlignment = HorizontalAlignment.Left,
}; VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
ToolTip = attributes.Description
};
comboBox.SelectionChanged += (sender, _) => comboBox.SelectionChanged += (sender, _) =>
{ {
Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; Settings[attributes.Name] = (string)((ComboBox)sender).SelectedItem;
}; };
contentControl = comboBox; contentControl = comboBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); break;
Grid.SetColumn(sep, 0); }
Grid.SetColumnSpan(sep, 2);
break;
}
case "checkbox": case "checkbox":
var checkBox = new CheckBox
{ {
IsChecked = // If can parse the default value to bool, use it, otherwise use false
Settings[attribute.Name] is bool isChecked var defaultValue = bool.TryParse(attributes.DefaultValue, out var value) && value;
var checkBox = new CheckBox
{
IsChecked =
Settings[attributes.Name] is bool isChecked
? isChecked ? isChecked
: bool.Parse(attribute.DefaultValue), : defaultValue,
Margin = settingCheckboxMargin, HorizontalAlignment = HorizontalAlignment.Left,
HorizontalAlignment = System.Windows.HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center,
ToolTip = attribute.Description Margin = SettingPanelItemTopBottomMargin,
}; Content = attributes.Label,
ToolTip = attributes.Description
};
checkBox.Click += (sender, _) => checkBox.Click += (sender, _) =>
{ {
Settings[attribute.Name] = ((CheckBox)sender).IsChecked; Settings[attributes.Name] = ((CheckBox)sender).IsChecked ?? defaultValue;
}; };
contentControl = checkBox; contentControl = checkBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount); break;
Grid.SetColumn(sep, 0); }
Grid.SetColumnSpan(sep, 2);
break;
case "hyperlink": case "hyperlink":
var hyperlink = new Hyperlink { ToolTip = attribute.Description, NavigateUri = attribute.url };
var linkbtn = new System.Windows.Controls.Button
{ {
HorizontalAlignment = System.Windows.HorizontalAlignment.Right, var hyperlink = new Hyperlink
Margin = settingControlMargin {
}; ToolTip = attributes.Description,
NavigateUri = attributes.url
};
linkbtn.Content = attribute.urlLabel; hyperlink.Inlines.Add(attributes.urlLabel);
hyperlink.RequestNavigate += (sender, e) =>
{
API.OpenUrl(e.Uri);
e.Handled = true;
};
contentControl = linkbtn; var textBlock = new TextBlock()
Grid.SetColumn(contentControl, 1); {
Grid.SetRow(contentControl, rowCount); HorizontalAlignment = HorizontalAlignment.Left,
if (rowCount != 0) VerticalAlignment = VerticalAlignment.Center,
mainPanel.Children.Add(sep); Margin = SettingPanelItemLeftTopBottomMargin,
TextAlignment = TextAlignment.Left,
TextWrapping = TextWrapping.Wrap
};
textBlock.Inlines.Add(hyperlink);
Grid.SetRow(sep, rowCount); contentControl = textBlock;
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break; break;
}
case "separator":
{
var sep = new Separator();
sep.SetResourceReference(Separator.StyleProperty, "SettingPanelSeparatorStyle");
contentControl = sep;
break;
}
default: default:
continue; continue;
} }
if (type != "textBlock") // If type is textBlock or separator, we just add the content control to the main grid
SettingControls[attribute.Name] = contentControl; if (panel == null)
{
// Add the content control to the column 0, row rowCount and columnSpan 2 of the main grid
mainPanel.Children.Add(contentControl);
Grid.SetColumn(contentControl, 0);
Grid.SetColumnSpan(contentControl, 2);
Grid.SetRow(contentControl, rowCount);
}
else
{
// Add the panel to the column 0 and row rowCount of the main grid
mainPanel.Children.Add(panel);
Grid.SetColumn(panel, 0);
Grid.SetRow(panel, rowCount);
// Add the content control to the column 1 and row rowCount of the main grid
mainPanel.Children.Add(contentControl);
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
}
// Add into SettingControls for settings storage if need
if (NeedSaveInSettings(type)) SettingControls[attributes.Name] = contentControl;
mainPanel.Children.Add(panel);
mainPanel.Children.Add(contentControl);
rowCount++; rowCount++;
} }
return settingWindow; // Wrap the main grid in a user control
return new UserControl()
{
Content = mainPanel
};
}
private static bool NeedSaveInSettings(string type)
{
return type != "textBlock" && type != "separator" && type != "hyperlink";
} }
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.IO; using System.IO;
@ -9,7 +9,6 @@ using System.Text.Json;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
internal abstract class PluginConfig internal abstract class PluginConfig
{ {
/// <summary> /// <summary>
@ -112,7 +111,7 @@ namespace Flow.Launcher.Core.Plugin
metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath)); metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
metadata.PluginDirectory = pluginDirectory; metadata.PluginDirectory = pluginDirectory;
// for plugins which doesn't has ActionKeywords key // for plugins which doesn't has ActionKeywords key
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword }; metadata.ActionKeywords ??= new List<string> { metadata.ActionKeyword };
// for plugin still use old ActionKeyword // for plugin still use old ActionKeyword
metadata.ActionKeyword = metadata.ActionKeywords?[0]; metadata.ActionKeyword = metadata.ActionKeywords?[0];
} }
@ -137,4 +136,4 @@ namespace Flow.Launcher.Core.Plugin
return metadata; return metadata;
} }
} }
} }

View file

@ -1,20 +1,19 @@
using Flow.Launcher.Core.ExternalPlugins; using System;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System.Text.Json; using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -35,7 +34,7 @@ namespace Flow.Launcher.Core.Plugin
private static PluginsSettings Settings; private static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas; private static List<PluginMetadata> _metadatas;
private static List<string> _modifiedPlugins = new List<string>(); private static List<string> _modifiedPlugins = new();
/// <summary> /// <summary>
/// Directories that will hold Flow Launcher plugin directory /// Directories that will hold Flow Launcher plugin directory
@ -72,15 +71,20 @@ namespace Flow.Launcher.Core.Plugin
{ {
foreach (var pluginPair in AllPlugins) foreach (var pluginPair in AllPlugins)
{ {
switch (pluginPair.Plugin) await DisposePluginAsync(pluginPair);
{ }
case IDisposable disposable: }
disposable.Dispose();
break; private static async Task DisposePluginAsync(PluginPair pluginPair)
case IAsyncDisposable asyncDisposable: {
await asyncDisposable.DisposeAsync(); switch (pluginPair.Plugin)
break; {
} case IDisposable disposable:
disposable.Dispose();
break;
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
} }
} }
@ -155,6 +159,25 @@ namespace Flow.Launcher.Core.Plugin
Settings = settings; Settings = settings;
Settings.UpdatePluginSettings(_metadatas); Settings.UpdatePluginSettings(_metadatas);
AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings);
// Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins
UpdatePluginDirectory(_metadatas);
}
private static void UpdatePluginDirectory(List<PluginMetadata> metadatas)
{
foreach (var metadata in metadatas)
{
if (AllowedLanguage.IsDotNet(metadata.Language))
{
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName);
metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName);
}
else
{
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name);
metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name);
}
}
} }
/// <summary> /// <summary>
@ -205,9 +228,6 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface<IPluginI18n>());
InternationalizationManager.Instance.ChangeLanguage(Ioc.Default.GetRequiredService<Settings>().Language);
if (failedPlugins.Any()) if (failedPlugins.Any())
{ {
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
@ -228,11 +248,9 @@ namespace Flow.Launcher.Core.Plugin
if (query is null) if (query is null)
return Array.Empty<PluginPair>(); return Array.Empty<PluginPair>();
if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
return GlobalPlugins; return GlobalPlugins;
var plugin = NonGlobalPlugins[query.ActionKeyword];
return new List<PluginPair> return new List<PluginPair>
{ {
plugin plugin
@ -367,7 +385,16 @@ namespace Flow.Launcher.Core.Plugin
NonGlobalPlugins[newActionKeyword] = plugin; NonGlobalPlugins[newActionKeyword] = plugin;
} }
// Update action keywords and action keyword in plugin metadata
plugin.Metadata.ActionKeywords.Add(newActionKeyword); plugin.Metadata.ActionKeywords.Add(newActionKeyword);
if (plugin.Metadata.ActionKeywords.Count > 0)
{
plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
}
else
{
plugin.Metadata.ActionKeyword = string.Empty;
}
} }
/// <summary> /// <summary>
@ -388,16 +415,15 @@ namespace Flow.Launcher.Core.Plugin
if (oldActionkeyword != Query.GlobalPluginWildcardSign) if (oldActionkeyword != Query.GlobalPluginWildcardSign)
NonGlobalPlugins.Remove(oldActionkeyword); NonGlobalPlugins.Remove(oldActionkeyword);
// Update action keywords and action keyword in plugin metadata
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
} if (plugin.Metadata.ActionKeywords.Count > 0)
public static void ReplaceActionKeyword(string id, string oldActionKeyword, string newActionKeyword)
{
if (oldActionKeyword != newActionKeyword)
{ {
AddActionKeyword(id, newActionKeyword); plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
RemoveActionKeyword(id, oldActionKeyword); }
else
{
plugin.Metadata.ActionKeyword = string.Empty;
} }
} }
@ -438,10 +464,10 @@ namespace Flow.Launcher.Core.Plugin
/// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
/// unless it's a local path installation /// unless it's a local path installation
/// </summary> /// </summary>
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{ {
InstallPlugin(newVersion, zipFilePath, checkModified:false); InstallPlugin(newVersion, zipFilePath, checkModified:false);
UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
_modifiedPlugins.Add(existingVersion.ID); _modifiedPlugins.Add(existingVersion.ID);
} }
@ -456,9 +482,9 @@ namespace Flow.Launcher.Core.Plugin
/// <summary> /// <summary>
/// Uninstall a plugin. /// Uninstall a plugin.
/// </summary> /// </summary>
public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{ {
UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true); await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
} }
#endregion #endregion
@ -539,64 +565,63 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
throw new ArgumentException($"Plugin {plugin.Name} has been modified"); throw new ArgumentException($"Plugin {plugin.Name} has been modified");
} }
if (removePluginSettings || removePluginFromSettings)
{
// If we want to remove plugin from AllPlugins,
// we need to dispose them so that they can release file handles
// which can help FL to delete the plugin settings & cache folders successfully
var pluginPairs = AllPlugins.FindAll(p => p.Metadata.ID == plugin.ID);
foreach (var pluginPair in pluginPairs)
{
await DisposePluginAsync(pluginPair);
}
}
if (removePluginSettings) if (removePluginSettings)
{ {
if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader // For dotnet plugins, we need to remove their PluginJsonStorage instance
if (AllowedLanguage.IsDotNet(plugin.Language))
{ {
var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath);
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var assemblyName = assembly.GetName().Name;
// if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin
// so we need to remove it from the api instance
var method = API.GetType().GetMethod("RemovePluginSettings"); var method = API.GetType().GetMethod("RemovePluginSettings");
var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName }); method?.Invoke(API, new object[] { plugin.AssemblyName });
// if there exists a json storage for current plugin, we need to delete the directory path
if (pluginJsonStorage != null)
{
var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory");
try
{
deleteMethod?.Invoke(pluginJsonStorage, null);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
} }
else // the plugin with json prc interface
try
{ {
var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID); var pluginSettingsDirectory = plugin.PluginSettingsDirectoryPath;
if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin) if (Directory.Exists(pluginSettingsDirectory))
{ Directory.Delete(pluginSettingsDirectory, true);
try }
{ catch (Exception e)
jsonRpcPlugin.DeletePluginSettingsDirectory(); {
} Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e);
catch (Exception e) API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
{ string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
} }
} }
if (removePluginFromSettings) if (removePluginFromSettings)
{ {
Settings.Plugins.Remove(plugin.ID); try
{
var pluginCacheDirectory = plugin.PluginCacheDirectoryPath;
if (Directory.Exists(pluginCacheDirectory))
Directory.Delete(pluginCacheDirectory, true);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
}
Settings.RemovePluginSettings(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
} }

View file

@ -50,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
return plugins; return plugins;
} }
public static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source) private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
{ {
var erroredPlugins = new List<string>(); var erroredPlugins = new List<string>();
@ -74,9 +74,11 @@ namespace Flow.Launcher.Core.Plugin
typeof(IAsyncPlugin)); typeof(IAsyncPlugin));
plugin = Activator.CreateInstance(type) as IAsyncPlugin; plugin = Activator.CreateInstance(type) as IAsyncPlugin;
metadata.AssemblyName = assembly.GetName().Name;
} }
#if DEBUG #if DEBUG
catch (Exception e) catch (Exception)
{ {
throw; throw;
} }
@ -112,7 +114,7 @@ namespace Flow.Launcher.Core.Plugin
if (erroredPlugins.Count > 0) if (erroredPlugins.Count > 0)
{ {
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins); var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = "The following " var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
@ -130,23 +132,31 @@ namespace Flow.Launcher.Core.Plugin
return plugins; return plugins;
} }
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source) private static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
{ {
return source return source
.Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) .Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase))
.Select(metadata => new PluginPair .Select(metadata =>
{ {
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata return new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
};
}); });
} }
public static IEnumerable<PluginPair> ExecutableV2Plugins(IEnumerable<PluginMetadata> source) private static IEnumerable<PluginPair> ExecutableV2Plugins(IEnumerable<PluginMetadata> source)
{ {
return source return source
.Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase)) .Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase))
.Select(metadata => new PluginPair .Select(metadata =>
{ {
Plugin = new ExecutablePluginV2(metadata.ExecuteFilePath), Metadata = metadata return new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
};
}); });
} }
} }

View file

@ -67,9 +67,9 @@ namespace Flow.Launcher.Core.Resource
return DefaultLanguageCode; return DefaultLanguageCode;
} }
internal void AddPluginLanguageDirectories(IEnumerable<PluginPair> plugins) private void AddPluginLanguageDirectories()
{ {
foreach (var plugin in plugins) foreach (var plugin in PluginManager.GetPluginsForInterface<IPluginI18n>())
{ {
var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location; var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location;
var dir = Path.GetDirectoryName(location); var dir = Path.GetDirectoryName(location);
@ -96,6 +96,32 @@ namespace Flow.Launcher.Core.Resource
_oldResources.Clear(); _oldResources.Clear();
} }
/// <summary>
/// Initialize language. Will change app language and plugin language based on settings.
/// </summary>
public async Task InitializeLanguageAsync()
{
// Get actual language
var languageCode = _settings.Language;
if (languageCode == Constant.SystemLanguageCode)
{
languageCode = SystemLanguageCode;
}
// Get language by language code and change language
var language = GetLanguageByLanguageCode(languageCode);
// Add plugin language directories first so that we can load language files from plugins
AddPluginLanguageDirectories();
// Change language
await ChangeLanguageAsync(language);
}
/// <summary>
/// Change language during runtime. Will change app language and plugin language & save settings.
/// </summary>
/// <param name="languageCode"></param>
public void ChangeLanguage(string languageCode) public void ChangeLanguage(string languageCode)
{ {
languageCode = languageCode.NonNull(); languageCode = languageCode.NonNull();
@ -110,7 +136,12 @@ namespace Flow.Launcher.Core.Resource
// Get language by language code and change language // Get language by language code and change language
var language = GetLanguageByLanguageCode(languageCode); var language = GetLanguageByLanguageCode(languageCode);
ChangeLanguage(language, isSystem);
// Change language
_ = ChangeLanguageAsync(language);
// Save settings
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
} }
private Language GetLanguageByLanguageCode(string languageCode) private Language GetLanguageByLanguageCode(string languageCode)
@ -128,26 +159,22 @@ namespace Flow.Launcher.Core.Resource
} }
} }
private void ChangeLanguage(Language language, bool isSystem) private async Task ChangeLanguageAsync(Language language)
{ {
language = language.NonNull(); // Remove old language files and load language
RemoveOldLanguageFiles(); RemoveOldLanguageFiles();
if (language != AvailableLanguages.English) if (language != AvailableLanguages.English)
{ {
LoadLanguage(language); LoadLanguage(language);
} }
// Culture of main thread // Culture of main thread
// Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode);
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
// Raise event after culture is set // Raise event for plugins after culture is set
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode; await Task.Run(UpdatePluginMetadataTranslations);
_ = Task.Run(() =>
{
UpdatePluginMetadataTranslations();
});
} }
public bool PromptShouldUsePinyin(string languageCodeToSet) public bool PromptShouldUsePinyin(string languageCodeToSet)

View file

@ -3,21 +3,29 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Effects; using System.Windows.Media.Effects;
using System.Windows.Shell; using System.Windows.Shell;
using System.Windows.Threading;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Microsoft.Win32;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
public class Theme public class Theme
{ {
#region Properties & Fields
public bool BlurEnabled { get; private set; }
private const string ThemeMetadataNamePrefix = "Name:"; private const string ThemeMetadataNamePrefix = "Name:";
private const string ThemeMetadataIsDarkPrefix = "IsDark:"; private const string ThemeMetadataIsDarkPrefix = "IsDark:";
private const string ThemeMetadataHasBlurPrefix = "HasBlur:"; private const string ThemeMetadataHasBlurPrefix = "HasBlur:";
@ -31,14 +39,14 @@ namespace Flow.Launcher.Core.Resource
private string _oldTheme; private string _oldTheme;
private const string Folder = Constant.Themes; private const string Folder = Constant.Themes;
private const string Extension = ".xaml"; private const string Extension = ".xaml";
private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); private static string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder); private static string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
public string CurrentTheme => _settings.Theme; private Thickness _themeResizeBorderThickness;
public bool BlurEnabled { get; set; } #endregion
private double mainWindowWidth; #region Constructor
public Theme(IPublicAPI publicAPI, Settings settings) public Theme(IPublicAPI publicAPI, Settings settings)
{ {
@ -50,20 +58,32 @@ namespace Flow.Launcher.Core.Resource
MakeSureThemeDirectoriesExist(); MakeSureThemeDirectoriesExist();
var dicts = Application.Current.Resources.MergedDictionaries; var dicts = Application.Current.Resources.MergedDictionaries;
_oldResource = dicts.First(d => _oldResource = dicts.FirstOrDefault(d =>
{ {
if (d.Source == null) if (d.Source == null) return false;
return false;
var p = d.Source.AbsolutePath; var p = d.Source.AbsolutePath;
var dir = Path.GetDirectoryName(p).NonNull(); return p.Contains(Folder) && Path.GetExtension(p) == Extension;
var info = new DirectoryInfo(dir);
var f = info.Name;
var e = Path.GetExtension(p);
var found = f == Folder && e == Extension;
return found;
}); });
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
if (_oldResource != null)
{
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
else
{
Log.Error("Current theme resource not found. Initializing with default theme.");
_oldTheme = Constant.DefaultTheme;
};
}
#endregion
#region Theme Resources
public string GetCurrentTheme()
{
return _settings.Theme;
} }
private void MakeSureThemeDirectoriesExist() private void MakeSureThemeDirectoriesExist()
@ -81,68 +101,154 @@ namespace Flow.Launcher.Core.Resource
} }
} }
public bool ChangeTheme(string theme)
{
const string defaultTheme = Constant.DefaultTheme;
string path = GetThemePath(theme);
try
{
if (string.IsNullOrEmpty(path))
throw new DirectoryNotFoundException("Theme path can't be found <{path}>");
// reload all resources even if the theme itself hasn't changed in order to pickup changes
// to things like fonts
UpdateResourceDictionary(GetResourceDictionary(theme));
_settings.Theme = theme;
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
if (_oldTheme != theme || theme == defaultTheme)
{
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
BlurEnabled = Win32Helper.IsBlurTheme();
if (_settings.UseDropShadowEffect && !BlurEnabled)
AddDropShadowEffectToCurrentTheme();
Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled);
}
catch (DirectoryNotFoundException)
{
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
if (theme != defaultTheme)
{
_api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
ChangeTheme(defaultTheme);
}
return false;
}
catch (XamlParseException)
{
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
if (theme != defaultTheme)
{
_api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
ChangeTheme(defaultTheme);
}
return false;
}
return true;
}
private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate) private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate)
{ {
var dicts = Application.Current.Resources.MergedDictionaries; // Add new resources
if (!Application.Current.Resources.MergedDictionaries.Contains(dictionaryToUpdate))
{
Application.Current.Resources.MergedDictionaries.Add(dictionaryToUpdate);
}
// Remove old resources
if (_oldResource != null && _oldResource != dictionaryToUpdate &&
Application.Current.Resources.MergedDictionaries.Contains(_oldResource))
{
Application.Current.Resources.MergedDictionaries.Remove(_oldResource);
}
dicts.Remove(_oldResource);
dicts.Add(dictionaryToUpdate);
_oldResource = dictionaryToUpdate; _oldResource = dictionaryToUpdate;
} }
/// <summary>
/// Updates only the font settings and refreshes the UI.
/// </summary>
public void UpdateFonts()
{
try
{
// Load a ResourceDictionary for the specified theme.
var themeName = GetCurrentTheme();
var dict = GetThemeResourceDictionary(themeName);
// Apply font settings to the theme resource.
ApplyFontSettings(dict);
UpdateResourceDictionary(dict);
// Must apply blur and drop shadow effects
_ = RefreshFrameAsync();
}
catch (Exception e)
{
Log.Exception("Error occurred while updating theme fonts", e);
}
}
/// <summary>
/// Loads and applies font settings to the theme resource.
/// </summary>
private void ApplyFontSettings(ResourceDictionary dict)
{
if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
{
var fontFamily = new FontFamily(_settings.QueryBoxFont);
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
SetFontProperties(queryBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, true);
SetFontProperties(querySuggestionBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
}
if (dict["ItemTitleStyle"] is Style resultItemStyle &&
dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle &&
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle)
{
var fontFamily = new FontFamily(_settings.ResultFont);
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch);
SetFontProperties(resultItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
SetFontProperties(resultItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
SetFontProperties(resultHotkeyItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
SetFontProperties(resultHotkeyItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
}
if (dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
{
var fontFamily = new FontFamily(_settings.ResultSubFont);
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch);
SetFontProperties(resultSubItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
SetFontProperties(resultSubItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
}
}
/// <summary>
/// Applies font properties to a Style.
/// </summary>
private static void SetFontProperties(Style style, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, bool isTextBox)
{
// Remove existing font-related setters
if (isTextBox)
{
// First, find the setters to remove and store them in a list
var settersToRemove = style.Setters
.OfType<Setter>()
.Where(setter =>
setter.Property == Control.FontFamilyProperty ||
setter.Property == Control.FontStyleProperty ||
setter.Property == Control.FontWeightProperty ||
setter.Property == Control.FontStretchProperty)
.ToList();
// Remove each found setter one by one
foreach (var setter in settersToRemove)
{
style.Setters.Remove(setter);
}
// Add New font setter
style.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily));
style.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle));
style.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight));
style.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch));
// Set caret brush (retain existing logic)
var caretBrushPropertyValue = style.Setters.OfType<Setter>().Any(x => x.Property.Name == "CaretBrush");
var foregroundPropertyValue = style.Setters.OfType<Setter>().Where(x => x.Property.Name == "Foreground")
.Select(x => x.Value).FirstOrDefault();
if (!caretBrushPropertyValue && foregroundPropertyValue != null)
style.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue));
}
else
{
var settersToRemove = style.Setters
.OfType<Setter>()
.Where(setter =>
setter.Property == TextBlock.FontFamilyProperty ||
setter.Property == TextBlock.FontStyleProperty ||
setter.Property == TextBlock.FontWeightProperty ||
setter.Property == TextBlock.FontStretchProperty)
.ToList();
foreach (var setter in settersToRemove)
{
style.Setters.Remove(setter);
}
style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, fontFamily));
style.Setters.Add(new Setter(TextBlock.FontStyleProperty, fontStyle));
style.Setters.Add(new Setter(TextBlock.FontWeightProperty, fontWeight));
style.Setters.Add(new Setter(TextBlock.FontStretchProperty, fontStretch));
}
}
private ResourceDictionary GetThemeResourceDictionary(string theme) private ResourceDictionary GetThemeResourceDictionary(string theme)
{ {
var uri = GetThemePath(theme); var uri = GetThemePath(theme);
@ -154,9 +260,7 @@ namespace Flow.Launcher.Core.Resource
return dict; return dict;
} }
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(_settings.Theme); private ResourceDictionary GetResourceDictionary(string theme)
public ResourceDictionary GetResourceDictionary(string theme)
{ {
var dict = GetThemeResourceDictionary(theme); var dict = GetThemeResourceDictionary(theme);
@ -168,22 +272,22 @@ namespace Flow.Launcher.Core.Resource
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight); var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch); var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily)); queryBoxStyle.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle)); queryBoxStyle.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight)); queryBoxStyle.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch)); queryBoxStyle.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch));
var caretBrushPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Any(x => x.Property.Name == "CaretBrush"); var caretBrushPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Any(x => x.Property.Name == "CaretBrush");
var foregroundPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Where(x => x.Property.Name == "Foreground") var foregroundPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Where(x => x.Property.Name == "Foreground")
.Select(x => x.Value).FirstOrDefault(); .Select(x => x.Value).FirstOrDefault();
if (!caretBrushPropertyValue && foregroundPropertyValue != null) //otherwise BaseQueryBoxStyle will handle styling if (!caretBrushPropertyValue && foregroundPropertyValue != null) //otherwise BaseQueryBoxStyle will handle styling
queryBoxStyle.Setters.Add(new Setter(TextBox.CaretBrushProperty, foregroundPropertyValue)); queryBoxStyle.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue));
// Query suggestion box's font style is aligned with query box // Query suggestion box's font style is aligned with query box
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily)); querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle)); querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight)); querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch)); querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch));
} }
if (dict["ItemTitleStyle"] is Style resultItemStyle && if (dict["ItemTitleStyle"] is Style resultItemStyle &&
@ -213,36 +317,20 @@ namespace Flow.Launcher.Core.Resource
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
Array.ForEach( Array.ForEach(
new[] { resultSubItemStyle,resultSubItemSelectedStyle}, o new[] { resultSubItemStyle, resultSubItemSelectedStyle }, o
=> Array.ForEach(setters, p => o.Setters.Add(p))); => Array.ForEach(setters, p => o.Setters.Add(p)));
} }
/* Ignore Theme Window Width and use setting */ /* Ignore Theme Window Width and use setting */
var windowStyle = dict["WindowStyle"] as Style; var windowStyle = dict["WindowStyle"] as Style;
var width = _settings.WindowSize; var width = _settings.WindowSize;
windowStyle.Setters.Add(new Setter(Window.WidthProperty, width)); windowStyle.Setters.Add(new Setter(FrameworkElement.WidthProperty, width));
mainWindowWidth = (double)width;
return dict; return dict;
} }
private ResourceDictionary GetCurrentResourceDictionary( ) private ResourceDictionary GetCurrentResourceDictionary()
{ {
return GetResourceDictionary(_settings.Theme); return GetResourceDictionary(GetCurrentTheme());
}
public List<ThemeData> LoadAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
{
var filePaths = Directory
.GetFiles(themeDirectory)
.Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml"))
.Select(GetThemeDataFromPath);
themes.AddRange(filePaths);
}
return themes.OrderBy(o => o.Name).ToList();
} }
private ThemeData GetThemeDataFromPath(string path) private ThemeData GetThemeDataFromPath(string path)
@ -264,15 +352,15 @@ namespace Flow.Launcher.Core.Resource
{ {
if (line.StartsWith(ThemeMetadataNamePrefix, StringComparison.OrdinalIgnoreCase)) if (line.StartsWith(ThemeMetadataNamePrefix, StringComparison.OrdinalIgnoreCase))
{ {
name = line.Remove(0, ThemeMetadataNamePrefix.Length).Trim(); name = line[ThemeMetadataNamePrefix.Length..].Trim();
} }
else if (line.StartsWith(ThemeMetadataIsDarkPrefix, StringComparison.OrdinalIgnoreCase)) else if (line.StartsWith(ThemeMetadataIsDarkPrefix, StringComparison.OrdinalIgnoreCase))
{ {
isDark = bool.Parse(line.Remove(0, ThemeMetadataIsDarkPrefix.Length).Trim()); isDark = bool.Parse(line[ThemeMetadataIsDarkPrefix.Length..].Trim());
} }
else if (line.StartsWith(ThemeMetadataHasBlurPrefix, StringComparison.OrdinalIgnoreCase)) else if (line.StartsWith(ThemeMetadataHasBlurPrefix, StringComparison.OrdinalIgnoreCase))
{ {
hasBlur = bool.Parse(line.Remove(0, ThemeMetadataHasBlurPrefix.Length).Trim()); hasBlur = bool.Parse(line[ThemeMetadataHasBlurPrefix.Length..].Trim());
} }
} }
@ -293,6 +381,82 @@ namespace Flow.Launcher.Core.Resource
return string.Empty; return string.Empty;
} }
#endregion
#region Load & Change
public List<ThemeData> LoadAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
{
var filePaths = Directory
.GetFiles(themeDirectory)
.Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml"))
.Select(GetThemeDataFromPath);
themes.AddRange(filePaths);
}
return themes.OrderBy(o => o.Name).ToList();
}
public bool ChangeTheme(string theme = null)
{
if (string.IsNullOrEmpty(theme))
theme = GetCurrentTheme();
string path = GetThemePath(theme);
try
{
if (string.IsNullOrEmpty(path))
throw new DirectoryNotFoundException($"Theme path can't be found <{path}>");
// Retrieve theme resource always use the resource with font settings applied.
var resourceDict = GetResourceDictionary(theme);
UpdateResourceDictionary(resourceDict);
_settings.Theme = theme;
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
if (_oldTheme != theme || theme == Constant.DefaultTheme)
{
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
BlurEnabled = IsBlurTheme();
// Can only apply blur but here also apply drop shadow effect to avoid possible drop shadow effect issues
_ = RefreshFrameAsync();
return true;
}
catch (DirectoryNotFoundException)
{
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme));
ChangeTheme(Constant.DefaultTheme);
}
return false;
}
catch (XamlParseException)
{
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme));
ChangeTheme(Constant.DefaultTheme);
}
return false;
}
}
#endregion
#region Shadow Effect
public void AddDropShadowEffectToCurrentTheme() public void AddDropShadowEffectToCurrentTheme()
{ {
var dict = GetCurrentResourceDictionary(); var dict = GetCurrentResourceDictionary();
@ -301,7 +465,7 @@ namespace Flow.Launcher.Core.Resource
var effectSetter = new Setter var effectSetter = new Setter
{ {
Property = Border.EffectProperty, Property = UIElement.EffectProperty,
Value = new DropShadowEffect Value = new DropShadowEffect
{ {
Opacity = 0.3, Opacity = 0.3,
@ -311,13 +475,12 @@ namespace Flow.Launcher.Core.Resource
} }
}; };
var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter; if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is not Setter marginSetter)
if (marginSetter == null)
{ {
var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin); var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin);
marginSetter = new Setter() marginSetter = new Setter()
{ {
Property = Border.MarginProperty, Property = FrameworkElement.MarginProperty,
Value = margin, Value = margin,
}; };
windowBorderStyle.Setters.Add(marginSetter); windowBorderStyle.Setters.Add(marginSetter);
@ -347,14 +510,12 @@ namespace Flow.Launcher.Core.Resource
var dict = GetCurrentResourceDictionary(); var dict = GetCurrentResourceDictionary();
var windowBorderStyle = dict["WindowBorderStyle"] as Style; var windowBorderStyle = dict["WindowBorderStyle"] as Style;
var effectSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) as Setter; if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == UIElement.EffectProperty) is Setter effectSetter)
var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter;
if (effectSetter != null)
{ {
windowBorderStyle.Setters.Remove(effectSetter); windowBorderStyle.Setters.Remove(effectSetter);
} }
if (marginSetter != null)
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is Setter marginSetter)
{ {
var currentMargin = (Thickness)marginSetter.Value; var currentMargin = (Thickness)marginSetter.Value;
var newMargin = new Thickness( var newMargin = new Thickness(
@ -370,31 +531,378 @@ namespace Flow.Launcher.Core.Resource
UpdateResourceDictionary(dict); UpdateResourceDictionary(dict);
} }
public void SetResizeBorderThickness(WindowChrome windowChrome, bool fixedWindowSize)
{
if (fixedWindowSize)
{
windowChrome.ResizeBorderThickness = new Thickness(0);
}
else
{
windowChrome.ResizeBorderThickness = _themeResizeBorderThickness;
}
}
// because adding drop shadow effect will change the margin of the window, // because adding drop shadow effect will change the margin of the window,
// we need to update the window chrome thickness to correct set the resize border // we need to update the window chrome thickness to correct set the resize border
private static void SetResizeBoarderThickness(Thickness? effectMargin) private void SetResizeBoarderThickness(Thickness? effectMargin)
{ {
var window = Application.Current.MainWindow; var window = Application.Current.MainWindow;
if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome) if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome)
{ {
Thickness thickness; // Save the theme resize border thickness so that we can restore it if we change ResizeWindow setting
if (effectMargin == null) if (effectMargin == null)
{ {
thickness = SystemParameters.WindowResizeBorderThickness; _themeResizeBorderThickness = SystemParameters.WindowResizeBorderThickness;
} }
else else
{ {
thickness = new Thickness( _themeResizeBorderThickness = new Thickness(
effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left, effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left,
effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top, effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top,
effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right, effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right,
effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom); effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom);
} }
windowChrome.ResizeBorderThickness = thickness; // Apply the resize border thickness to the window chrome
SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
} }
} }
#endregion
#region Blur Handling
/// <summary>
/// Refreshes the frame to apply the current theme settings.
/// </summary>
public async Task RefreshFrameAsync()
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
// Get the actual backdrop type and drop shadow effect settings
var (backdropType, useDropShadowEffect) = GetActualValue();
// Remove OS minimizing/maximizing animation
// Methods.SetWindowAttribute(new WindowInteropHelper(mainWindow).Handle, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 3);
// The timing of adding the shadow effect should vary depending on whether the theme is transparent.
if (BlurEnabled)
{
AutoDropShadow(useDropShadowEffect);
}
SetBlurForWindow(GetCurrentTheme(), backdropType);
if (!BlurEnabled)
{
AutoDropShadow(useDropShadowEffect);
}
}, DispatcherPriority.Render);
}
/// <summary>
/// Sets the blur for a window via SetWindowCompositionAttribute
/// </summary>
public async Task SetBlurForWindowAsync()
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
// Get the actual backdrop type and drop shadow effect settings
var (backdropType, _) = GetActualValue();
SetBlurForWindow(GetCurrentTheme(), backdropType);
}, DispatcherPriority.Render);
}
/// <summary>
/// Gets the actual backdrop type and drop shadow effect settings based on the current theme status.
/// </summary>
public (BackdropTypes BackdropType, bool UseDropShadowEffect) GetActualValue()
{
var backdropType = _settings.BackdropType;
var useDropShadowEffect = _settings.UseDropShadowEffect;
// When changed non-blur theme, change to backdrop to none
if (!BlurEnabled)
{
backdropType = BackdropTypes.None;
}
// Dropshadow on and control disabled.(user can't change dropshadow with blur theme)
if (BlurEnabled)
{
useDropShadowEffect = true;
}
return (backdropType, useDropShadowEffect);
}
private void SetBlurForWindow(string theme, BackdropTypes backdropType)
{
var dict = GetResourceDictionary(theme);
if (dict == null) return;
var windowBorderStyle = dict.Contains("WindowBorderStyle") ? dict["WindowBorderStyle"] as Style : null;
if (windowBorderStyle == null) return;
var mainWindow = Application.Current.MainWindow;
if (mainWindow == null) return;
// Check if the theme supports blur
bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b;
if (BlurEnabled && hasBlur && Win32Helper.IsBackdropSupported())
{
// If the BackdropType is Mica or MicaAlt, set the windowborderstyle's background to transparent
if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt)
{
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property.Name == "Background"));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0))));
}
else if (backdropType == BackdropTypes.Acrylic)
{
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property.Name == "Background"));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
}
// Apply the blur effect
Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType);
ColorizeWindow(theme, backdropType);
}
else
{
// Apply default style when Blur is disabled
Win32Helper.DWMSetBackdropForWindow(mainWindow, BackdropTypes.None);
ColorizeWindow(theme, backdropType);
}
UpdateResourceDictionary(dict);
}
private void AutoDropShadow(bool useDropShadowEffect)
{
SetWindowCornerPreference("Default");
RemoveDropShadowEffectFromCurrentTheme();
if (useDropShadowEffect)
{
if (BlurEnabled && Win32Helper.IsBackdropSupported())
{
SetWindowCornerPreference("Round");
}
else
{
SetWindowCornerPreference("Default");
AddDropShadowEffectToCurrentTheme();
}
}
else
{
if (BlurEnabled && Win32Helper.IsBackdropSupported())
{
SetWindowCornerPreference("Default");
}
else
{
RemoveDropShadowEffectFromCurrentTheme();
}
}
}
private static void SetWindowCornerPreference(string cornerType)
{
Window mainWindow = Application.Current.MainWindow;
if (mainWindow == null)
return;
Win32Helper.DWMSetCornerPreferenceForWindow(mainWindow, cornerType);
}
// Get Background Color from WindowBorderStyle when there not color for BG.
// for theme has not "LightBG" or "DarkBG" case.
private Color GetWindowBorderStyleBackground(string theme)
{
var Resources = GetThemeResourceDictionary(theme);
var windowBorderStyle = (Style)Resources["WindowBorderStyle"];
var backgroundSetter = windowBorderStyle.Setters
.OfType<Setter>()
.FirstOrDefault(s => s.Property == Border.BackgroundProperty);
if (backgroundSetter != null)
{
// Background's Value is DynamicColor Case
var backgroundValue = backgroundSetter.Value;
if (backgroundValue is SolidColorBrush solidColorBrush)
{
return solidColorBrush.Color; // Return SolidColorBrush's Color
}
else if (backgroundValue is DynamicResourceExtension dynamicResource)
{
// When DynamicResource Extension it is, Key is resource's name.
var resourceKey = backgroundSetter.Value.ToString();
// find key in resource and return color.
if (Resources.Contains(resourceKey))
{
var colorResource = Resources[resourceKey];
if (colorResource is SolidColorBrush colorBrush)
{
return colorBrush.Color;
}
else if (colorResource is Color color)
{
return color;
}
}
}
}
return Colors.Transparent; // Default is transparent
}
private void ApplyPreviewBackground(Color? bgColor = null)
{
if (bgColor == null) return;
// Copy the existing WindowBorderStyle
var previewStyle = new Style(typeof(Border));
if (Application.Current.Resources.Contains("WindowBorderStyle"))
{
if (Application.Current.Resources["WindowBorderStyle"] is Style originalStyle)
{
foreach (var setter in originalStyle.Setters.OfType<Setter>())
{
previewStyle.Setters.Add(new Setter(setter.Property, setter.Value));
}
}
}
// Apply background color (remove transparency in color)
// WPF does not allow the use of an acrylic brush within the window's internal area,
// so transparency effects are not applied to the preview.
Color backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B);
previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(backgroundColor)));
// The blur theme keeps the corner round fixed (applying DWM code to modify it causes rendering issues).
// The non-blur theme retains the previously set WindowBorderStyle.
if (BlurEnabled)
{
previewStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(5)));
previewStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(1)));
}
Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle;
}
private void ColorizeWindow(string theme, BackdropTypes backdropType)
{
var dict = GetThemeResourceDictionary(theme);
if (dict == null) return;
var mainWindow = Application.Current.MainWindow;
if (mainWindow == null) return;
// Check if the theme supports blur
bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b;
// SystemBG value check (Auto, Light, Dark)
string systemBG = dict.Contains("SystemBG") ? dict["SystemBG"] as string : "Auto"; // 기본값 Auto
// Check the user's ColorScheme setting
string colorScheme = _settings.ColorScheme;
// Check system dark mode setting (read AppsUseLightTheme value)
int themeValue = (int)Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
bool isSystemDark = themeValue == 0;
// Final decision on whether to use dark mode
bool useDarkMode = false;
// If systemBG is not "Auto", prioritize it over ColorScheme and set the mode based on systemBG value
if (systemBG == "Dark")
{
useDarkMode = true; // Dark
}
else if (systemBG == "Light")
{
useDarkMode = false; // Light
}
else if (systemBG == "Auto")
{
// If systemBG is "Auto", decide based on ColorScheme
if (colorScheme == "Dark")
useDarkMode = true;
else if (colorScheme == "Light")
useDarkMode = false;
else
useDarkMode = isSystemDark; // Auto (based on system setting)
}
// Apply DWM Dark Mode
Win32Helper.DWMSetDarkModeForWindow(mainWindow, useDarkMode);
Color LightBG;
Color DarkBG;
// Retrieve LightBG value (fallback to WindowBorderStyle background color if not found)
try
{
LightBG = dict.Contains("LightBG") ? (Color)dict["LightBG"] : GetWindowBorderStyleBackground(theme);
}
catch (Exception)
{
LightBG = GetWindowBorderStyleBackground(theme);
}
// Retrieve DarkBG value (fallback to LightBG if not found)
try
{
DarkBG = dict.Contains("DarkBG") ? (Color)dict["DarkBG"] : LightBG;
}
catch (Exception)
{
DarkBG = LightBG;
}
// Select background color based on ColorScheme and SystemBG
Color selectedBG = useDarkMode ? DarkBG : LightBG;
ApplyPreviewBackground(selectedBG);
bool isBlurAvailable = hasBlur && Win32Helper.IsBackdropSupported(); // Windows 11 미만이면 hasBlur를 강제 false
if (!isBlurAvailable)
{
mainWindow.Background = Brushes.Transparent;
}
else
{
// Only set the background to transparent if the theme supports blur
if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt)
{
mainWindow.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
}
else
{
mainWindow.Background = new SolidColorBrush(selectedBG);
}
}
}
private static bool IsBlurTheme()
{
if (!Win32Helper.IsBackdropSupported()) // Windows 11 미만이면 무조건 false
return false;
var resource = Application.Current.TryFindResource("ThemeBlurEnabled");
return resource is bool b && b;
}
#endregion
#region Classes
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
#endregion
} }
} }

View file

@ -1,12 +0,0 @@
using System;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource
{
[Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Theme>() instead.")]
public class ThemeManager
{
public static Theme Instance
=> Ioc.Default.GetRequiredService<Theme>();
}
}

View file

@ -32,6 +32,7 @@ namespace Flow.Launcher.Infrastructure
public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png");
public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png"); public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png");
public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png"); public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png");
public static readonly string HistoryIcon = Path.Combine(ImagesDirectory, "history.png");
public static string PythonPath; public static string PythonPath;
public static string NodePath; public static string NodePath;
@ -47,6 +48,7 @@ namespace Flow.Launcher.Infrastructure
public const string Themes = "Themes"; public const string Themes = "Themes";
public const string Settings = "Settings"; public const string Settings = "Settings";
public const string Logs = "Logs"; public const string Logs = "Logs";
public const string Cache = "Cache";
public const string Website = "https://flowlauncher.com"; public const string Website = "https://flowlauncher.com";
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher"; public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";

View file

@ -67,8 +67,6 @@ namespace Flow.Launcher.Infrastructure
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First; return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
} }
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
/// <summary> /// <summary>
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1. /// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
/// </summary> /// </summary>

View file

@ -12,13 +12,13 @@ namespace Flow.Launcher.Infrastructure.Logger
{ {
public static class Log public static class Log
{ {
public const string DirectoryName = "Logs"; public const string DirectoryName = Constant.Logs;
public static string CurrentLogDirectory { get; } public static string CurrentLogDirectory { get; }
static Log() static Log()
{ {
CurrentLogDirectory = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Version); CurrentLogDirectory = DataLocation.VersionLogDirectory;
if (!Directory.Exists(CurrentLogDirectory)) if (!Directory.Exists(CurrentLogDirectory))
{ {
Directory.CreateDirectory(CurrentLogDirectory); Directory.CreateDirectory(CurrentLogDirectory);

View file

@ -0,0 +1,123 @@
using System.Collections.Generic;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Infrastructure;
/// <summary>
/// Contains full information about a display monitor.
/// Codes are edited from: <see href="https://github.com/Jack251970/DesktopWidgets3">.
/// </summary>
internal class MonitorInfo
{
/// <summary>
/// Gets the display monitors (including invisible pseudo-monitors associated with the mirroring drivers).
/// </summary>
/// <returns>A list of display monitors</returns>
public static unsafe IList<MonitorInfo> GetDisplayMonitors()
{
var monitorCount = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS);
var list = new List<MonitorInfo>(monitorCount);
var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) =>
{
list.Add(new MonitorInfo(monitor, rect));
return true;
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
}
return list;
}
/// <summary>
/// Gets the display monitor that is nearest to a given window.
/// </summary>
/// <param name="hwnd">Window handle</param>
/// <returns>The display monitor that is nearest to a given window, or null if no monitor is found.</returns>
public static unsafe MonitorInfo GetNearestDisplayMonitor(HWND hwnd)
{
var nearestMonitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
MonitorInfo nearestMonitorInfo = null;
var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) =>
{
if (monitor == nearestMonitor)
{
nearestMonitorInfo = new MonitorInfo(monitor, rect);
return false;
}
return true;
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
}
return nearestMonitorInfo;
}
private readonly HMONITOR _monitor;
internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect)
{
RectMonitor =
new Rect(new Point(rect->left, rect->top),
new Point(rect->right, rect->bottom));
_monitor = monitor;
var info = new MONITORINFOEXW() { monitorInfo = new MONITORINFO() { cbSize = (uint)sizeof(MONITORINFOEXW) } };
GetMonitorInfo(monitor, ref info);
RectWork =
new Rect(new Point(info.monitorInfo.rcWork.left, info.monitorInfo.rcWork.top),
new Point(info.monitorInfo.rcWork.right, info.monitorInfo.rcWork.bottom));
Name = new string(info.szDevice.AsSpan()).Replace("\0", "").Trim();
}
/// <summary>
/// Gets the name of the display.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the display monitor rectangle, expressed in virtual-screen coordinates.
/// </summary>
/// <remarks>
/// <note>If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.</note>
/// </remarks>
public Rect RectMonitor { get; }
/// <summary>
/// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates.
/// </summary>
/// <remarks>
/// <note>If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.</note>
/// </remarks>
public Rect RectWork { get; }
/// <summary>
/// Gets if the monitor is the the primary display monitor.
/// </summary>
public bool IsPrimary => _monitor == PInvoke.MonitorFromWindow(new(IntPtr.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY);
/// <inheritdoc />
public override string ToString() => $"{Name} {RectMonitor.Width}x{RectMonitor.Height}";
private static unsafe bool GetMonitorInfo(HMONITOR hMonitor, ref MONITORINFOEXW lpmi)
{
fixed (MONITORINFOEXW* lpmiLocal = &lpmi)
{
var lpmiBase = (MONITORINFO*)lpmiLocal;
var __result = PInvoke.GetMonitorInfo(hMonitor, lpmiBase);
return __result;
}
}
}

View file

@ -16,4 +16,46 @@ WM_KEYUP
WM_SYSKEYDOWN WM_SYSKEYDOWN
WM_SYSKEYUP WM_SYSKEYUP
EnumWindows EnumWindows
DwmSetWindowAttribute
DWM_SYSTEMBACKDROP_TYPE
DWM_WINDOW_CORNER_PREFERENCE
MAX_PATH
SystemParametersInfo
SetForegroundWindow
GetWindowLong
GetForegroundWindow
GetDesktopWindow
GetShellWindow
GetWindowRect
GetClassName
FindWindowEx
WINDOW_STYLE
SetLastError
WINDOW_EX_STYLE
GetSystemMetrics
EnumDisplayMonitors
MonitorFromWindow
GetMonitorInfo
MONITORINFOEXW
WM_ENTERSIZEMOVE
WM_EXITSIZEMOVE
GetKeyboardLayout
GetWindowThreadProcessId
ActivateKeyboardLayout
GetKeyboardLayoutList
PostMessage
WM_INPUTLANGCHANGEREQUEST
INPUTLANGCHANGE_FORWARD
LOCALE_TRANSIENT_KEYBOARD1
LOCALE_TRANSIENT_KEYBOARD2
LOCALE_TRANSIENT_KEYBOARD3
LOCALE_TRANSIENT_KEYBOARD4

View file

@ -0,0 +1,25 @@
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Windows.Win32;
// Edited from: https://github.com/files-community/Files
internal static partial class PInvoke
{
[DllImport("User32", EntryPoint = "SetWindowLongW", ExactSpelling = true)]
static extern int _SetWindowLong(HWND hWnd, int nIndex, int dwNewLong);
[DllImport("User32", EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)]
static extern nint _SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong);
// NOTE:
// CsWin32 doesn't generate SetWindowLong on other than x86 and vice versa.
// For more info, visit https://github.com/microsoft/CsWin32/issues/882
public static unsafe nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong)
{
return sizeof(nint) is 4
? _SetWindowLong(hWnd, (int)nIndex, (int)dwNewLong)
: _SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong);
}
}

View file

@ -1,9 +1,4 @@
using System; using System.IO;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
@ -16,18 +11,16 @@ namespace Flow.Launcher.Infrastructure.Storage
/// Normally, it has better performance, but not readable /// Normally, it has better performance, but not readable
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable /// It utilize MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
/// https://github.com/Cysharp/MemoryPack
/// </remarks> /// </remarks>
public class BinaryStorage<T> public class BinaryStorage<T>
{ {
const string DirectoryName = "Cache"; public const string FileSuffix = ".cache";
const string FileSuffix = ".cache"; // Let the derived class to set the file path
public BinaryStorage(string filename, string directoryPath = null)
public BinaryStorage(string filename)
{ {
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); directoryPath ??= DataLocation.CacheDirectory;
Helper.ValidateDirectory(directoryPath); Helper.ValidateDirectory(directoryPath);
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
@ -58,14 +51,14 @@ namespace Flow.Launcher.Infrastructure.Storage
} }
} }
private async ValueTask<T> DeserializeAsync(Stream stream, T defaultData) private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
{ {
try try
{ {
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream); var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
return t; return t;
} }
catch (System.Exception e) catch (System.Exception)
{ {
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); // Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
return defaultData; return defaultData;

View file

@ -16,7 +16,7 @@ namespace Flow.Launcher.Infrastructure.Storage
protected T? Data; protected T? Data;
// need a new directory name // need a new directory name
public const string DirectoryName = "Settings"; public const string DirectoryName = Constant.Settings;
public const string FileSuffix = ".json"; public const string FileSuffix = ".json";
protected string FilePath { get; init; } = null!; protected string FilePath { get; init; } = null!;

View file

@ -13,7 +13,7 @@ namespace Flow.Launcher.Infrastructure.Storage
// C# related, add python related below // C# related, add python related below
var dataType = typeof(T); var dataType = typeof(T);
AssemblyName = dataType.Assembly.GetName().Name; AssemblyName = dataType.Assembly.GetName().Name;
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName); DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
Helper.ValidateDirectory(DirectoryPath); Helper.ValidateDirectory(DirectoryPath);
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
@ -23,13 +23,5 @@ namespace Flow.Launcher.Infrastructure.Storage
{ {
Data = data; Data = data;
} }
public void DeleteDirectory()
{
if (Directory.Exists(DirectoryPath))
{
Directory.Delete(DirectoryPath, true);
}
}
} }
} }

View file

@ -25,8 +25,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
return false; return false;
} }
public static string VersionLogDirectory => Path.Combine(LogDirectory, Constant.Version);
public static string LogDirectory => Path.Combine(DataDirectory(), Constant.Logs);
public static readonly string CacheDirectory = Path.Combine(DataDirectory(), Constant.Cache);
public static readonly string SettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings);
public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins);
public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins); public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes);
public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectory, Constant.Plugins);
public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins);
public const string PythonEnvironmentName = "Python"; public const string PythonEnvironmentName = "Python";
public const string NodeEnvironmentName = "Node.js"; public const string NodeEnvironmentName = "Node.js";

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
@ -6,8 +7,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public class PluginsSettings : BaseModel public class PluginsSettings : BaseModel
{ {
private string pythonExecutablePath = string.Empty; private string pythonExecutablePath = string.Empty;
public string PythonExecutablePath { public string PythonExecutablePath
get { return pythonExecutablePath; } {
get => pythonExecutablePath;
set set
{ {
pythonExecutablePath = value; pythonExecutablePath = value;
@ -18,7 +20,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
private string nodeExecutablePath = string.Empty; private string nodeExecutablePath = string.Empty;
public string NodeExecutablePath public string NodeExecutablePath
{ {
get { return nodeExecutablePath; } get => nodeExecutablePath;
set set
{ {
nodeExecutablePath = value; nodeExecutablePath = value;
@ -26,19 +28,32 @@ namespace Flow.Launcher.Infrastructure.UserSettings
} }
} }
public Dictionary<string, Plugin> Plugins { get; set; } = new Dictionary<string, Plugin>(); /// <summary>
/// Only used for serialization
/// </summary>
public Dictionary<string, Plugin> Plugins { get; set; } = new();
/// <summary>
/// Update plugin settings with metadata.
/// FL will get default values from metadata first and then load settings to metadata
/// </summary>
/// <param name="metadatas">Parsed plugin metadatas</param>
public void UpdatePluginSettings(List<PluginMetadata> metadatas) public void UpdatePluginSettings(List<PluginMetadata> metadatas)
{ {
foreach (var metadata in metadatas) foreach (var metadata in metadatas)
{ {
if (Plugins.ContainsKey(metadata.ID)) if (Plugins.TryGetValue(metadata.ID, out var settings))
{ {
var settings = Plugins[metadata.ID]; // If settings exist, update settings & metadata value
// update settings values with metadata
if (string.IsNullOrEmpty(settings.Version)) if (string.IsNullOrEmpty(settings.Version))
{
settings.Version = metadata.Version; settings.Version = metadata.Version;
}
settings.DefaultActionKeywords = metadata.ActionKeywords; // metadata provides default values
settings.DefaultSearchDelayTime = metadata.SearchDelayTime; // metadata provides default values
// update metadata values with settings
if (settings.ActionKeywords?.Count > 0) if (settings.ActionKeywords?.Count > 0)
{ {
metadata.ActionKeywords = settings.ActionKeywords; metadata.ActionKeywords = settings.ActionKeywords;
@ -51,30 +66,65 @@ namespace Flow.Launcher.Infrastructure.UserSettings
} }
metadata.Disabled = settings.Disabled; metadata.Disabled = settings.Disabled;
metadata.Priority = settings.Priority; metadata.Priority = settings.Priority;
metadata.SearchDelayTime = settings.SearchDelayTime;
} }
else else
{ {
// If settings does not exist, create a new one
Plugins[metadata.ID] = new Plugin Plugins[metadata.ID] = new Plugin
{ {
ID = metadata.ID, ID = metadata.ID,
Name = metadata.Name, Name = metadata.Name,
Version = metadata.Version, Version = metadata.Version,
ActionKeywords = metadata.ActionKeywords, DefaultActionKeywords = metadata.ActionKeywords, // metadata provides default values
ActionKeywords = metadata.ActionKeywords, // use default value
Disabled = metadata.Disabled, Disabled = metadata.Disabled,
Priority = metadata.Priority Priority = metadata.Priority,
DefaultSearchDelayTime = metadata.SearchDelayTime, // metadata provides default values
SearchDelayTime = metadata.SearchDelayTime, // use default value
}; };
} }
} }
} }
public Plugin GetPluginSettings(string id)
{
if (Plugins.TryGetValue(id, out var plugin))
{
return plugin;
}
return null;
}
public Plugin RemovePluginSettings(string id)
{
Plugins.Remove(id, out var plugin);
return plugin;
}
} }
public class Plugin public class Plugin
{ {
public string ID { get; set; } public string ID { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Version { get; set; } public string Version { get; set; }
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
[JsonIgnore]
public List<string> DefaultActionKeywords { get; set; }
// a reference of the action keywords from plugin manager
public List<string> ActionKeywords { get; set; }
public int Priority { get; set; } public int Priority { get; set; }
[JsonIgnore]
public SearchDelayTime? DefaultSearchDelayTime { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchDelayTime? SearchDelayTime { get; set; }
/// <summary> /// <summary>
/// Used only to save the state of the plugin in settings /// Used only to save the state of the plugin in settings
/// </summary> /// </summary>

View file

@ -68,14 +68,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
get => _theme; get => _theme;
set set
{ {
if (value == _theme) if (value != _theme)
return; {
_theme = value; _theme = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(MaxResultsToShow)); OnPropertyChanged(nameof(MaxResultsToShow));
}
} }
} }
public bool UseDropShadowEffect { get; set; } = true; public bool UseDropShadowEffect { get; set; } = true;
public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None;
/* Appearance Settings. It should be separated from the setting later.*/ /* Appearance Settings. It should be separated from the setting later.*/
public double WindowHeightSize { get; set; } = 42; public double WindowHeightSize { get; set; } = 42;
@ -110,7 +112,34 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double SettingWindowHeight { get; set; } = 700; public double SettingWindowHeight { get; set; } = 700;
public double? SettingWindowTop { get; set; } = null; public double? SettingWindowTop { get; set; } = null;
public double? SettingWindowLeft { get; set; } = null; public double? SettingWindowLeft { get; set; } = null;
public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; public WindowState SettingWindowState { get; set; } = WindowState.Normal;
bool _showPlaceholder { get; set; } = false;
public bool ShowPlaceholder
{
get => _showPlaceholder;
set
{
if (_showPlaceholder != value)
{
_showPlaceholder = value;
OnPropertyChanged();
}
}
}
string _placeholderText { get; set; } = string.Empty;
public string PlaceholderText
{
get => _placeholderText;
set
{
if (_placeholderText != value)
{
_placeholderText = value;
OnPropertyChanged();
}
}
}
public int CustomExplorerIndex { get; set; } = 0; public int CustomExplorerIndex { get; set; } = 0;
@ -240,10 +269,26 @@ namespace Flow.Launcher.Infrastructure.UserSettings
/// </summary> /// </summary>
public double CustomWindowTop { get; set; } = 0; public double CustomWindowTop { get; set; } = 0;
public bool KeepMaxResults { get; set; } = false; /// <summary>
public int MaxResultsToShow { get; set; } = 5; /// Fixed window size
public int ActivateTimes { get; set; } /// </summary>
private bool _keepMaxResults { get; set; } = false;
public bool KeepMaxResults
{
get => _keepMaxResults;
set
{
if (_keepMaxResults != value)
{
_keepMaxResults = value;
OnPropertyChanged();
}
}
}
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>(); public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
@ -265,7 +310,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
bool _hideNotifyIcon { get; set; } bool _hideNotifyIcon { get; set; }
public bool HideNotifyIcon public bool HideNotifyIcon
{ {
get { return _hideNotifyIcon; } get => _hideNotifyIcon;
set set
{ {
_hideNotifyIcon = value; _hideNotifyIcon = value;
@ -275,6 +320,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool LeaveCmdOpen { get; set; } public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactivated { get; set; } = true; public bool HideWhenDeactivated { get; set; } = true;
public bool SearchQueryResultsWithDelay { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchDelayTime SearchDelayTime { get; set; } = SearchDelayTime.Normal;
[JsonConverter(typeof(JsonStringEnumConverter))] [JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor; public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
@ -297,7 +347,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonIgnore] [JsonIgnore]
public bool WMPInstalled { get; set; } = true; public bool WMPInstalled { get; set; } = true;
// This needs to be loaded last by staying at the bottom // This needs to be loaded last by staying at the bottom
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
@ -430,4 +479,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Fast, Fast,
Custom Custom
} }
public enum BackdropTypes
{
None,
Acrylic,
Mica,
MicaAlt
}
} }

View file

@ -1,7 +1,18 @@
using System; using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows; using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using Flow.Launcher.Infrastructure.UserSettings;
using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;
using Point = System.Windows.Point;
namespace Flow.Launcher.Infrastructure namespace Flow.Launcher.Infrastructure
{ {
@ -9,93 +20,473 @@ namespace Flow.Launcher.Infrastructure
{ {
#region Blur Handling #region Blur Handling
/* public static bool IsBackdropSupported()
Found on https://github.com/riverar/sample-win10-aeroglass
*/
private enum AccentState
{ {
ACCENT_DISABLED = 0, // Mica and Acrylic only supported Windows 11 22000+
ACCENT_ENABLE_GRADIENT = 1, return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, Environment.OSVersion.Version.Build >= 22000;
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
} }
[StructLayout(LayoutKind.Sequential)] public static unsafe bool DWMSetCloakForWindow(Window window, bool cloak)
private struct AccentPolicy
{ {
public AccentState AccentState; var cloaked = cloak ? 1 : 0;
public int AccentFlags;
public int GradientColor; return PInvoke.DwmSetWindowAttribute(
public int AnimationId; GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_CLOAK,
&cloaked,
(uint)Marshal.SizeOf<int>()).Succeeded;
} }
[StructLayout(LayoutKind.Sequential)] public static unsafe bool DWMSetBackdropForWindow(Window window, BackdropTypes backdrop)
private struct WindowCompositionAttributeData
{ {
public WindowCompositionAttribute Attribute; var backdropType = backdrop switch
public IntPtr Data; {
public int SizeOfData; BackdropTypes.Acrylic => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW,
BackdropTypes.Mica => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW,
BackdropTypes.MicaAlt => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TABBEDWINDOW,
_ => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_AUTO
};
return PInvoke.DwmSetWindowAttribute(
GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
&backdropType,
(uint)Marshal.SizeOf<int>()).Succeeded;
} }
private enum WindowCompositionAttribute public static unsafe bool DWMSetDarkModeForWindow(Window window, bool useDarkMode)
{ {
WCA_ACCENT_POLICY = 19 var darkMode = useDarkMode ? 1 : 0;
}
[DllImport("user32.dll")] return PInvoke.DwmSetWindowAttribute(
private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&darkMode,
(uint)Marshal.SizeOf<int>()).Succeeded;
}
/// <summary> /// <summary>
/// Checks if the blur theme is enabled ///
/// </summary> /// </summary>
public static bool IsBlurTheme() /// <param name="window"></param>
/// <param name="cornerType">DoNotRound, Round, RoundSmall, Default</param>
/// <returns></returns>
public static unsafe bool DWMSetCornerPreferenceForWindow(Window window, string cornerType)
{ {
if (Environment.OSVersion.Version >= new Version(6, 2)) var preference = cornerType switch
{ {
var resource = Application.Current.TryFindResource("ThemeBlurEnabled"); "DoNotRound" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND,
"Round" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND,
"RoundSmall" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL,
"Default" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DEFAULT,
_ => throw new InvalidOperationException("Invalid corner type")
};
if (resource is bool b) return PInvoke.DwmSetWindowAttribute(
return b; GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
&preference,
(uint)Marshal.SizeOf<int>()).Succeeded;
}
#endregion
#region Wallpaper
public static unsafe string GetWallpaperPath()
{
var wallpaperPtr = stackalloc char[(int)PInvoke.MAX_PATH];
PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, PInvoke.MAX_PATH,
wallpaperPtr,
0);
var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr);
return wallpaper.ToString();
}
#endregion
#region Window Foreground
public static nint GetForegroundWindow()
{
return PInvoke.GetForegroundWindow().Value;
}
public static bool SetForegroundWindow(Window window)
{
return PInvoke.SetForegroundWindow(GetWindowHandle(window));
}
public static bool SetForegroundWindow(nint handle)
{
return PInvoke.SetForegroundWindow(new(handle));
}
#endregion
#region Task Switching
/// <summary>
/// Hide windows in the Alt+Tab window list
/// </summary>
/// <param name="window">To hide a window</param>
public static void HideFromAltTab(Window window)
{
var hwnd = GetWindowHandle(window);
var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
// Add TOOLWINDOW style, remove APPWINDOW style
var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
}
/// <summary>
/// Restore window display in the Alt+Tab window list.
/// </summary>
/// <param name="window">To restore the displayed window</param>
public static void ShowInAltTab(Window window)
{
var hwnd = GetWindowHandle(window);
var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
// Remove the TOOLWINDOW style and add the APPWINDOW style.
var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
SetWindowStyle(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
}
/// <summary>
/// Disable windows toolbar's control box
/// This will also disable system menu with Alt+Space hotkey
/// </summary>
public static void DisableControlBox(Window window)
{
var hwnd = GetWindowHandle(window);
var style = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
style &= ~(int)WINDOW_STYLE.WS_SYSMENU;
SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style);
}
private static int GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex)
{
var style = PInvoke.GetWindowLong(hWnd, nIndex);
if (style == 0 && Marshal.GetLastPInvokeError() != 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return style;
}
private static nint SetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong)
{
PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error
var result = PInvoke.SetWindowLongPtr(hWnd, nIndex, dwNewLong);
if (result == 0 && Marshal.GetLastPInvokeError() != 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return result;
}
#endregion
#region Window Fullscreen
private const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
private const string WINDOW_CLASS_WINTAB = "Flip3D";
private const string WINDOW_CLASS_PROGMAN = "Progman";
private const string WINDOW_CLASS_WORKERW = "WorkerW";
private static HWND _hwnd_shell;
private static HWND HWND_SHELL =>
_hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow();
private static HWND _hwnd_desktop;
private static HWND HWND_DESKTOP =>
_hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow();
public static unsafe bool IsForegroundWindowFullscreen()
{
// Get current active window
var hWnd = PInvoke.GetForegroundWindow();
if (hWnd.Equals(HWND.Null))
{
return false; return false;
} }
return false; // If current active window is desktop or shell, exit early
if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))
{
return false;
}
string windowClass;
const int capacity = 256;
Span<char> buffer = stackalloc char[capacity];
int validLength;
fixed (char* pBuffer = buffer)
{
validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity);
}
windowClass = buffer[..validLength].ToString();
// For Win+Tab (Flip3D)
if (windowClass == WINDOW_CLASS_WINTAB)
{
return false;
}
PInvoke.GetWindowRect(hWnd, out var appBounds);
// For console (ConsoleWindowClass), we have to check for negative dimensions
if (windowClass == WINDOW_CLASS_CONSOLE)
{
return appBounds.top < 0 && appBounds.bottom < 0;
}
// For desktop (Progman or WorkerW, depends on the system), we have to check
if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW)
{
var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
if (hWndDesktop.Value != IntPtr.Zero)
{
return false;
}
}
var monitorInfo = MonitorInfo.GetNearestDisplayMonitor(hWnd);
return (appBounds.bottom - appBounds.top) == monitorInfo.RectMonitor.Height &&
(appBounds.right - appBounds.left) == monitorInfo.RectMonitor.Width;
}
#endregion
#region Pixel to DIP
/// <summary>
/// Transforms pixels to Device Independent Pixels used by WPF
/// </summary>
/// <param name="visual">current window, required to get presentation source</param>
/// <param name="unitX">horizontal position in pixels</param>
/// <param name="unitY">vertical position in pixels</param>
/// <returns>point containing device independent pixels</returns>
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
{
Matrix matrix;
var source = PresentationSource.FromVisual(visual);
if (source is not null)
{
matrix = source.CompositionTarget.TransformFromDevice;
}
else
{
using var src = new HwndSource(new HwndSourceParameters());
matrix = src.CompositionTarget.TransformFromDevice;
}
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
}
#endregion
#region WndProc
public const int WM_ENTERSIZEMOVE = (int)PInvoke.WM_ENTERSIZEMOVE;
public const int WM_EXITSIZEMOVE = (int)PInvoke.WM_EXITSIZEMOVE;
#endregion
#region Window Handle
internal static HWND GetWindowHandle(Window window, bool ensure = false)
{
var windowHelper = new WindowInteropHelper(window);
if (ensure)
{
windowHelper.EnsureHandle();
}
return new(windowHelper.Handle);
}
#endregion
#region Keyboard Layout
private const string UserProfileRegistryPath = @"Control Panel\International\User Profile";
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
private const string EnglishLanguageTag = "en";
private static readonly string[] ImeLanguageTags =
{
"zh", // Chinese
"ja", // Japanese
"ko", // Korean
};
private const uint KeyboardLayoutLoWord = 0xFFFF;
// Store the previous keyboard layout
private static HKL _previousLayout;
/// <summary>
/// Switches the keyboard layout to English if available.
/// </summary>
/// <param name="backupPrevious">If true, the current keyboard layout will be stored for later restoration.</param>
/// <exception cref="Win32Exception">Thrown when there's an error getting the window thread process ID.</exception>
public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious)
{
// Find an installed English layout
var enHKL = FindEnglishKeyboardLayout();
// No installed English layout found
if (enHKL == HKL.Null) return;
// Get the current foreground window
var hwnd = PInvoke.GetForegroundWindow();
if (hwnd == HWND.Null) return;
// Get the current foreground window thread ID
var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
// If the current layout has an IME mode, disable it without switching to another layout.
// This is needed because for languages with IME mode, Flow Launcher just temporarily disables
// the IME mode instead of switching to another layout.
var currentLayout = PInvoke.GetKeyboardLayout(threadId);
var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord;
foreach (var langTag in ImeLanguageTags)
{
if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
// Backup current keyboard layout
if (backupPrevious) _previousLayout = currentLayout;
// Switch to English layout
PInvoke.ActivateKeyboardLayout(enHKL, 0);
} }
/// <summary> /// <summary>
/// Sets the blur for a window via SetWindowCompositionAttribute /// Restores the previously backed-up keyboard layout.
/// If it wasn't backed up or has already been restored, this method does nothing.
/// </summary> /// </summary>
public static void SetBlurForWindow(Window w, bool blur) public static void RestorePreviousKeyboardLayout()
{ {
SetWindowAccent(w, blur ? AccentState.ACCENT_ENABLE_BLURBEHIND : AccentState.ACCENT_DISABLED); if (_previousLayout == HKL.Null) return;
var hwnd = PInvoke.GetForegroundWindow();
if (hwnd == HWND.Null) return;
PInvoke.PostMessage(
hwnd,
PInvoke.WM_INPUTLANGCHANGEREQUEST,
PInvoke.INPUTLANGCHANGE_FORWARD,
_previousLayout.Value
);
_previousLayout = HKL.Null;
} }
private static void SetWindowAccent(Window w, AccentState state) /// <summary>
/// Finds an installed English keyboard layout.
/// </summary>
/// <returns></returns>
/// <exception cref="Win32Exception"></exception>
private static unsafe HKL FindEnglishKeyboardLayout()
{ {
var windowHelper = new WindowInteropHelper(w); // Get the number of keyboard layouts
int count = PInvoke.GetKeyboardLayoutList(0, null);
if (count <= 0) return HKL.Null;
windowHelper.EnsureHandle(); // Get all keyboard layouts
var handles = new HKL[count];
var accent = new AccentPolicy { AccentState = state }; fixed (HKL* h = handles)
var accentStructSize = Marshal.SizeOf(accent);
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var data = new WindowCompositionAttributeData
{ {
Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, var result = PInvoke.GetKeyboardLayoutList(count, h);
SizeOfData = accentStructSize, if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
Data = accentPtr }
};
SetWindowCompositionAttribute(windowHelper.Handle, ref data); // Look for any English keyboard layout
foreach (var hkl in handles)
{
// The lower word contains the language identifier
var langId = (uint)hkl.Value & KeyboardLayoutLoWord;
var langTag = GetLanguageTag(langId);
Marshal.FreeHGlobal(accentPtr); // Check if it's an English layout
if (langTag.StartsWith(EnglishLanguageTag, StringComparison.OrdinalIgnoreCase))
{
return hkl;
}
}
return HKL.Null;
} }
/// <summary>
/// Returns the
/// <see href="https://learn.microsoft.com/globalization/locale/standard-locale-names">
/// BCP 47 language tag
/// </see>
/// of the current input language.
/// </summary>
/// <remarks>
/// Edited from: https://github.com/dotnet/winforms
/// </remarks>
private static string GetLanguageTag(uint langId)
{
// We need to convert the language identifier to a language tag, because they are deprecated and may have a
// transient value.
// https://learn.microsoft.com/globalization/locale/other-locale-names#lcid
// https://learn.microsoft.com/windows/win32/winmsg/wm-inputlangchange#remarks
//
// It turns out that the LCIDToLocaleName API, which is used inside CultureInfo, may return incorrect
// language tags for transient language identifiers. For example, it returns "nqo-GN" and "jv-Java-ID"
// instead of the "nqo" and "jv-Java" (as seen in the Get-WinUserLanguageList PowerShell cmdlet).
//
// Try to extract proper language tag from registry as a workaround approved by a Windows team.
// https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949
//
// NOTE: this logic may break in future versions of Windows since it is not documented.
if (langId is PInvoke.LOCALE_TRANSIENT_KEYBOARD1
or PInvoke.LOCALE_TRANSIENT_KEYBOARD2
or PInvoke.LOCALE_TRANSIENT_KEYBOARD3
or PInvoke.LOCALE_TRANSIENT_KEYBOARD4)
{
using var key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath);
if (key?.GetValue("Languages") is string[] languages)
{
foreach (string language in languages)
{
using var subKey = key.OpenSubKey(language);
if (subKey?.GetValue("TransientLangId") is int transientLangId
&& transientLangId == langId)
{
return language;
}
}
}
}
return CultureInfo.GetCultureInfo((int)langId).Name;
}
#endregion #endregion
} }
} }

View file

@ -51,6 +51,9 @@ namespace Flow.Launcher.Plugin
(WinPressed ? ModifierKeys.Windows : ModifierKeys.None); (WinPressed ? ModifierKeys.Windows : ModifierKeys.None);
} }
/// <summary>
/// Default <see cref="SpecialKeyState"/> object with all keys not pressed.
/// </summary>
public static readonly SpecialKeyState Default = new () { public static readonly SpecialKeyState Default = new () {
CtrlPressed = false, CtrlPressed = false,
ShiftPressed = false, ShiftPressed = false,

View file

@ -152,7 +152,6 @@ namespace Flow.Launcher.Plugin
/// <param name="callback"></param> /// <param name="callback"></param>
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback); public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);
/// <summary> /// <summary>
/// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses
/// </summary> /// </summary>
@ -191,14 +190,15 @@ namespace Flow.Launcher.Plugin
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default); Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default);
/// <summary> /// <summary>
/// Add ActionKeyword for specific plugin /// Add ActionKeyword and update action keyword metadata for specific plugin
/// Before adding, please check if action keyword is already assigned by <see cref="ActionKeywordAssigned"/>
/// </summary> /// </summary>
/// <param name="pluginId">ID for plugin that needs to add action keyword</param> /// <param name="pluginId">ID for plugin that needs to add action keyword</param>
/// <param name="newActionKeyword">The actionkeyword that is supposed to be added</param> /// <param name="newActionKeyword">The actionkeyword that is supposed to be added</param>
void AddActionKeyword(string pluginId, string newActionKeyword); void AddActionKeyword(string pluginId, string newActionKeyword);
/// <summary> /// <summary>
/// Remove ActionKeyword for specific plugin /// Remove ActionKeyword and update action keyword metadata for specific plugin
/// </summary> /// </summary>
/// <param name="pluginId">ID for plugin that needs to remove action keyword</param> /// <param name="pluginId">ID for plugin that needs to remove action keyword</param>
/// <param name="oldActionKeyword">The actionkeyword that is supposed to be removed</param> /// <param name="oldActionKeyword">The actionkeyword that is supposed to be removed</param>

View file

@ -4,17 +4,42 @@ using System.Threading;
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// Interface for plugins that want to manually update their results
/// </summary>
public interface IResultUpdated : IFeatures public interface IResultUpdated : IFeatures
{ {
/// <summary>
/// Event that is triggered when the results are updated
/// </summary>
event ResultUpdatedEventHandler ResultsUpdated; event ResultUpdatedEventHandler ResultsUpdated;
} }
/// <summary>
/// Delegate for the ResultsUpdated event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventArgs e); public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventArgs e);
/// <summary>
/// Event arguments for the ResultsUpdated event
/// </summary>
public class ResultUpdatedEventArgs : EventArgs public class ResultUpdatedEventArgs : EventArgs
{ {
/// <summary>
/// List of results that should be displayed
/// </summary>
public List<Result> Results; public List<Result> Results;
/// <summary>
/// Query that triggered the update
/// </summary>
public Query Query; public Query Query;
/// <summary>
/// Token that can be used to cancel the update
/// </summary>
public CancellationToken Token { get; init; } public CancellationToken Token { get; init; }
} }
} }

View file

@ -2,8 +2,15 @@
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// This interface is used to create settings panel for .Net plugins
/// </summary>
public interface ISettingProvider public interface ISettingProvider
{ {
/// <summary>
/// Create settings panel control for .Net plugins
/// </summary>
/// <returns></returns>
Control CreateSettingPanel(); Control CreateSettingPanel();
} }
} }

View file

@ -5,10 +5,18 @@
/// </summary> /// </summary>
public class PluginInitContext public class PluginInitContext
{ {
/// <summary>
/// Default constructor.
/// </summary>
public PluginInitContext() public PluginInitContext()
{ {
} }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="currentPluginMetadata"></param>
/// <param name="api"></param>
public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api) public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api)
{ {
CurrentPluginMetadata = currentPluginMetadata; CurrentPluginMetadata = currentPluginMetadata;

View file

@ -4,24 +4,77 @@ using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// Plugin metadata
/// </summary>
public class PluginMetadata : BaseModel public class PluginMetadata : BaseModel
{ {
private string _pluginDirectory; /// <summary>
/// Plugin ID.
/// </summary>
public string ID { get; set; } public string ID { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string Language { get; set; }
public string Description { get; set; }
public string Website { get; set; }
public bool Disabled { get; set; }
public string ExecuteFilePath { get; private set;}
/// <summary>
/// Plugin name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Plugin author.
/// </summary>
public string Author { get; set; }
/// <summary>
/// Plugin version.
/// </summary>
public string Version { get; set; }
/// <summary>
/// Plugin language.
/// See <see cref="AllowedLanguage"/>
/// </summary>
public string Language { get; set; }
/// <summary>
/// Plugin description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Plugin website.
/// </summary>
public string Website { get; set; }
/// <summary>
/// Whether plugin is disabled.
/// </summary>
public bool Disabled { get; set; }
/// <summary>
/// Plugin execute file path.
/// </summary>
public string ExecuteFilePath { get; private set; }
/// <summary>
/// Plugin execute file name.
/// </summary>
public string ExecuteFileName { get; set; } public string ExecuteFileName { get; set; }
/// <summary>
/// Plugin assembly name.
/// Only available for .Net plugins.
/// </summary>
[JsonIgnore]
public string AssemblyName { get; internal set; }
private string _pluginDirectory;
/// <summary>
/// Plugin source directory.
/// </summary>
public string PluginDirectory public string PluginDirectory
{ {
get { return _pluginDirectory; } get => _pluginDirectory;
internal set internal set
{ {
_pluginDirectory = value; _pluginDirectory = value;
@ -30,28 +83,78 @@ namespace Flow.Launcher.Plugin
} }
} }
/// <summary>
/// The first action keyword of plugin.
/// </summary>
public string ActionKeyword { get; set; } public string ActionKeyword { get; set; }
/// <summary>
/// All action keywords of plugin.
/// </summary>
public List<string> ActionKeywords { get; set; } public List<string> ActionKeywords { get; set; }
public string IcoPath { get; set;} /// <summary>
/// Hide plugin keyword setting panel.
public override string ToString() /// </summary>
{ public bool HideActionKeywordPanel { get; set; }
return Name;
}
/// <summary>
/// Plugin search delay time. Null means use default search delay time.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchDelayTime? SearchDelayTime { get; set; } = null;
/// <summary>
/// Plugin icon path.
/// </summary>
public string IcoPath { get; set;}
/// <summary>
/// Plugin priority.
/// </summary>
[JsonIgnore] [JsonIgnore]
public int Priority { get; set; } public int Priority { get; set; }
/// <summary> /// <summary>
/// Init time include both plugin load time and init time /// Init time include both plugin load time and init time.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public long InitTime { get; set; } public long InitTime { get; set; }
/// <summary>
/// Average query time.
/// </summary>
[JsonIgnore] [JsonIgnore]
public long AvgQueryTime { get; set; } public long AvgQueryTime { get; set; }
/// <summary>
/// Query count.
/// </summary>
[JsonIgnore] [JsonIgnore]
public int QueryCount { get; set; } public int QueryCount { get; set; }
/// <summary>
/// The path to the plugin settings directory which is not validated.
/// It is used to store plugin settings files and data files.
/// When plugin is deleted, FL will ask users whether to keep its settings.
/// If users do not want to keep, this directory will be deleted.
/// </summary>
public string PluginSettingsDirectoryPath { get; internal set; }
/// <summary>
/// The path to the plugin cache directory which is not validated.
/// It is used to store cache files.
/// When plugin is deleted, this directory will be deleted as well.
/// </summary>
public string PluginCacheDirectoryPath { get; internal set; }
/// <summary>
/// Convert <see cref="PluginMetadata"/> to string.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Name;
}
} }
} }

View file

@ -1,21 +1,37 @@
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// Plugin instance and plugin metadata
/// </summary>
public class PluginPair public class PluginPair
{ {
/// <summary>
/// Plugin instance
/// </summary>
public IAsyncPlugin Plugin { get; internal set; } public IAsyncPlugin Plugin { get; internal set; }
/// <summary>
/// Plugin metadata
/// </summary>
public PluginMetadata Metadata { get; internal set; } public PluginMetadata Metadata { get; internal set; }
/// <summary>
/// Convert to string
/// </summary>
/// <returns></returns>
public override string ToString() public override string ToString()
{ {
return Metadata.Name; return Metadata.Name;
} }
/// <summary>
/// Compare by plugin metadata ID
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
PluginPair r = obj as PluginPair; if (obj is PluginPair r)
if (r != null)
{ {
return string.Equals(r.Metadata.ID, Metadata.ID); return string.Equals(r.Metadata.ID, Metadata.ID);
} }
@ -25,6 +41,10 @@
} }
} }
/// <summary>
/// Get hash code
/// </summary>
/// <returns></returns>
public override int GetHashCode() public override int GetHashCode()
{ {
var hashcode = Metadata.ID?.GetHashCode() ?? 0; var hashcode = Metadata.ID?.GetHashCode() ?? 0;

View file

@ -2,10 +2,11 @@
namespace Flow.Launcher.Plugin namespace Flow.Launcher.Plugin
{ {
/// <summary>
/// Represents a query that is sent to a plugin.
/// </summary>
public class Query public class Query
{ {
public Query() { }
/// <summary> /// <summary>
/// Raw query, this includes action keyword if it has /// Raw query, this includes action keyword if it has
/// We didn't recommend use this property directly. You should always use Search property. /// We didn't recommend use this property directly. You should always use Search property.
@ -39,10 +40,9 @@ namespace Flow.Launcher.Plugin
public const string TermSeparator = " "; public const string TermSeparator = " ";
/// <summary> /// <summary>
/// User can set multiple action keywords seperated by ';' /// User can set multiple action keywords seperated by whitespace
/// </summary> /// </summary>
public const string ActionKeywordSeparator = ";"; public const string ActionKeywordSeparator = TermSeparator;
/// <summary> /// <summary>
/// Wildcard action keyword. Plugins using this value will be queried on every search. /// Wildcard action keyword. Plugins using this value will be queried on every search.
@ -55,13 +55,13 @@ namespace Flow.Launcher.Plugin
/// </summary> /// </summary>
public string ActionKeyword { get; init; } public string ActionKeyword { get; init; }
[JsonIgnore]
/// <summary> /// <summary>
/// Splits <see cref="SearchTerms"/> by spaces and returns the first item. /// Splits <see cref="SearchTerms"/> by spaces and returns the first item.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// returns an empty string when <see cref="SearchTerms"/> does not have enough items. /// returns an empty string when <see cref="SearchTerms"/> does not have enough items.
/// </remarks> /// </remarks>
[JsonIgnore]
public string FirstSearch => SplitSearch(0); public string FirstSearch => SplitSearch(0);
[JsonIgnore] [JsonIgnore]

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Runtime;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,7 +12,6 @@ namespace Flow.Launcher.Plugin
/// </summary> /// </summary>
public class Result public class Result
{ {
private string _pluginDirectory; private string _pluginDirectory;
private string _icoPath; private string _icoPath;

View file

@ -0,0 +1,32 @@
namespace Flow.Launcher.Plugin;
/// <summary>
/// Enum for search delay time
/// </summary>
public enum SearchDelayTime
{
/// <summary>
/// Very long search delay time. 250ms.
/// </summary>
VeryLong,
/// <summary>
/// Long search delay time. 200ms.
/// </summary>
Long,
/// <summary>
/// Normal search delay time. 150ms. Default value.
/// </summary>
Normal,
/// <summary>
/// Short search delay time. 100ms.
/// </summary>
Short,
/// <summary>
/// Very short search delay time. 50ms.
/// </summary>
VeryShort
}

View file

@ -6,6 +6,9 @@ using System.Linq;
namespace Flow.Launcher.Plugin.SharedCommands namespace Flow.Launcher.Plugin.SharedCommands
{ {
/// <summary>
/// Contains methods to open a search in a new browser window or tab.
/// </summary>
public static class SearchWeb public static class SearchWeb
{ {
private static string GetDefaultBrowserPath() private static string GetDefaultBrowserPath()
@ -106,4 +109,4 @@ namespace Flow.Launcher.Plugin.SharedCommands
} }
} }
} }
} }

View file

@ -8,12 +8,26 @@ using Windows.Win32.Foundation;
namespace Flow.Launcher.Plugin.SharedCommands namespace Flow.Launcher.Plugin.SharedCommands
{ {
/// <summary>
/// Contains methods for running shell commands
/// </summary>
public static class ShellCommand public static class ShellCommand
{ {
/// <summary>
/// Delegate for EnumThreadWindows
/// </summary>
/// <param name="hwnd"></param>
/// <param name="lParam"></param>
/// <returns></returns>
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
private static bool containsSecurityWindow; private static bool containsSecurityWindow;
/// <summary>
/// Runs a windows command using the provided ProcessStartInfo
/// </summary>
/// <param name="processStartInfo"></param>
/// <returns></returns>
public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo)
{ {
processStartInfo.Verb = "RunAsUser"; processStartInfo.Verb = "RunAsUser";
@ -65,6 +79,15 @@ namespace Flow.Launcher.Plugin.SharedCommands
return buffer[..length].ToString(); return buffer[..length].ToString();
} }
/// <summary>
/// Runs a windows command using the provided ProcessStartInfo
/// </summary>
/// <param name="fileName"></param>
/// <param name="workingDirectory"></param>
/// <param name="arguments"></param>
/// <param name="verb"></param>
/// <param name="createNoWindow"></param>
/// <returns></returns>
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "",
string arguments = "", string verb = "", bool createNoWindow = false) string arguments = "", string verb = "", bool createNoWindow = false)
{ {

View file

@ -2,14 +2,29 @@
namespace Flow.Launcher.Plugin.SharedModels namespace Flow.Launcher.Plugin.SharedModels
{ {
/// <summary>
/// Represents the result of a match operation.
/// </summary>
public class MatchResult public class MatchResult
{ {
/// <summary>
/// Initializes a new instance of the <see cref="MatchResult"/> class.
/// </summary>
/// <param name="success"></param>
/// <param name="searchPrecision"></param>
public MatchResult(bool success, SearchPrecisionScore searchPrecision) public MatchResult(bool success, SearchPrecisionScore searchPrecision)
{ {
Success = success; Success = success;
SearchPrecision = searchPrecision; SearchPrecision = searchPrecision;
} }
/// <summary>
/// Initializes a new instance of the <see cref="MatchResult"/> class.
/// </summary>
/// <param name="success"></param>
/// <param name="searchPrecision"></param>
/// <param name="matchData"></param>
/// <param name="rawScore"></param>
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore) public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
{ {
Success = success; Success = success;
@ -18,6 +33,9 @@ namespace Flow.Launcher.Plugin.SharedModels
RawScore = rawScore; RawScore = rawScore;
} }
/// <summary>
/// Whether the match operation was successful.
/// </summary>
public bool Success { get; set; } public bool Success { get; set; }
/// <summary> /// <summary>
@ -30,6 +48,9 @@ namespace Flow.Launcher.Plugin.SharedModels
/// </summary> /// </summary>
private int _rawScore; private int _rawScore;
/// <summary>
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
public int RawScore public int RawScore
{ {
get { return _rawScore; } get { return _rawScore; }
@ -45,8 +66,15 @@ namespace Flow.Launcher.Plugin.SharedModels
/// </summary> /// </summary>
public List<int> MatchData { get; set; } public List<int> MatchData { get; set; }
/// <summary>
/// The search precision score used to filter the search results.
/// </summary>
public SearchPrecisionScore SearchPrecision { get; set; } public SearchPrecisionScore SearchPrecision { get; set; }
/// <summary>
/// Determines if the search precision score is met.
/// </summary>
/// <returns></returns>
public bool IsSearchPrecisionScoreMet() public bool IsSearchPrecisionScoreMet()
{ {
return IsSearchPrecisionScoreMet(_rawScore); return IsSearchPrecisionScoreMet(_rawScore);
@ -63,10 +91,24 @@ namespace Flow.Launcher.Plugin.SharedModels
} }
} }
/// <summary>
/// Represents the search precision score used to filter search results.
/// </summary>
public enum SearchPrecisionScore public enum SearchPrecisionScore
{ {
/// <summary>
/// The highest search precision score.
/// </summary>
Regular = 50, Regular = 50,
/// <summary>
/// The medium search precision score.
/// </summary>
Low = 20, Low = 20,
/// <summary>
/// The lowest search precision score.
/// </summary>
None = 0 None = 0
} }
} }

View file

@ -3,6 +3,8 @@ using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
using Flow.Launcher.Core; using Flow.Launcher.Core;
using System.Linq;
using System.Collections.Generic;
namespace Flow.Launcher namespace Flow.Launcher
{ {
@ -32,14 +34,37 @@ namespace Flow.Launcher
private void btnDone_OnClick(object sender, RoutedEventArgs _) private void btnDone_OnClick(object sender, RoutedEventArgs _)
{ {
var oldActionKeyword = plugin.Metadata.ActionKeywords[0]; var oldActionKeywords = plugin.Metadata.ActionKeywords;
var newActionKeyword = tbAction.Text.Trim();
newActionKeyword = newActionKeyword.Length > 0 ? newActionKeyword : "*"; var newActionKeywords = tbAction.Text.Split(Query.ActionKeywordSeparator).ToList();
newActionKeywords.RemoveAll(string.IsNullOrEmpty);
if (!PluginViewModel.IsActionKeywordRegistered(newActionKeyword)) newActionKeywords = newActionKeywords.Distinct().ToList();
newActionKeywords = newActionKeywords.Count > 0 ? newActionKeywords : new() { Query.GlobalPluginWildcardSign };
var addedActionKeywords = newActionKeywords.Except(oldActionKeywords).ToList();
var removedActionKeywords = oldActionKeywords.Except(newActionKeywords).ToList();
if (!addedActionKeywords.Any(App.API.ActionKeywordAssigned))
{ {
pluginViewModel.ChangeActionKeyword(newActionKeyword, oldActionKeyword); if (oldActionKeywords.Count != newActionKeywords.Count)
Close(); {
ReplaceActionKeyword(plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
return;
}
var sortedOldActionKeywords = oldActionKeywords.OrderBy(s => s).ToList();
var sortedNewActionKeywords = newActionKeywords.OrderBy(s => s).ToList();
if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords))
{
// User just changes the sequence of action keywords
var msg = translater.GetTranslation("newActionKeywordsSameAsOld");
MessageBoxEx.Show(msg);
}
else
{
ReplaceActionKeyword(plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
}
} }
else else
{ {
@ -47,5 +72,21 @@ namespace Flow.Launcher
App.API.ShowMsgBox(msg); App.API.ShowMsgBox(msg);
} }
} }
private void ReplaceActionKeyword(string id, IReadOnlyList<string> removedActionKeywords, IReadOnlyList<string> addedActionKeywords)
{
foreach (var actionKeyword in removedActionKeywords)
{
App.API.RemoveActionKeyword(id, actionKeyword);
}
foreach (var actionKeyword in addedActionKeywords)
{
App.API.AddActionKeyword(id, actionKeyword);
}
// Update action keywords text and close window
pluginViewModel.OnActionKeywordsChanged();
Close();
}
} }
} }

View file

@ -4,7 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:ui="http://schemas.modernwpf.com/2019"
ShutdownMode="OnMainWindowClose" ShutdownMode="OnMainWindowClose"
Startup="OnStartupAsync"> Startup="OnStartup">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -27,11 +27,26 @@ namespace Flow.Launcher
{ {
public partial class App : IDisposable, ISingleInstanceApp public partial class App : IDisposable, ISingleInstanceApp
{ {
#region Public Properties
public static IPublicAPI API { get; private set; } public static IPublicAPI API { get; private set; }
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
#endregion
#region Private Fields
private static bool _disposed; private static bool _disposed;
private MainWindow _mainWindow;
private readonly MainViewModel _mainVM;
private readonly Settings _settings; private readonly Settings _settings;
// To prevent two disposals running at the same time.
private static readonly object _disposingLock = new();
#endregion
#region Constructor
public App() public App()
{ {
// Initialize settings // Initialize settings
@ -79,40 +94,55 @@ namespace Flow.Launcher
{ {
API = Ioc.Default.GetRequiredService<IPublicAPI>(); API = Ioc.Default.GetRequiredService<IPublicAPI>();
_settings.Initialize(); _settings.Initialize();
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
} }
catch (Exception e) catch (Exception e)
{ {
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e); ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
return; return;
} }
// Local function
static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
{
// Firstly show users the message
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
Environment.FailFast(message, e);
}
} }
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e) #endregion
{
// Firstly show users the message
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. #region Main
Environment.FailFast(message, e);
}
[STAThread] [STAThread]
public static void Main() public static void Main()
{ {
if (SingleInstance<App>.InitializeAsFirstInstance(Unique)) if (SingleInstance<App>.InitializeAsFirstInstance())
{ {
using (var application = new App()) using var application = new App();
{ application.InitializeComponent();
application.InitializeComponent(); application.Run();
application.Run();
}
} }
} }
private async void OnStartupAsync(object sender, StartupEventArgs e) #endregion
#region App Events
#pragma warning disable VSTHRD100 // Avoid async void methods
private async void OnStartup(object sender, StartupEventArgs e)
{ {
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
{ {
// Because new message box api uses MessageBoxEx window,
// if it is created and closed before main window is created, it will cause the application to exit.
// So set to OnExplicitShutdown to prevent the application from shutting down before main window is created
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
Log.SetLogLevel(_settings.LogLevel); Log.SetLogLevel(_settings.LogLevel);
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate(); Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
@ -127,29 +157,32 @@ namespace Flow.Launcher
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
// TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
PluginManager.LoadPlugins(_settings.PluginSettings); PluginManager.LoadPlugins(_settings.PluginSettings);
// Register ResultsUpdated event after all plugins are loaded
Ioc.Default.GetRequiredService<MainViewModel>().RegisterResultsUpdatedEvent();
Http.Proxy = _settings.Proxy; Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(); await PluginManager.InitializePluginsAsync();
// Change language after all plugins are initialized because we need to update plugin title based on their api
// TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
await imageLoadertask; await imageLoadertask;
var mainVM = Ioc.Default.GetRequiredService<MainViewModel>(); _mainWindow = new MainWindow();
var window = new MainWindow(_settings, mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
Current.MainWindow = window; Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.FlowLauncher; Current.MainWindow.Title = Constant.FlowLauncher;
HotKeyMapper.Initialize(); HotKeyMapper.Initialize();
// main windows needs initialized before theme change because of blur settings // main windows needs initialized before theme change because of blur settings
// TODO: Clean ThemeManager.Instance in future Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
ThemeManager.Instance.ChangeTheme(_settings.Theme);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
@ -159,11 +192,12 @@ namespace Flow.Launcher
AutoUpdates(); AutoUpdates();
API.SaveAppAllSettings(); API.SaveAppAllSettings();
Log.Info( Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------");
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
}); });
} }
#pragma warning restore VSTHRD100 // Avoid async void methods
private void AutoStartup() private void AutoStartup()
{ {
// we try to enable auto-startup on first launch, or reenable if it was removed // we try to enable auto-startup on first launch, or reenable if it was removed
@ -186,13 +220,11 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying // but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget // this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false; _settings.StartFlowLauncherOnSystemStartup = false;
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message);
e.Message);
} }
} }
} }
//[Conditional("RELEASE")]
private void AutoUpdates() private void AutoUpdates()
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
@ -210,11 +242,29 @@ namespace Flow.Launcher
}); });
} }
#endregion
#region Register Events
private void RegisterExitEvents() private void RegisterExitEvents()
{ {
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose(); AppDomain.CurrentDomain.ProcessExit += (s, e) =>
Current.Exit += (s, e) => Dispose(); {
Current.SessionEnding += (s, e) => Dispose(); Log.Info("|App.RegisterExitEvents|Process Exit");
Dispose();
};
Current.Exit += (s, e) =>
{
Log.Info("|App.RegisterExitEvents|Application Exit");
Dispose();
};
Current.SessionEnding += (s, e) =>
{
Log.Info("|App.RegisterExitEvents|Session Ending");
Dispose();
};
} }
/// <summary> /// <summary>
@ -235,20 +285,60 @@ namespace Flow.Launcher
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle; AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
} }
public void Dispose() #endregion
#region IDisposable
protected virtual void Dispose(bool disposing)
{ {
// if sessionending is called, exit proverbially be called when log off / shutdown // Prevent two disposes at the same time.
// but if sessionending is not called, exit won't be called when log off / shutdown lock (_disposingLock)
if (!_disposed)
{ {
API.SaveAppAllSettings(); if (!disposing)
{
return;
}
if (_disposed)
{
return;
}
_disposed = true; _disposed = true;
} }
Stopwatch.Normal("|App.Dispose|Dispose cost", () =>
{
Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------");
if (disposing)
{
// Dispose needs to be called on the main Windows thread,
// since some resources owned by the thread need to be disposed.
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
_mainVM?.Dispose();
}
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");
});
} }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
#region ISingleInstanceApp
public void OnSecondAppStarted() public void OnSecondAppStarted()
{ {
Ioc.Default.GetRequiredService<MainViewModel>().Show(); Ioc.Default.GetRequiredService<MainViewModel>().Show();
} }
#endregion
} }
} }

View file

@ -94,10 +94,6 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines --> <!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 --> <!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
<PackageReference Include="ModernWpfUI" Version="0.9.4" /> <PackageReference Include="ModernWpfUI" Version="0.9.4" />

View file

@ -1,66 +0,0 @@
using System;
using System.Windows;
using System.Windows.Interop;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.Controls;
namespace Flow.Launcher.Helper;
public class DwmDropShadow
{
/// <summary>
/// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven).
/// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
/// as AllowsTransparency involves a huge permormance issue (hardware acceleration is turned off for all the window).
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
public static void DropShadowToWindow(Window window)
{
if (!DropShadow(window))
{
window.SourceInitialized += window_SourceInitialized;
}
}
private static void window_SourceInitialized(object sender, EventArgs e) //fixed typo
{
Window window = (Window)sender;
DropShadow(window);
window.SourceInitialized -= window_SourceInitialized;
}
/// <summary>
/// The actual method that makes API calls to drop the shadow to the window
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
/// <returns>True if the method succeeded, false if not</returns>
private static unsafe bool DropShadow(Window window)
{
try
{
WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
var ret1 = PInvoke.DwmSetWindowAttribute(new (helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4);
if (ret1 == HRESULT.S_OK)
{
var m = new MARGINS { cyBottomHeight = 0, cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0 };
var ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m);
return ret2 == HRESULT.S_OK;
}
return false;
}
catch (Exception)
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
}
}

View file

@ -8,10 +8,10 @@ using System.Windows;
// modified to allow single instace restart // modified to allow single instace restart
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper
{ {
public interface ISingleInstanceApp public interface ISingleInstanceApp
{ {
void OnSecondAppStarted(); void OnSecondAppStarted();
} }
/// <summary> /// <summary>
/// This class checks to make sure that only one instance of /// This class checks to make sure that only one instance of
@ -24,9 +24,7 @@ namespace Flow.Launcher.Helper
/// running as Administrator, can activate it with command line arguments. /// running as Administrator, can activate it with command line arguments.
/// For most apps, this will not be much of an issue. /// For most apps, this will not be much of an issue.
/// </remarks> /// </remarks>
public static class SingleInstance<TApplication> public static class SingleInstance<TApplication> where TApplication : Application, ISingleInstanceApp
where TApplication: Application , ISingleInstanceApp
{ {
#region Private Fields #region Private Fields
@ -39,11 +37,12 @@ namespace Flow.Launcher.Helper
/// Suffix to the channel name. /// Suffix to the channel name.
/// </summary> /// </summary>
private const string ChannelNameSuffix = "SingeInstanceIPCChannel"; private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
private const string InstanceMutexName = "Flow.Launcher_Unique_Application_Mutex";
/// <summary> /// <summary>
/// Application mutex. /// Application mutex.
/// </summary> /// </summary>
internal static Mutex singleInstanceMutex; internal static Mutex SingleInstanceMutex { get; set; }
#endregion #endregion
@ -54,24 +53,23 @@ namespace Flow.Launcher.Helper
/// If not, activates the first instance. /// If not, activates the first instance.
/// </summary> /// </summary>
/// <returns>True if this is the first instance of the application.</returns> /// <returns>True if this is the first instance of the application.</returns>
public static bool InitializeAsFirstInstance( string uniqueName ) public static bool InitializeAsFirstInstance()
{ {
// Build unique application Id and the IPC channel name. // Build unique application Id and the IPC channel name.
string applicationIdentifier = uniqueName + Environment.UserName; string applicationIdentifier = InstanceMutexName + Environment.UserName;
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix); string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
// Create mutex based on unique application Id to check if this is the first instance of the application. // Create mutex based on unique application Id to check if this is the first instance of the application.
bool firstInstance; SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance);
singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
if (firstInstance) if (firstInstance)
{ {
_ = CreateRemoteService(channelName); _ = CreateRemoteServiceAsync(channelName);
return true; return true;
} }
else else
{ {
_ = SignalFirstInstance(channelName); _ = SignalFirstInstanceAsync(channelName);
return false; return false;
} }
} }
@ -81,7 +79,7 @@ namespace Flow.Launcher.Helper
/// </summary> /// </summary>
public static void Cleanup() public static void Cleanup()
{ {
singleInstanceMutex?.ReleaseMutex(); SingleInstanceMutex?.ReleaseMutex();
} }
#endregion #endregion
@ -93,22 +91,19 @@ namespace Flow.Launcher.Helper
/// Once receives signal from client, will activate first instance. /// Once receives signal from client, will activate first instance.
/// </summary> /// </summary>
/// <param name="channelName">Application's IPC channel name.</param> /// <param name="channelName">Application's IPC channel name.</param>
private static async Task CreateRemoteService(string channelName) private static async Task CreateRemoteServiceAsync(string channelName)
{ {
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In)) using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In);
while (true)
{ {
while(true) // Wait for connection to the pipe
{ await pipeServer.WaitForConnectionAsync();
// Wait for connection to the pipe
await pipeServer.WaitForConnectionAsync(); // Do an asynchronous call to ActivateFirstInstance function
if (Application.Current != null) Application.Current?.Dispatcher.Invoke(ActivateFirstInstance);
{
// Do an asynchronous call to ActivateFirstInstance function // Disconect client
Application.Current.Dispatcher.Invoke(ActivateFirstInstance); pipeServer.Disconnect();
}
// Disconect client
pipeServer.Disconnect();
}
} }
} }
@ -119,25 +114,13 @@ namespace Flow.Launcher.Helper
/// <param name="args"> /// <param name="args">
/// Command line arguments for the second instance, passed to the first instance to take appropriate action. /// Command line arguments for the second instance, passed to the first instance to take appropriate action.
/// </param> /// </param>
private static async Task SignalFirstInstance(string channelName) private static async Task SignalFirstInstanceAsync(string channelName)
{ {
// Create a client pipe connected to server // Create a client pipe connected to server
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out)) using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out);
{
// Connect to the available pipe
await pipeClient.ConnectAsync(0);
}
}
/// <summary> // Connect to the available pipe
/// Callback for activating first instance of the application. await pipeClient.ConnectAsync(0);
/// </summary>
/// <param name="arg">Callback argument.</param>
/// <returns>Always null.</returns>
private static object ActivateFirstInstanceCallback(object o)
{
ActivateFirstInstance();
return null;
} }
/// <summary> /// <summary>

View file

@ -2,22 +2,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Flow.Launcher.Infrastructure;
using Microsoft.Win32; using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Helper; namespace Flow.Launcher.Helper;
public static class WallpaperPathRetrieval public static class WallpaperPathRetrieval
{ {
private static readonly int MAX_PATH = 260; private const int MaxCacheSize = 3;
private static readonly int MAX_CACHE_SIZE = 3; private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
private static readonly object CacheLock = new();
private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new();
public static Brush GetWallpaperBrush() public static Brush GetWallpaperBrush()
{ {
@ -29,47 +26,72 @@ public static class WallpaperPathRetrieval
try try
{ {
var wallpaperPath = GetWallpaperPath(); var wallpaperPath = Win32Helper.GetWallpaperPath();
if (wallpaperPath is not null && File.Exists(wallpaperPath)) if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
{ {
// Since the wallpaper file name can be the same (TranscodedWallpaper), App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Wallpaper path is invalid: {wallpaperPath}");
// we need to add the last modified date to differentiate them var wallpaperColor = GetWallpaperColor();
var dateModified = File.GetLastWriteTime(wallpaperPath); return new SolidColorBrush(wallpaperColor);
wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); }
// Since the wallpaper file name can be the same (TranscodedWallpaper),
// we need to add the last modified date to differentiate them
var dateModified = File.GetLastWriteTime(wallpaperPath);
lock (CacheLock)
{
WallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper);
if (cachedWallpaper != null) if (cachedWallpaper != null)
{ {
return cachedWallpaper; return cachedWallpaper;
} }
}
using var fileStream = File.OpenRead(wallpaperPath);
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
var frame = decoder.Frames[0];
var originalWidth = frame.PixelWidth;
var originalHeight = frame.PixelHeight;
// We should not dispose the memory stream since the bitmap is still in use if (originalWidth == 0 || originalHeight == 0)
var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath)); {
var bitmap = new BitmapImage(); App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
bitmap.BeginInit(); return new SolidColorBrush(Colors.Transparent);
bitmap.StreamSource = memStream; }
bitmap.DecodePixelWidth = 800;
bitmap.DecodePixelHeight = 600;
bitmap.EndInit();
bitmap.Freeze(); // Make the bitmap thread-safe
var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
wallpaperBrush.Freeze(); // Make the brush thread-safe
// Manage cache size // Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
if (wallpaperCache.Count >= MAX_CACHE_SIZE) var widthRatio = 800.0 / originalWidth;
var heightRatio = 600.0 / originalHeight;
var scaleFactor = Math.Min(widthRatio, heightRatio);
var decodedPixelWidth = (int)(originalWidth * scaleFactor);
var decodedPixelHeight = (int)(originalHeight * scaleFactor);
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(wallpaperPath);
bitmap.DecodePixelWidth = decodedPixelWidth;
bitmap.DecodePixelHeight = decodedPixelHeight;
bitmap.EndInit();
bitmap.Freeze(); // Make the bitmap thread-safe
var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
wallpaperBrush.Freeze(); // Make the brush thread-safe
// Manage cache size
lock (CacheLock)
{
if (WallpaperCache.Count >= MaxCacheSize)
{ {
// Remove the oldest wallpaper from the cache // Remove the oldest wallpaper from the cache
var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); var oldestCache = WallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
if (oldestCache != default) if (oldestCache != default)
{ {
wallpaperCache.Remove(oldestCache); WallpaperCache.Remove(oldestCache);
} }
} }
wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); WallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
return wallpaperBrush; return wallpaperBrush;
} }
var wallpaperColor = GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -78,20 +100,9 @@ public static class WallpaperPathRetrieval
} }
} }
private static unsafe string GetWallpaperPath()
{
var wallpaperPtr = stackalloc char[MAX_PATH];
PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH,
wallpaperPtr,
0);
var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr);
return wallpaper.ToString();
}
private static Color GetWallpaperColor() private static Color GetWallpaperColor()
{ {
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true); RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
var result = key?.GetValue("Background", null); var result = key?.GetValue("Background", null);
if (result is string strResult) if (result is string strResult)
{ {
@ -100,8 +111,9 @@ public static class WallpaperPathRetrieval
var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList(); var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]); return Color.FromRgb(parts[0], parts[1], parts[2]);
} }
catch catch (Exception ex)
{ {
App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex);
} }
} }

View file

@ -1,211 +0,0 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Point = System.Windows.Point;
namespace Flow.Launcher.Helper;
public class WindowsInteropHelper
{
private static HWND _hwnd_shell;
private static HWND _hwnd_desktop;
//Accessors for shell and desktop handlers
//Will set the variables once and then will return them
private static HWND HWND_SHELL
{
get
{
return _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow();
}
}
private static HWND HWND_DESKTOP
{
get
{
return _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow();
}
}
const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
const string WINDOW_CLASS_WINTAB = "Flip3D";
const string WINDOW_CLASS_PROGMAN = "Progman";
const string WINDOW_CLASS_WORKERW = "WorkerW";
public unsafe static bool IsWindowFullscreen()
{
//get current active window
var hWnd = PInvoke.GetForegroundWindow();
if (hWnd.Equals(HWND.Null))
{
return false;
}
//if current active window is desktop or shell, exit early
if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))
{
return false;
}
string windowClass;
const int capacity = 256;
Span<char> buffer = stackalloc char[capacity];
int validLength;
fixed (char* pBuffer = buffer)
{
validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity);
}
windowClass = buffer[..validLength].ToString();
//for Win+Tab (Flip3D)
if (windowClass == WINDOW_CLASS_WINTAB)
{
return false;
}
PInvoke.GetWindowRect(hWnd, out var appBounds);
//for console (ConsoleWindowClass), we have to check for negative dimensions
if (windowClass == WINDOW_CLASS_CONSOLE)
{
return appBounds.top < 0 && appBounds.bottom < 0;
}
//for desktop (Progman or WorkerW, depends on the system), we have to check
if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW)
{
var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
if (hWndDesktop.Value != (IntPtr.Zero))
{
return false;
}
}
Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds;
return (appBounds.bottom - appBounds.top) == screenBounds.Height &&
(appBounds.right - appBounds.left) == screenBounds.Width;
}
/// <summary>
/// disable windows toolbar's control box
/// this will also disable system menu with Alt+Space hotkey
/// </summary>
public static void DisableControlBox(Window win)
{
var hwnd = new HWND(new WindowInteropHelper(win).Handle);
var style = PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
if (style == 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
style &= ~(int)WINDOW_STYLE.WS_SYSMENU;
var previousStyle = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE,
style);
if (previousStyle == 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
}
/// <summary>
/// Transforms pixels to Device Independent Pixels used by WPF
/// </summary>
/// <param name="visual">current window, required to get presentation source</param>
/// <param name="unitX">horizontal position in pixels</param>
/// <param name="unitY">vertical position in pixels</param>
/// <returns>point containing device independent pixels</returns>
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
{
Matrix matrix;
var source = PresentationSource.FromVisual(visual);
if (source is not null)
{
matrix = source.CompositionTarget.TransformFromDevice;
}
else
{
using var src = new HwndSource(new HwndSourceParameters());
matrix = src.CompositionTarget.TransformFromDevice;
}
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
}
#region Alt Tab
private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong)
{
PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error
var result = PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong);
if (result == 0 && Marshal.GetLastPInvokeError() != 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return result;
}
/// <summary>
/// Hide windows in the Alt+Tab window list
/// </summary>
/// <param name="window">To hide a window</param>
public static void HideFromAltTab(Window window)
{
var exStyle = GetCurrentWindowStyle(window);
// Add TOOLWINDOW style, remove APPWINDOW style
var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
}
/// <summary>
/// Restore window display in the Alt+Tab window list.
/// </summary>
/// <param name="window">To restore the displayed window</param>
public static void ShowInAltTab(Window window)
{
var exStyle = GetCurrentWindowStyle(window);
// Remove the TOOLWINDOW style and add the APPWINDOW style.
var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
}
/// <summary>
/// To obtain the current overridden style of a window.
/// </summary>
/// <param name="window">To obtain the style dialog window</param>
/// <returns>current extension style value</returns>
private static int GetCurrentWindowStyle(Window window)
{
var style = PInvoke.GetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
if (style == 0 && Marshal.GetLastPInvokeError() != 0)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return style;
}
#endregion
}

View file

@ -9,6 +9,11 @@
Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable
</system:String> </system:String>
<system:String x:Key="runtimePluginChooseRuntimeExecutable">Please select the {0} executable</system:String> <system:String x:Key="runtimePluginChooseRuntimeExecutable">Please select the {0} executable</system:String>
<system:String x:Key="runtimeExecutableInvalidChooseDownload">
Your selected {0} executable is invalid.
{2}{2}
Click yes if you would like select the {0} executable agian. Click no if you would like to download {1}
</system:String>
<system:String x:Key="runtimePluginUnableToSetExecutablePath">Unable to set {0} executable path, please try from Flow's settings (scroll down to the bottom).</system:String> <system:String x:Key="runtimePluginUnableToSetExecutablePath">Unable to set {0} executable path, please try from Flow's settings (scroll down to the bottom).</system:String>
<system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String> <system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String>
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String> <system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>
@ -39,7 +44,7 @@
<system:String x:Key="GameMode">Game Mode</system:String> <system:String x:Key="GameMode">Game Mode</system:String>
<system:String x:Key="GameModeToolTip">Suspend the use of Hotkeys.</system:String> <system:String x:Key="GameModeToolTip">Suspend the use of Hotkeys.</system:String>
<system:String x:Key="PositionReset">Position Reset</system:String> <system:String x:Key="PositionReset">Position Reset</system:String>
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String> <system:String x:Key="queryTextBoxPlaceholder">Type here to search</system:String>
<!-- Setting General --> <!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Settings</system:String> <system:String x:Key="flowlauncher_settings">Settings</system:String>
@ -72,8 +77,6 @@
<system:String x:Key="LastQueryEmpty">Empty last Query</system:String> <system:String x:Key="LastQueryEmpty">Empty last Query</system:String>
<system:String x:Key="LastQueryActionKeywordPreserved">Preserve Last Action Keyword</system:String> <system:String x:Key="LastQueryActionKeywordPreserved">Preserve Last Action Keyword</system:String>
<system:String x:Key="LastQueryActionKeywordSelected">Select Last Action Keyword</system:String> <system:String x:Key="LastQueryActionKeywordSelected">Select Last Action Keyword</system:String>
<system:String x:Key="KeepMaxResults">Fixed Window Height</system:String>
<system:String x:Key="KeepMaxResultsToolTip">The window height is not adjustable by dragging.</system:String>
<system:String x:Key="maxShowResults">Maximum results shown</system:String> <system:String x:Key="maxShowResults">Maximum results shown</system:String>
<system:String x:Key="maxShowResultsToolTip">You can also quickly adjust this by using CTRL+Plus and CTRL+Minus.</system:String> <system:String x:Key="maxShowResultsToolTip">You can also quickly adjust this by using CTRL+Plus and CTRL+Minus.</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignore hotkeys in fullscreen mode</system:String> <system:String x:Key="ignoreHotkeysOnFullscreen">Ignore hotkeys in fullscreen mode</system:String>
@ -104,6 +107,15 @@
<system:String x:Key="AlwaysPreview">Always Preview</system:String> <system:String x:Key="AlwaysPreview">Always Preview</system:String>
<system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String> <system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String>
<system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String> <system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
<system:String x:Key="searchDelay">Search Delay</system:String>
<system:String x:Key="searchDelayToolTip">Delay for a while to search when typing. This reduces interface jumpiness and result load.</system:String>
<system:String x:Key="searchDelayTime">Default Search Delay Time</system:String>
<system:String x:Key="searchDelayTimeToolTip">Plugin default delay time after which search results appear when typing is stopped.</system:String>
<system:String x:Key="SearchDelayTimeVeryLong">Very long</system:String>
<system:String x:Key="SearchDelayTimeLong">Long</system:String>
<system:String x:Key="SearchDelayTimeNormal">Normal</system:String>
<system:String x:Key="SearchDelayTimeShort">Short</system:String>
<system:String x:Key="SearchDelayTimeVeryShort">Very short</system:String>
<!-- Setting Plugin --> <!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String> <system:String x:Key="searchplugin">Search Plugin</system:String>
@ -120,6 +132,8 @@
<system:String x:Key="currentActionKeywords">Current action keyword</system:String> <system:String x:Key="currentActionKeywords">Current action keyword</system:String>
<system:String x:Key="newActionKeyword">New action keyword</system:String> <system:String x:Key="newActionKeyword">New action keyword</system:String>
<system:String x:Key="actionKeywordsTooltip">Change Action Keywords</system:String> <system:String x:Key="actionKeywordsTooltip">Change Action Keywords</system:String>
<system:String x:Key="pluginSearchDelayTime">Plugin seach delay time</system:String>
<system:String x:Key="pluginSearchDelayTimeTooltip">Change Plugin Seach Delay Time</system:String>
<system:String x:Key="currentPriority">Current Priority</system:String> <system:String x:Key="currentPriority">Current Priority</system:String>
<system:String x:Key="newPriority">New Priority</system:String> <system:String x:Key="newPriority">New Priority</system:String>
<system:String x:Key="priority">Priority</system:String> <system:String x:Key="priority">Priority</system:String>
@ -133,6 +147,9 @@
<system:String x:Key="plugin_uninstall">Uninstall</system:String> <system:String x:Key="plugin_uninstall">Uninstall</system:String>
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String> <system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String> <system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
<system:String x:Key="failedToRemovePluginCacheTitle">Fail to remove plugin cache</system:String>
<system:String x:Key="failedToRemovePluginCacheMessage">Plugins: {0} - Fail to remove plugin cache files, please remove them manually</system:String>
<system:String x:Key="default">Default</system:String>
<!-- Setting Plugin Store --> <!-- Setting Plugin Store -->
<system:String x:Key="pluginStore">Plugin Store</system:String> <system:String x:Key="pluginStore">Plugin Store</system:String>
@ -195,8 +212,20 @@
<system:String x:Key="AnimationSpeedCustom">Custom</system:String> <system:String x:Key="AnimationSpeedCustom">Custom</system:String>
<system:String x:Key="Clock">Clock</system:String> <system:String x:Key="Clock">Clock</system:String>
<system:String x:Key="Date">Date</system:String> <system:String x:Key="Date">Date</system:String>
<system:String x:Key="BackdropType">Backdrop Type</system:String>
<system:String x:Key="BackdropTypeDisabledToolTip">Backdrop supported starting from Windows 11 build 22000 and above</system:String>
<system:String x:Key="BackdropTypesNone">None</system:String>
<system:String x:Key="BackdropTypesAcrylic">Acrylic</system:String>
<system:String x:Key="BackdropTypesMica">Mica</system:String>
<system:String x:Key="BackdropTypesMicaAlt">Mica Alt</system:String>
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String> <system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String> <system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
<system:String x:Key="ShowPlaceholder">Show placeholder</system:String>
<system:String x:Key="ShowPlaceholderTip">Display placeholder when query is empty</system:String>
<system:String x:Key="PlaceholderText">Placeholder text</system:String>
<system:String x:Key="PlaceholderTextTip">Change placeholder text. Input empty will use: {0}</system:String>
<system:String x:Key="KeepMaxResults">Fixed Window Size</system:String>
<system:String x:Key="KeepMaxResultsToolTip">The window size is not adjustable by dragging.</system:String>
<!-- Setting Hotkey --> <!-- Setting Hotkey -->
<system:String x:Key="hotkey">Hotkey</system:String> <system:String x:Key="hotkey">Hotkey</system:String>
@ -296,6 +325,9 @@
<system:String x:Key="logfolder">Log Folder</system:String> <system:String x:Key="logfolder">Log Folder</system:String>
<system:String x:Key="clearlogfolder">Clear Logs</system:String> <system:String x:Key="clearlogfolder">Clear Logs</system:String>
<system:String x:Key="clearlogfolderMessage">Are you sure you want to delete all logs?</system:String> <system:String x:Key="clearlogfolderMessage">Are you sure you want to delete all logs?</system:String>
<system:String x:Key="clearcachefolder">Clear Caches</system:String>
<system:String x:Key="clearcachefolderMessage">Are you sure you want to delete all caches?</system:String>
<system:String x:Key="clearfolderfailMessage">Failed to clear part of folders and files. Please see log file for more information</system:String>
<system:String x:Key="welcomewindow">Wizard</system:String> <system:String x:Key="welcomewindow">Wizard</system:String>
<system:String x:Key="userdatapath">User Data Location</system:String> <system:String x:Key="userdatapath">User Data Location</system:String>
<system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String> <system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String>
@ -337,9 +369,16 @@
<system:String x:Key="cannotFindSpecifiedPlugin">Can't find specified plugin</system:String> <system:String x:Key="cannotFindSpecifiedPlugin">Can't find specified plugin</system:String>
<system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String> <system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String>
<system:String x:Key="newActionKeywordsHasBeenAssigned">This new Action Keyword is already assigned to another plugin, please choose a different one</system:String> <system:String x:Key="newActionKeywordsHasBeenAssigned">This new Action Keyword is already assigned to another plugin, please choose a different one</system:String>
<system:String x:Key="newActionKeywordsSameAsOld">This new Action Keyword is the same as old, please choose a different one</system:String>
<system:String x:Key="success">Success</system:String> <system:String x:Key="success">Success</system:String>
<system:String x:Key="completedSuccessfully">Completed successfully</system:String> <system:String x:Key="completedSuccessfully">Completed successfully</system:String>
<system:String x:Key="actionkeyword_tips">Enter the action keyword you like to use to start the plugin. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords.</system:String> <system:String x:Key="actionkeyword_tips">Enter the action keywords you like to use to start the plugin and use whitespace to divide them. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords.</system:String>
<!-- Search Delay Settings Dialog -->
<system:String x:Key="searchDelayTimeTitle">Search Delay Time Setting</system:String>
<system:String x:Key="searchDelayTime_tips">Select the search delay time you like to use for the plugin. Select "{0}" if you don't want to specify any, and the plugin will use default search delay time.</system:String>
<system:String x:Key="currentSearchDelayTime">Current search delay time</system:String>
<system:String x:Key="newSearchDelayTime">New search delay time</system:String>
<!-- Custom Query Hotkey Dialog --> <!-- Custom Query Hotkey Dialog -->
<system:String x:Key="customeQueryHotkeyTitle">Custom Query Hotkey</system:String> <system:String x:Key="customeQueryHotkeyTitle">Custom Query Hotkey</system:String>

View file

@ -17,20 +17,20 @@
AllowDrop="True" AllowDrop="True"
AllowsTransparency="True" AllowsTransparency="True"
Background="Transparent" Background="Transparent"
Closed="OnClosed"
Closing="OnClosing" Closing="OnClosing"
Deactivated="OnDeactivated" Deactivated="OnDeactivated"
Icon="Images/app.png" Icon="Images/app.png"
SourceInitialized="OnSourceInitialized"
Initialized="OnInitialized"
Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Loaded="OnLoaded" Loaded="OnLoaded"
LocationChanged="OnLocationChanged" LocationChanged="OnLocationChanged"
Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
PreviewKeyDown="OnKeyDown" PreviewKeyDown="OnKeyDown"
PreviewKeyUp="OnKeyUp" PreviewKeyUp="OnKeyUp"
PreviewMouseMove="OnPreviewMouseMove"
ResizeMode="CanResize" ResizeMode="CanResize"
ShowInTaskbar="False" ShowInTaskbar="False"
SizeToContent="Height" SizeToContent="Height"
SourceInitialized="OnSourceInitialized"
Topmost="True" Topmost="True"
Visibility="{Binding MainWindowVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding MainWindowVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
WindowStartupLocation="Manual" WindowStartupLocation="Manual"
@ -215,9 +215,17 @@
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}"> <Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Grid> <Grid x:Name="QueryBoxArea">
<Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}"> <Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}">
<Grid> <Grid>
<TextBox
x:Name="QueryTextPlaceholderBox"
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="False"
Style="{DynamicResource QuerySuggestionBoxStyle}"
Text="{Binding PlaceholderText, Mode=OneWay}"
Visibility="Collapsed" />
<TextBox <TextBox
x:Name="QueryTextSuggestionBox" x:Name="QueryTextSuggestionBox"
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
@ -240,14 +248,15 @@
FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}" InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}" InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
PreviewDragOver="OnPreviewDragOver" PreviewDragOver="QueryTextBox_OnPreviewDragOver"
PreviewKeyUp="QueryTextBox_KeyUp" PreviewKeyUp="QueryTextBox_KeyUp"
Style="{DynamicResource QueryBoxStyle}" Style="{DynamicResource QueryBoxStyle}"
Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding QueryText, Mode=OneWay}"
TextChanged="QueryTextBox_TextChanged1"
Visibility="Visible" Visibility="Visible"
WindowChrome.IsHitTestVisibleInChrome="True"> WindowChrome.IsHitTestVisibleInChrome="True">
<TextBox.CommandBindings> <TextBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy" Executed="OnCopy" /> <CommandBinding Command="ApplicationCommands.Copy" Executed="QueryTextBox_OnCopy" />
</TextBox.CommandBindings> </TextBox.CommandBindings>
<TextBox.ContextMenu> <TextBox.ContextMenu>
<ContextMenu MinWidth="160"> <ContextMenu MinWidth="160">
@ -289,7 +298,9 @@
<StackPanel <StackPanel
x:Name="ClockPanel" x:Name="ClockPanel"
IsHitTestVisible="False" IsHitTestVisible="False"
Style="{DynamicResource ClockPanel}"> Opacity="{Binding ClockPanelOpacity}"
Style="{DynamicResource ClockPanel}"
Visibility="{Binding ClockPanelVisibility}">
<TextBlock <TextBlock
x:Name="ClockBox" x:Name="ClockBox"
Style="{DynamicResource ClockBox}" Style="{DynamicResource ClockBox}"
@ -317,6 +328,7 @@
Name="SearchIcon" Name="SearchIcon"
Margin="0" Margin="0"
Data="{DynamicResource SearchIconImg}" Data="{DynamicResource SearchIconImg}"
Opacity="{Binding SearchIconOpacity}"
Stretch="Fill" Stretch="Fill"
Style="{DynamicResource SearchIconStyle}" Style="{DynamicResource SearchIconStyle}"
Visibility="{Binding SearchIconVisibility}" /> Visibility="{Binding SearchIconVisibility}" />
@ -338,7 +350,7 @@
Y2="0" /> Y2="0" />
</Grid> </Grid>
<Grid ClipToBounds="True"> <Grid x:Name="MiddleSeparatorArea" ClipToBounds="True">
<ContentControl> <ContentControl>
<ContentControl.Style> <ContentControl.Style>
<Style TargetType="ContentControl"> <Style TargetType="ContentControl">
@ -377,8 +389,8 @@
Style="{DynamicResource SeparatorStyle}" /> Style="{DynamicResource SeparatorStyle}" />
</ContentControl> </ContentControl>
</Grid> </Grid>
<Border Style="{DynamicResource WindowRadius}"> <Border x:Name="ResultPreviewAreaBoarder" Style="{DynamicResource WindowRadius}">
<Border.Clip> <Border.Clip>
<MultiBinding Converter="{StaticResource BorderClipConverter}"> <MultiBinding Converter="{StaticResource BorderClipConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" /> <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
@ -386,12 +398,14 @@
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" /> <Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
</MultiBinding> </MultiBinding>
</Border.Clip> </Border.Clip>
<Grid>
<Grid x:Name="ResultPreviewArea">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="80" /> <ColumnDefinition Width="*" MinWidth="80" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="0.85*" MinWidth="244" /> <ColumnDefinition Width="0.85*" MinWidth="244" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel <StackPanel
x:Name="ResultArea" x:Name="ResultArea"
Grid.Column="0" Grid.Column="0"
@ -418,7 +432,9 @@
RightClickResultCommand="{Binding RightClickResultCommand}" /> RightClickResultCommand="{Binding RightClickResultCommand}" />
</ContentControl> </ContentControl>
</StackPanel> </StackPanel>
<GridSplitter <GridSplitter
x:Name="PreviewMiddleSeparator"
Grid.Column="1" Grid.Column="1"
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@ -432,6 +448,7 @@
</ControlTemplate> </ControlTemplate>
</GridSplitter.Template> </GridSplitter.Template>
</GridSplitter> </GridSplitter>
<Grid <Grid
x:Name="Preview" x:Name="Preview"
Grid.Column="2" Grid.Column="2"
@ -441,7 +458,7 @@
<Border <Border
MinHeight="380" MinHeight="380"
d:DataContext="{d:DesignInstance vm:ResultViewModel}" d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}" DataContext="{Binding PreviewSelectedItem, Mode=OneWay}"
Visibility="{Binding ShowDefaultPreview}"> Visibility="{Binding ShowDefaultPreview}">
<Grid <Grid
Margin="0 0 10 5" Margin="0 0 10 5"
@ -518,7 +535,7 @@
MaxHeight="{Binding ElementName=ResultListBox, Path=ActualHeight}" MaxHeight="{Binding ElementName=ResultListBox, Path=ActualHeight}"
Padding="0 0 10 10" Padding="0 0 10 10"
d:DataContext="{d:DesignInstance vm:ResultViewModel}" d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}" DataContext="{Binding PreviewSelectedItem, Mode=OneWay}"
Visibility="{Binding ShowCustomizedPreview}"> Visibility="{Binding ShowCustomizedPreview}">
<ContentControl Content="{Binding Result.PreviewPanel.Value}" /> <ContentControl Content="{Binding Result.PreviewPanel.Value}" />
</Border> </Border>

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
@ -12,14 +11,14 @@ namespace Flow.Launcher
{ {
public partial class Msg : Window public partial class Msg : Window
{ {
Storyboard fadeOutStoryboard = new Storyboard(); private readonly Storyboard fadeOutStoryboard = new();
private bool closing; private bool closing;
public Msg() public Msg()
{ {
InitializeComponent(); InitializeComponent();
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dipWorkingArea = WindowsInteropHelper.TransformPixelsToDIP(this, var dipWorkingArea = Win32Helper.TransformPixelsToDIP(this,
screen.WorkingArea.Width, screen.WorkingArea.Width,
screen.WorkingArea.Height); screen.WorkingArea.Height);
Left = dipWorkingArea.X - Width; Left = dipWorkingArea.X - Width;
@ -82,13 +81,13 @@ namespace Flow.Launcher
Show(); Show();
await Dispatcher.InvokeAsync(async () => await Dispatcher.InvokeAsync(async () =>
{ {
if (!closing) if (!closing)
{ {
closing = true; closing = true;
await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin); await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin);
} }
}); });
} }
} }
} }

View file

@ -1,20 +0,0 @@
DwmSetWindowAttribute
DwmExtendFrameIntoClientArea
SystemParametersInfo
SetForegroundWindow
GetWindowLong
SetWindowLong
GetForegroundWindow
GetDesktopWindow
GetShellWindow
GetWindowRect
GetClassName
FindWindowEx
WINDOW_STYLE
WM_ENTERSIZEMOVE
WM_EXITSIZEMOVE
SetLastError
WINDOW_EX_STYLE

View file

@ -1,46 +1,48 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Squirrel; using Squirrel;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper; using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Plugin.SharedCommands;
using System.Threading;
using System.IO;
using Flow.Launcher.Infrastructure.Http;
using JetBrains.Annotations;
using System.Runtime.CompilerServices;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.Storage;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Collections.Specialized;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Flow.Launcher.Core.Resource;
namespace Flow.Launcher namespace Flow.Launcher
{ {
public class PublicAPIInstance : IPublicAPI public class PublicAPIInstance : IPublicAPI
{ {
private readonly Settings _settings; private readonly Settings _settings;
private readonly Internationalization _translater;
private readonly MainViewModel _mainVM; private readonly MainViewModel _mainVM;
#region Constructor #region Constructor
public PublicAPIInstance(Settings settings, MainViewModel mainVM) public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
{ {
_settings = settings; _settings = settings;
_translater = translater;
_mainVM = mainVM; _mainVM = mainVM;
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback; GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
@ -153,17 +155,17 @@ namespace Flow.Launcher
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key); public string GetTranslation(string key) => _translater.GetTranslation(key);
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList(); public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
public MatchResult FuzzySearch(string query, string stringToCompare) => public MatchResult FuzzySearch(string query, string stringToCompare) =>
StringMatcher.FuzzySearch(query, stringToCompare); StringMatcher.FuzzySearch(query, stringToCompare);
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url); public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url, token);
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) => public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
Http.GetStreamAsync(url); Http.GetStreamAsync(url, token);
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token); CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
@ -190,7 +192,7 @@ namespace Flow.Launcher
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new(); private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
public object RemovePluginSettings(string assemblyName) public void RemovePluginSettings(string assemblyName)
{ {
foreach (var keyValuePair in _pluginJsonStorages) foreach (var keyValuePair in _pluginJsonStorages)
{ {
@ -200,11 +202,8 @@ namespace Flow.Launcher
if (name == assemblyName) if (name == assemblyName)
{ {
_pluginJsonStorages.Remove(key, out var pluginJsonStorage); _pluginJsonStorages.Remove(key, out var pluginJsonStorage);
return pluginJsonStorage;
} }
} }
return null;
} }
/// <summary> /// <summary>
@ -329,7 +328,6 @@ namespace Flow.Launcher
return _mainVM.GameModeStatus; return _mainVM.GameModeStatus;
} }
private readonly List<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new(); private readonly List<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new();
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback); public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback);

View file

@ -20,21 +20,21 @@
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" /> <Setter Property="CornerRadius" Value="5" />
<Setter Property="MinHeight" Value="68" /> <Setter Property="MinHeight" Value="68" />
<Setter Property="Padding" Value="0,15,0,15" /> <Setter Property="Padding" Value="0 15 0 15" />
<Setter Property="Margin" Value="0,4,0,0" /> <Setter Property="Margin" Value="0 4 0 0" />
<Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="SnapsToDevicePixels" Value="True" />
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="Inside"> <DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="Inside">
<Setter Property="BorderThickness" Value="0,1,0,0" /> <Setter Property="BorderThickness" Value="0 1 0 0" />
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" /> <Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="InsideFit"> <DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="InsideFit">
<Setter Property="BorderThickness" Value="0,1,0,0" /> <Setter Property="BorderThickness" Value="0 1 0 0" />
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" /> <Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="Padding" Value="38,0,26,0" /> <Setter Property="Padding" Value="35 0 26 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</DataTrigger> </DataTrigger>
@ -73,7 +73,7 @@
<ContentControl <ContentControl
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Margin="0,0,16,0" Margin="0 0 16 0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Content="{TemplateBinding Content}" /> Content="{TemplateBinding Content}" />
@ -91,7 +91,7 @@
<TextBlock.Style> <TextBlock.Style>
<Style TargetType="{x:Type TextBlock}"> <Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color05B}" /> <Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="Margin" Value="0,0,0,0" /> <Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
</Style> </Style>
@ -107,8 +107,8 @@
</Style.Triggers> </Style.Triggers>
<Setter Property="Foreground" Value="{DynamicResource Color04B}" /> <Setter Property="Foreground" Value="{DynamicResource Color04B}" />
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="0,0,0,0" /> <Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="Padding" Value="0,0,24,0" /> <Setter Property="Padding" Value="0 0 24 0" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" /> <Setter Property="TextWrapping" Value="WrapWithOverflow" />
</Style> </Style>
</TextBlock.Style> </TextBlock.Style>
@ -120,11 +120,11 @@
<Style TargetType="{x:Type TextBlock}"> <Style TargetType="{x:Type TextBlock}">
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ItemIcon, Path=Text}" Value="{x:Static sys:String.Empty}"> <DataTrigger Binding="{Binding ElementName=ItemIcon, Path=Text}" Value="{x:Static sys:String.Empty}">
<Setter Property="Margin" Value="24,0,0,0" /> <Setter Property="Margin" Value="24 0 0 0" />
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
<Setter Property="Grid.Column" Value="0" /> <Setter Property="Grid.Column" Value="0" />
<Setter Property="Margin" Value="24,0,16,0" /> <Setter Property="Margin" Value="24 0 16 0" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="20" /> <Setter Property="FontSize" Value="20" />
<Setter Property="FontFamily" Value="/Resources/#Segoe Fluent Icons" /> <Setter Property="FontFamily" Value="/Resources/#Segoe Fluent Icons" />

View file

@ -1,14 +1,16 @@
<UserControl x:Class="Flow.Launcher.Resources.Controls.InstalledPluginDisplay" <UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x:Class="Flow.Launcher.Resources.Controls.InstalledPluginDisplay"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls" xmlns:ui="http://schemas.modernwpf.com/2019"
mc:Ignorable="d" xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel"
d:DataContext="{d:DesignInstance viewModel:PluginViewModel}" d:DataContext="{d:DesignInstance viewModel:PluginViewModel}"
d:DesignHeight="300" d:DesignWidth="300"> d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Expander <Expander
Padding="0" Padding="0"
BorderThickness="0" BorderThickness="0"
@ -28,7 +30,7 @@
Grid.Column="0" Grid.Column="0"
Width="32" Width="32"
Height="32" Height="32"
Source="{Binding Image, IsAsync=True}" /> Source="{Binding Image, Mode=OneWay, IsAsync=True}" />
<StackPanel Grid.Column="1" Margin="16 0 14 0"> <StackPanel Grid.Column="1" Margin="16 0 14 0">
<TextBlock <TextBlock
Foreground="{DynamicResource Color05B}" Foreground="{DynamicResource Color05B}"
@ -37,12 +39,15 @@
ToolTip="{Binding PluginPair.Metadata.Version}" /> ToolTip="{Binding PluginPair.Metadata.Version}" />
<TextBlock <TextBlock
Margin="0 2 0 0" Margin="0 2 0 0"
Foreground="{DynamicResource Color04B}"
FontSize="12" FontSize="12"
Foreground="{DynamicResource Color04B}"
Text="{Binding PluginPair.Metadata.Description}" Text="{Binding PluginPair.Metadata.Description}"
TextWrapping="WrapWithOverflow" /> TextWrapping="WrapWithOverflow" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel
Grid.Column="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock <TextBlock
Margin="0 0 8 0" Margin="0 0 8 0"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -70,9 +75,7 @@
<Setter Property="FontWeight" Value="DemiBold" /> <Setter Property="FontWeight" Value="DemiBold" />
<Setter Property="Foreground" Value="{DynamicResource Color05B}" /> <Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Style.Triggers> <Style.Triggers>
<DataTrigger <DataTrigger Binding="{Binding ElementName=PriorityButton, UpdateSourceTrigger=PropertyChanged, Path=Content}" Value="0">
Binding="{Binding ElementName=PriorityButton, UpdateSourceTrigger=PropertyChanged, Path=Content}"
Value="0">
<Setter Property="Foreground" Value="{DynamicResource Color08B}" /> <Setter Property="Foreground" Value="{DynamicResource Color08B}" />
<Setter Property="FontWeight" Value="Normal" /> <Setter Property="FontWeight" Value="Normal" />
</DataTrigger> </DataTrigger>
@ -95,10 +98,12 @@
<StackPanel> <StackPanel>
<ContentControl Content="{Binding BottomPart1}" /> <ContentControl Content="{Binding BottomPart1}" />
<ContentControl Content="{Binding BottomPart2}" />
<Border <Border
BorderThickness="0 1 0 0" Background="{DynamicResource Color00B}"
BorderBrush="{DynamicResource Color03B}" BorderBrush="{DynamicResource Color03B}"
Background="{DynamicResource Color00B}"> BorderThickness="0 1 0 0">
<Border.Style> <Border.Style>
<Style TargetType="Border"> <Style TargetType="Border">
<Style.Triggers> <Style.Triggers>
@ -115,7 +120,7 @@
Content="{Binding SettingControl}" /> Content="{Binding SettingControl}" />
</Border> </Border>
<ContentControl Content="{Binding BottomPart2}" /> <ContentControl Content="{Binding BottomPart3}" />
</StackPanel> </StackPanel>
</Expander> </Expander>
</UserControl> </UserControl>

View file

@ -11,30 +11,33 @@
mc:Ignorable="d"> mc:Ignorable="d">
<Border <Border
Width="Auto" Width="Auto"
Height="52" Height="Auto"
Margin="0" Margin="0"
Padding="0" Padding="0"
BorderThickness="0 1 0 0" BorderThickness="0 1 0 0"
CornerRadius="0" CornerRadius="0"
Style="{DynamicResource SettingGroupBox}" Style="{DynamicResource SettingGroupBox}"
Visibility="{Binding ActionKeywordsVisibility}"> Visibility="{Binding ActionKeywordsVisibility}">
<DockPanel Margin="22 0 18 0" VerticalAlignment="Center"> <DockPanel Margin="{StaticResource SettingPanelMargin}">
<TextBlock <TextBlock
Margin="48 0 10 0" Margin="{StaticResource SettingPanelItemRightMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DockPanel.Dock="Left" DockPanel.Dock="Left"
Style="{StaticResource Glyph}"> Style="{StaticResource Glyph}">
&#xe819; &#xe819;
</TextBlock> </TextBlock>
<TextBlock <TextBlock
Margin="{StaticResource SettingPanelItemRightMargin}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
DockPanel.Dock="Left" DockPanel.Dock="Left"
Style="{DynamicResource SettingTitleLabel}" Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource actionKeywords}" /> Text="{DynamicResource actionKeywords}" />
<!-- Here Margin="0 -4.5 0 -4.5" is to remove redundant top bottom margin from Margin="{StaticResource SettingPanelMargin}" -->
<Button <Button
Width="100" Width="100"
Height="34" Margin="0 -4.5 0 -4.5"
Margin="5 0 0 0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Command="{Binding SetActionKeywordsCommand}" Command="{Binding SetActionKeywordsCommand}"
Content="{Binding ActionKeywordsText}" Content="{Binding ActionKeywordsText}"

View file

@ -0,0 +1,49 @@
<UserControl
x:Class="Flow.Launcher.Resources.Controls.InstalledPluginSearchDelay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel"
d:DataContext="{d:DesignInstance viewModel:PluginViewModel}"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Border
Width="Auto"
Height="Auto"
Margin="0"
Padding="0"
BorderThickness="0 1 0 0"
CornerRadius="0"
Style="{DynamicResource SettingGroupBox}">
<DockPanel Margin="{StaticResource SettingPanelMargin}">
<TextBlock
Margin="{StaticResource SettingPanelItemRightMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Style="{StaticResource Glyph}">
&#xE916;
</TextBlock>
<TextBlock
Margin="{StaticResource SettingPanelItemRightMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource pluginSearchDelayTime}" />
<!-- Here Margin="0 -4.5 0 -4.5" is to remove redundant top bottom margin from Margin="{StaticResource SettingPanelMargin}" -->
<Button
Width="100"
Margin="0 -4.5 0 -4.5"
HorizontalAlignment="Right"
Command="{Binding SetSearchDelayTimeCommand}"
Content="{Binding SearchDelayTimeText}"
Cursor="Hand"
DockPanel.Dock="Right"
FontWeight="Bold"
ToolTip="{DynamicResource pluginSearchDelayTimeTooltip}" />
</DockPanel>
</Border>
</UserControl>

View file

@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace Flow.Launcher.Resources.Controls;
public partial class InstalledPluginSearchDelay : UserControl
{
public InstalledPluginSearchDelay()
{
InitializeComponent();
}
}

View file

@ -5585,4 +5585,31 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<!-- Settings Panel -->
<Thickness x:Key="SettingPanelMargin">70,13.5,18,13.5</Thickness>
<Thickness x:Key="SettingPanelItemLeftMargin">9,0,0,0</Thickness>
<Thickness x:Key="SettingPanelItemRightMargin">0,0,9,0</Thickness>
<Thickness x:Key="SettingPanelItemTopBottomMargin">0,4.5,0,4.5</Thickness>
<Thickness x:Key="SettingPanelItemLeftTopBottomMargin">9,4.5,0,4.5</Thickness>
<Thickness x:Key="SettingPanelItemRightTopBottomMargin">0,4.5,9,4.5</Thickness>
<Style x:Key="SettingPanelSeparatorStyle" TargetType="Separator">
<Setter Property="Margin" Value="-70 13.5 -18 13.5" />
<Setter Property="Height" Value="1" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Background" Value="{DynamicResource Color03B}" />
</Style>
<system:Double x:Key="SettingPanelTextBoxMinWidth">180</system:Double>
<system:Double x:Key="SettingPanelPathTextBoxWidth">240</system:Double>
<system:Double x:Key="SettingPanelAreaTextBoxMinHeight">150</system:Double>
<Style x:Key="SettingPanelTextBlockDescriptionStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="0 2 0 0" />
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
</Style>
</ResourceDictionary> </ResourceDictionary>

View file

@ -7,17 +7,17 @@
xmlns:sys="clr-namespace:System;assembly=mscorlib"> xmlns:sys="clr-namespace:System;assembly=mscorlib">
<SolidColorBrush x:Key="SystemThemeBorder" Color="#3f3f3f" /> <SolidColorBrush x:Key="SystemThemeBorder" Color="#3f3f3f" />
<SolidColorBrush x:Key="QuerySuggestionBoxForeground" Color="#4d4d4d" /> <SolidColorBrush x:Key="QuerySuggestionBoxForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="QuerySelectionBrush" Color="#4a5459" /> <SolidColorBrush x:Key="QuerySelectionBrush" Color="#4a5459" />
<SolidColorBrush x:Key="SubTitleForeground" Color="#7b7b7b" /> <SolidColorBrush x:Key="SubTitleForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="SubTitleSelectedForeground" Color="#7b7b7b" /> <SolidColorBrush x:Key="SubTitleSelectedForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="SearchIconForeground" Color="#4d4d4d" /> <SolidColorBrush x:Key="SearchIconForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="SeparatorForeground" Color="#14ffffff" /> <SolidColorBrush x:Key="SeparatorForeground" Color="#24FFFFFF" />
<SolidColorBrush x:Key="ThumbColor" Color="#9a9a9a" /> <SolidColorBrush x:Key="ThumbColor" Color="#9a9a9a" />
<SolidColorBrush x:Key="InlineHighlight" Color="#0078d7" /> <SolidColorBrush x:Key="InlineHighlight" Color="#0078d7" />
<SolidColorBrush x:Key="HotkeyForeground" Color="#7b7b7b" /> <SolidColorBrush x:Key="HotkeyForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="HotkeySelectedForeground" Color="#7b7b7b" /> <SolidColorBrush x:Key="HotkeySelectedForeground" Color="#33FFFFFF" />
<SolidColorBrush x:Key="ClockDateForeground" Color="#4d4d4d" /> <SolidColorBrush x:Key="ClockDateForeground" Color="#26FFFFFF" />
<Color x:Key="ItemSelectedBackgroundColorBrush">#198F8F8F</Color> <Color x:Key="ItemSelectedBackgroundColorBrush">#198F8F8F</Color>
@ -58,6 +58,9 @@
<SolidColorBrush x:Key="BasicSeparatorColor" Color="#cecece" /> <SolidColorBrush x:Key="BasicSeparatorColor" Color="#cecece" />
<SolidColorBrush x:Key="BasicLineColor" Color="#cecece" /> <SolidColorBrush x:Key="BasicLineColor" Color="#cecece" />
<SolidColorBrush x:Key="NewHotkeyForeground" Color="#44FFFFFF" />
<SolidColorBrush x:Key="BasicHotkeyBGColor" Color="#13FFFFFF" />
<SolidColorBrush x:Key="ThemeHoverButton" Color="#3c3c3c" /> <SolidColorBrush x:Key="ThemeHoverButton" Color="#3c3c3c" />
<SolidColorBrush x:Key="PopuBGColor" Color="#202020" /> <SolidColorBrush x:Key="PopuBGColor" Color="#202020" />
<SolidColorBrush x:Key="PopupBGColor" Color="#202020" /> <SolidColorBrush x:Key="PopupBGColor" Color="#202020" />
@ -116,6 +119,8 @@
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" /> <SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
<SolidColorBrush x:Key="InfoBarBD" Color="#19000000" /> <SolidColorBrush x:Key="InfoBarBD" Color="#19000000" />
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
<SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" /> <SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" />
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#3f3f3f" /> <SolidColorBrush x:Key="ButtonInsideBorder" Color="#3f3f3f" />
<SolidColorBrush x:Key="ButtonBackgroundColor" Color="#373737" /> <SolidColorBrush x:Key="ButtonBackgroundColor" Color="#373737" />

View file

@ -7,18 +7,18 @@
xmlns:sys="clr-namespace:System;assembly=mscorlib"> xmlns:sys="clr-namespace:System;assembly=mscorlib">
<SolidColorBrush x:Key="SystemThemeBorder" Color="#aaaaaa" /> <SolidColorBrush x:Key="SystemThemeBorder" Color="#aaaaaa" />
<SolidColorBrush x:Key="QuerySuggestionBoxForeground" Color="#d8d2d0" /> <SolidColorBrush x:Key="QuerySuggestionBoxForeground" Color="#51000000" />
<SolidColorBrush x:Key="QuerySelectionBrush" Color="#0a68d8" /> <SolidColorBrush x:Key="QuerySelectionBrush" Color="#0a68d8" />
<SolidColorBrush x:Key="SubTitleForeground" Color="#818181" /> <SolidColorBrush x:Key="SubTitleForeground" Color="#818181" />
<SolidColorBrush x:Key="SubTitleSelectedForeground" Color="#72767d" /> <SolidColorBrush x:Key="SubTitleSelectedForeground" Color="#72767d" />
<SolidColorBrush x:Key="SearchIconForeground" Color="#d3d3d3" /> <SolidColorBrush x:Key="SearchIconForeground" Color="#20000000" />
<SolidColorBrush x:Key="SeparatorForeground" Color="#14000000" /> <SolidColorBrush x:Key="SeparatorForeground" Color="#20000000" />
<SolidColorBrush x:Key="ThumbColor" Color="#868686" /> <SolidColorBrush x:Key="ThumbColor" Color="#868686" />
<SolidColorBrush x:Key="InlineHighlight" Color="#0078d7" /> <SolidColorBrush x:Key="InlineHighlight" Color="#0078d7" />
<SolidColorBrush x:Key="HotkeyForeground" Color="#acacac" /> <SolidColorBrush x:Key="HotkeyForeground" Color="#51000000" />
<SolidColorBrush x:Key="HotkeySelectedForeground" Color="#acacac" /> <SolidColorBrush x:Key="HotkeySelectedForeground" Color="#51000000" />
<SolidColorBrush x:Key="ClockDateForeground" Color="#d3d3d3" /> <SolidColorBrush x:Key="ClockDateForeground" Color="#44000000" />
<Color x:Key="ItemSelectedBackgroundColorBrush">#198F8F8F</Color> <Color x:Key="ItemSelectedBackgroundColorBrush">#0C000000</Color>
<SolidColorBrush x:Key="Color00B" Color="#fbfbfb" /> <SolidColorBrush x:Key="Color00B" Color="#fbfbfb" />
<SolidColorBrush x:Key="Color01B" Color="#f3f3f3" /> <SolidColorBrush x:Key="Color01B" Color="#f3f3f3" />
@ -51,6 +51,9 @@
<SolidColorBrush x:Key="PluginInfoColor" Color="#8f8f8f" /> <SolidColorBrush x:Key="PluginInfoColor" Color="#8f8f8f" />
<SolidColorBrush x:Key="BasicLabelColor" Color="#1b1b1b" /> <SolidColorBrush x:Key="BasicLabelColor" Color="#1b1b1b" />
<SolidColorBrush x:Key="NewHotkeyForeground" Color="#5E000000" />
<SolidColorBrush x:Key="BasicHotkeyBGColor" Color="#0A000000" />
<SolidColorBrush x:Key="ThemeHoverButton" Color="#f6f6f6" /> <SolidColorBrush x:Key="ThemeHoverButton" Color="#f6f6f6" />
<!-- Typo --> <!-- Typo -->
<SolidColorBrush x:Key="PopuBGColor" Color="#ffffff" /> <SolidColorBrush x:Key="PopuBGColor" Color="#ffffff" />
@ -107,6 +110,7 @@
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#FFF4CE" /> <SolidColorBrush x:Key="InfoBarWarningBG" Color="#FFF4CE" />
<SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" /> <SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" />
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
<SolidColorBrush x:Key="ButtonOutBorder" Color="#e5e5e5" /> <SolidColorBrush x:Key="ButtonOutBorder" Color="#e5e5e5" />
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#d3d3d3" /> <SolidColorBrush x:Key="ButtonInsideBorder" Color="#d3d3d3" />

View file

@ -110,8 +110,8 @@
<Border.Background> <Border.Background>
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1"> <LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
<LinearGradientBrush.GradientStops> <LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#1494df" /> <GradientStop Offset="0.0" Color="#2A4D8C" />
<GradientStop Offset="1.0" Color="#1073bd" /> <GradientStop Offset="1.0" Color="#1E3160" />
</LinearGradientBrush.GradientStops> </LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Border.Background> </Border.Background>

View file

@ -60,7 +60,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Border Width="450" Style="{DynamicResource WindowBorderStyle}"> <Border Width="450" Style="{DynamicResource PreviewWindowBorderStyle}">
<Border Style="{DynamicResource WindowRadius}"> <Border Style="{DynamicResource WindowRadius}">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>

View file

@ -58,10 +58,10 @@
<Border Grid.Row="0" HorizontalAlignment="Stretch"> <Border Grid.Row="0" HorizontalAlignment="Stretch">
<Border.Background> <Border.Background>
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1"> <LinearGradientBrush StartPoint="0 1" EndPoint="0 0">
<LinearGradientBrush.GradientStops> <LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#7b83eb" /> <GradientStop Offset="0.0" Color="#E5F3F7" />
<GradientStop Offset="1.0" Color="#555dc0" /> <GradientStop Offset="1.0" Color="#FAFAFD" />
</LinearGradientBrush.GradientStops> </LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Border.Background> </Border.Background>

View file

@ -65,23 +65,24 @@
Margin="0 0 10 0" Margin="0 0 10 0"
VerticalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowOpenResultHotkey}"> Visibility="{Binding ShowOpenResultHotkey}">
<TextBlock <Border x:Name="HotkeyBG" Style="{DynamicResource ItemHotkeyBGStyle}">
x:Name="Hotkey" <Border.Visibility>
Margin="12 0 12 0"
Padding="0 0 0 0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{DynamicResource ItemHotkeyStyle}">
<TextBlock.Visibility>
<Binding Converter="{StaticResource ResourceKey=OpenResultHotkeyVisibilityConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" /> <Binding Converter="{StaticResource ResourceKey=OpenResultHotkeyVisibilityConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" />
</TextBlock.Visibility> </Border.Visibility>
<TextBlock.Text> <TextBlock
<MultiBinding StringFormat="{}{0}+{1}"> x:Name="Hotkey"
<Binding Path="OpenResultModifiers" /> Padding="0 0 0 0"
<Binding Converter="{StaticResource ResourceKey=OrdinalConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" /> HorizontalAlignment="Right"
</MultiBinding> VerticalAlignment="Center"
</TextBlock.Text> Style="{DynamicResource ItemHotkeyStyle}">
</TextBlock> <TextBlock.Text>
<MultiBinding StringFormat="{}{0}+{1}">
<Binding Path="OpenResultModifiers" />
<Binding Converter="{StaticResource ResourceKey=OrdinalConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</StackPanel> </StackPanel>
<Grid Grid.Column="0"> <Grid Grid.Column="0">
@ -214,6 +215,7 @@
<Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" /> <Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
<Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" /> <Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
<Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" /> <Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" />
<Setter TargetName="HotkeyBG" Property="Style" Value="{DynamicResource ItemHotkeyBGSelectedStyle}" />
<Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" /> <Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" />
</DataTrigger> </DataTrigger>
</DataTemplate.Triggers> </DataTemplate.Triggers>

View file

@ -0,0 +1,138 @@
<Window
x:Class="Flow.Launcher.SearchDelayTimeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{DynamicResource searchDelayTimeTitle}"
Width="450"
Background="{DynamicResource PopuBGColor}"
Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png"
Loaded="SearchDelayTimeWindow_OnLoaded"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterScreen">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<Grid>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="4"
Click="BtnCancel_OnClick"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
</Grid>
</StackPanel>
<StackPanel Margin="26 12 26 0">
<StackPanel Grid.Row="0" Margin="0 0 0 12">
<TextBlock
Grid.Column="0"
Margin="0 0 0 0"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource searchDelayTimeTitle}"
TextAlignment="Left" />
</StackPanel>
<StackPanel>
<TextBlock
x:Name="tbSearchDelayTimeTips"
FontSize="14"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
</StackPanel>
<StackPanel Margin="0 18 0 0" Orientation="Horizontal">
<TextBlock
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource currentSearchDelayTime}" />
<TextBlock
x:Name="tbOldSearchDelayTime"
Grid.Row="0"
Grid.Column="1"
Margin="14 10 10 10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}" />
</StackPanel>
<StackPanel Margin="0 0 0 10" Orientation="Horizontal">
<TextBlock
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource newSearchDelayTime}" />
<ComboBox
x:Name="cbDelay"
MaxWidth="200"
Margin="10 10 15 10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="Display"
SelectedValuePath="Value" />
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
<Border
Grid.Row="1"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0 1 0 0">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
Width="145"
Height="30"
Margin="10 0 5 0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnDone"
Width="145"
Height="30"
Margin="5 0 10 0"
Click="btnDone_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
</Button>
</StackPanel>
</Border>
</Grid>
</Window>

View file

@ -0,0 +1,57 @@
using System.Linq;
using System.Windows;
using Flow.Launcher.Plugin;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
using static Flow.Launcher.SettingPages.ViewModels.SettingsPaneGeneralViewModel;
namespace Flow.Launcher;
public partial class SearchDelayTimeWindow : Window
{
private readonly PluginViewModel _pluginViewModel;
public SearchDelayTimeWindow(PluginViewModel pluginViewModel)
{
InitializeComponent();
_pluginViewModel = pluginViewModel;
}
private void SearchDelayTimeWindow_OnLoaded(object sender, RoutedEventArgs e)
{
tbSearchDelayTimeTips.Text = string.Format(App.API.GetTranslation("searchDelayTime_tips"),
App.API.GetTranslation("default"));
tbOldSearchDelayTime.Text = _pluginViewModel.SearchDelayTimeText;
var searchDelayTimes = DropdownDataGeneric<SearchDelayTime>.GetValues<SearchDelayTimeData>("SearchDelayTime");
SearchDelayTimeData selected = null;
// Because default value is SearchDelayTime.VeryShort, we need to get selected value before adding default value
if (_pluginViewModel.PluginSearchDelayTime != null)
{
selected = searchDelayTimes.FirstOrDefault(x => x.Value == _pluginViewModel.PluginSearchDelayTime);
}
// Add default value to the beginning of the list
// When _pluginViewModel.PluginSearchDelayTime equals null, we will select this
searchDelayTimes.Insert(0, new SearchDelayTimeData { Display = App.API.GetTranslation("default"), LocalizationKey = "default" });
selected ??= searchDelayTimes.FirstOrDefault();
cbDelay.ItemsSource = searchDelayTimes;
cbDelay.SelectedItem = selected;
cbDelay.Focus();
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private void btnDone_OnClick(object sender, RoutedEventArgs _)
{
// Update search delay time
var selected = cbDelay.SelectedItem as SearchDelayTimeData;
SearchDelayTime? changedValue = selected?.LocalizationKey != "default" ? selected.Value : null;
_pluginViewModel.PluginSearchDelayTime = changedValue;
// Update search delay time text and close window
_pluginViewModel.OnSearchDelayTimeChanged();
Close();
}
}

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.SettingPages.ViewModels; namespace Flow.Launcher.SettingPages.ViewModels;
@ -9,7 +8,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
{ {
public string Display { get; set; } public string Display { get; set; }
public TValue Value { get; private init; } public TValue Value { get; private init; }
private string LocalizationKey { get; init; } public string LocalizationKey { get; set; }
public static List<TR> GetValues<TR>(string keyPrefix) where TR : DropdownDataGeneric<TValue>, new() public static List<TR> GetValues<TR>(string keyPrefix) where TR : DropdownDataGeneric<TValue>, new()
{ {
@ -19,7 +18,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
foreach (var value in enumValues) foreach (var value in enumValues)
{ {
var key = keyPrefix + value; var key = keyPrefix + value;
var display = InternationalizationManager.Instance.GetTranslation(key); var display = App.API.GetTranslation(key);
data.Add(new TR { Display = display, Value = value, LocalizationKey = key }); data.Add(new TR { Display = display, Value = value, LocalizationKey = key });
} }
@ -30,7 +29,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
{ {
foreach (var item in options) foreach (var item in options)
{ {
item.Display = InternationalizationManager.Instance.GetTranslation(item.LocalizationKey); item.Display = App.API.GetTranslation(item.LocalizationKey);
} }
} }
} }

View file

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core; using Flow.Launcher.Core;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
@ -16,6 +15,8 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneAboutViewModel : BaseModel public partial class SettingsPaneAboutViewModel : BaseModel
{ {
private static readonly string ClassName = nameof(SettingsPaneAboutViewModel);
private readonly Settings _settings; private readonly Settings _settings;
private readonly Updater _updater; private readonly Updater _updater;
@ -24,7 +25,16 @@ public partial class SettingsPaneAboutViewModel : BaseModel
get get
{ {
var size = GetLogFiles().Sum(file => file.Length); var size = GetLogFiles().Sum(file => file.Length);
return $"{InternationalizationManager.Instance.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})"; return $"{App.API.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})";
}
}
public string CacheFolderSize
{
get
{
var size = GetCacheFiles().Sum(file => file.Length);
return $"{App.API.GetTranslation("clearcachefolder")} ({BytesToReadableString(size)})";
} }
} }
@ -42,7 +52,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}; };
public string ActivatedTimes => string.Format( public string ActivatedTimes => string.Format(
InternationalizationManager.Instance.GetTranslation("about_activate_times"), App.API.GetTranslation("about_activate_times"),
_settings.ActivateTimes _settings.ActivateTimes
); );
@ -88,32 +98,52 @@ public partial class SettingsPaneAboutViewModel : BaseModel
private void AskClearLogFolderConfirmation() private void AskClearLogFolderConfirmation()
{ {
var confirmResult = App.API.ShowMsgBox( var confirmResult = App.API.ShowMsgBox(
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"), App.API.GetTranslation("clearlogfolderMessage"),
InternationalizationManager.Instance.GetTranslation("clearlogfolder"), App.API.GetTranslation("clearlogfolder"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
); );
if (confirmResult == MessageBoxResult.Yes) if (confirmResult == MessageBoxResult.Yes)
{ {
ClearLogFolder(); if (!ClearLogFolder())
{
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage"));
}
}
}
[RelayCommand]
private void AskClearCacheFolderConfirmation()
{
var confirmResult = App.API.ShowMsgBox(
App.API.GetTranslation("clearcachefolderMessage"),
App.API.GetTranslation("clearcachefolder"),
MessageBoxButton.YesNo
);
if (confirmResult == MessageBoxResult.Yes)
{
if (!ClearCacheFolder())
{
App.API.ShowMsgBox(App.API.GetTranslation("clearfolderfailMessage"));
}
} }
} }
[RelayCommand] [RelayCommand]
private void OpenSettingsFolder() private void OpenSettingsFolder()
{ {
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings)); App.API.OpenDirectory(DataLocation.SettingsDirectory);
} }
[RelayCommand] [RelayCommand]
private void OpenParentOfSettingsFolder(object parameter) private void OpenParentOfSettingsFolder(object parameter)
{ {
string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings); string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectory);
string parentFolderPath = Path.GetDirectoryName(settingsFolderPath); string parentFolderPath = Path.GetDirectoryName(settingsFolderPath);
App.API.OpenDirectory(parentFolderPath); App.API.OpenDirectory(parentFolderPath);
} }
[RelayCommand] [RelayCommand]
private void OpenLogsFolder() private void OpenLogsFolder()
{ {
@ -121,26 +151,52 @@ public partial class SettingsPaneAboutViewModel : BaseModel
} }
[RelayCommand] [RelayCommand]
private Task UpdateApp() => _updater.UpdateAppAsync(false); private Task UpdateAppAsync() => _updater.UpdateAppAsync(false);
private void ClearLogFolder() private bool ClearLogFolder()
{ {
var success = true;
var logDirectory = GetLogDir(); var logDirectory = GetLogDir();
var logFiles = GetLogFiles(); var logFiles = GetLogFiles();
logFiles.ForEach(f => f.Delete()); logFiles.ForEach(f =>
{
try
{
f.Delete();
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete log file: {f.Name}", e);
success = false;
}
});
logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
// Do not clean log files of current version
.Where(dir => !Constant.Version.Equals(dir.Name)) .Where(dir => !Constant.Version.Equals(dir.Name))
.ToList() .ToList()
.ForEach(dir => dir.Delete()); .ForEach(dir =>
{
try
{
dir.Delete(true);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete log directory: {dir.Name}", e);
success = false;
}
});
OnPropertyChanged(nameof(LogFolderSize)); OnPropertyChanged(nameof(LogFolderSize));
return success;
} }
private static DirectoryInfo GetLogDir(string version = "") private static DirectoryInfo GetLogDir(string version = "")
{ {
return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version)); return new DirectoryInfo(Path.Combine(DataLocation.LogDirectory, version));
} }
private static List<FileInfo> GetLogFiles(string version = "") private static List<FileInfo> GetLogFiles(string version = "")
@ -148,6 +204,55 @@ public partial class SettingsPaneAboutViewModel : BaseModel
return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList(); return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
} }
private bool ClearCacheFolder()
{
var success = true;
var cacheDirectory = GetCacheDir();
var cacheFiles = GetCacheFiles();
cacheFiles.ForEach(f =>
{
try
{
f.Delete();
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache file: {f.Name}", e);
success = false;
}
});
cacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.ToList()
.ForEach(dir =>
{
try
{
dir.Delete(true);
}
catch (Exception e)
{
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
}
});
OnPropertyChanged(nameof(CacheFolderSize));
return success;
}
private static DirectoryInfo GetCacheDir()
{
return new DirectoryInfo(DataLocation.CacheDirectory);
}
private static List<FileInfo> GetCacheFiles()
{
return GetCacheDir().EnumerateFiles("*", SearchOption.AllDirectories).ToList();
}
private static string BytesToReadableString(long bytes) private static string BytesToReadableString(long bytes)
{ {
const int scale = 1024; const int scale = 1024;
@ -156,8 +261,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
foreach (string order in orders) foreach (string order in orders)
{ {
if (bytes > max) if (bytes > max) return $"{decimal.Divide(bytes, max):##.##} {order}";
return $"{decimal.Divide(bytes, max):##.##} {order}";
max /= scale; max /= scale;
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core; using Flow.Launcher.Core;
@ -30,6 +31,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
public class SearchWindowAlignData : DropdownDataGeneric<SearchWindowAligns> { } public class SearchWindowAlignData : DropdownDataGeneric<SearchWindowAligns> { }
public class SearchPrecisionData : DropdownDataGeneric<SearchPrecisionScore> { } public class SearchPrecisionData : DropdownDataGeneric<SearchPrecisionScore> { }
public class LastQueryModeData : DropdownDataGeneric<LastQueryMode> { } public class LastQueryModeData : DropdownDataGeneric<LastQueryMode> { }
public class SearchDelayTimeData : DropdownDataGeneric<SearchDelayTime> { }
public bool StartFlowLauncherOnSystemStartup public bool StartFlowLauncherOnSystemStartup
{ {
@ -142,12 +144,33 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
public List<LastQueryModeData> LastQueryModes { get; } = public List<LastQueryModeData> LastQueryModes { get; } =
DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery"); DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery");
public List<SearchDelayTimeData> SearchDelayTimes { get; } =
DropdownDataGeneric<SearchDelayTime>.GetValues<SearchDelayTimeData>("SearchDelayTime");
public SearchDelayTimeData SearchDelayTime
{
get => SearchDelayTimes.FirstOrDefault(x => x.Value == Settings.SearchDelayTime) ??
SearchDelayTimes.FirstOrDefault(x => x.Value == Plugin.SearchDelayTime.Normal) ??
SearchDelayTimes.FirstOrDefault();
set
{
if (value == null)
return;
if (Settings.SearchDelayTime != value.Value)
{
Settings.SearchDelayTime = value.Value;
}
}
}
private void UpdateEnumDropdownLocalizations() private void UpdateEnumDropdownLocalizations()
{ {
DropdownDataGeneric<SearchWindowScreens>.UpdateLabels(SearchWindowScreens); DropdownDataGeneric<SearchWindowScreens>.UpdateLabels(SearchWindowScreens);
DropdownDataGeneric<SearchWindowAligns>.UpdateLabels(SearchWindowAligns); DropdownDataGeneric<SearchWindowAligns>.UpdateLabels(SearchWindowAligns);
DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores); DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores);
DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes); DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes);
DropdownDataGeneric<SearchDelayTime>.UpdateLabels(SearchDelayTimes);
} }
public string Language public string Language

View file

@ -30,8 +30,9 @@ public class SettingsPanePluginsViewModel : BaseModel
.Select(plugin => new PluginViewModel .Select(plugin => new PluginViewModel
{ {
PluginPair = plugin, PluginPair = plugin,
PluginSettingsObject = _settings.PluginSettings.Plugins[plugin.Metadata.ID] PluginSettingsObject = _settings.PluginSettings.GetPluginSettings(plugin.Metadata.ID)
}) })
.Where(plugin => plugin.PluginSettingsObject != null)
.ToList(); .ToList();
public List<PluginViewModel> FilteredPluginViewModels => PluginViewModels public List<PluginViewModel> FilteredPluginViewModels => PluginViewModels

View file

@ -5,6 +5,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Media; using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper; using Flow.Launcher.Helper;
@ -13,7 +14,6 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
using ModernWpf; using ModernWpf;
using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager; using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
namespace Flow.Launcher.SettingPages.ViewModels; namespace Flow.Launcher.SettingPages.ViewModels;
@ -21,46 +21,70 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneThemeViewModel : BaseModel public partial class SettingsPaneThemeViewModel : BaseModel
{ {
private const string DefaultFont = "Segoe UI"; private const string DefaultFont = "Segoe UI";
public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : "";
public Settings Settings { get; } public Settings Settings { get; }
private readonly Theme _theme = Ioc.Default.GetRequiredService<Theme>();
public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; 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 Theme.ThemeData _selectedTheme; private Theme.ThemeData _selectedTheme;
public Theme.ThemeData SelectedTheme public Theme.ThemeData SelectedTheme
{ {
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Settings.Theme); get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
set set
{ {
_selectedTheme = value; _selectedTheme = value;
ThemeManager.Instance.ChangeTheme(value.FileNameWithoutExtension); _theme.ChangeTheme(value.FileNameWithoutExtension);
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect) // Update UI state
DropShadowEffect = false; OnPropertyChanged(nameof(BackdropType));
OnPropertyChanged(nameof(IsBackdropEnabled));
OnPropertyChanged(nameof(IsDropShadowEnabled));
OnPropertyChanged(nameof(DropShadowEffect));
_ = _theme.RefreshFrameAsync();
} }
} }
public bool IsBackdropEnabled
{
get
{
if (!Win32Helper.IsBackdropSupported()) return false;
return SelectedTheme?.HasBlur ?? false;
}
}
public bool IsDropShadowEnabled => !_theme.BlurEnabled;
public bool DropShadowEffect public bool DropShadowEffect
{ {
get => Settings.UseDropShadowEffect; get => Settings.UseDropShadowEffect;
set set
{ {
if (ThemeManager.Instance.BlurEnabled && value) if (_theme.BlurEnabled)
{ {
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed")); // Always DropShadowEffect = true with blur theme
Settings.UseDropShadowEffect = true;
return; return;
} }
// User can change shadow with non-blur theme.
if (value) if (value)
{ {
ThemeManager.Instance.AddDropShadowEffectToCurrentTheme(); _theme.AddDropShadowEffectToCurrentTheme();
} }
else else
{ {
ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme(); _theme.RemoveDropShadowEffectFromCurrentTheme();
} }
Settings.UseDropShadowEffect = value; Settings.UseDropShadowEffect = value;
OnPropertyChanged(nameof(DropShadowEffect));
} }
} }
@ -94,12 +118,25 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set => Settings.ResultSubItemFontSize = value; set => Settings.ResultSubItemFontSize = value;
} }
private List<Theme.ThemeData> _themes;
public List<Theme.ThemeData> Themes => _themes ??= ThemeManager.Instance.LoadAvailableThemes();
public class ColorSchemeData : DropdownDataGeneric<ColorSchemes> { } public class ColorSchemeData : DropdownDataGeneric<ColorSchemes> { }
public List<ColorSchemeData> ColorSchemes { get; } = DropdownDataGeneric<ColorSchemes>.GetValues<ColorSchemeData>("ColorScheme"); public List<ColorSchemeData> ColorSchemes { get; } = DropdownDataGeneric<ColorSchemes>.GetValues<ColorSchemeData>("ColorScheme");
public string ColorScheme
{
get => Settings.ColorScheme;
set
{
ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = value switch
{
Constant.Light => ApplicationTheme.Light,
Constant.Dark => ApplicationTheme.Dark,
Constant.System => null,
_ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme
};
Settings.ColorScheme = value;
_ = _theme.RefreshFrameAsync();
}
}
public List<string> TimeFormatList { get; } = new() public List<string> TimeFormatList { get; } = new()
{ {
@ -174,6 +211,33 @@ public partial class SettingsPaneThemeViewModel : BaseModel
public class AnimationSpeedData : DropdownDataGeneric<AnimationSpeeds> { } public class AnimationSpeedData : DropdownDataGeneric<AnimationSpeeds> { }
public List<AnimationSpeedData> AnimationSpeeds { get; } = DropdownDataGeneric<AnimationSpeeds>.GetValues<AnimationSpeedData>("AnimationSpeed"); public List<AnimationSpeedData> AnimationSpeeds { get; } = DropdownDataGeneric<AnimationSpeeds>.GetValues<AnimationSpeedData>("AnimationSpeed");
public class BackdropTypeData : DropdownDataGeneric<BackdropTypes> { }
public List<BackdropTypeData> BackdropTypesList { get; } =
DropdownDataGeneric<BackdropTypes>.GetValues<BackdropTypeData>("BackdropTypes");
public BackdropTypes BackdropType
{
get => Enum.IsDefined(typeof(BackdropTypes), Settings.BackdropType)
? Settings.BackdropType
: BackdropTypes.None;
set
{
if (!Enum.IsDefined(typeof(BackdropTypes), value))
{
value = BackdropTypes.None;
}
Settings.BackdropType = value;
// Can only apply blur because drop shadow effect is not supported with backdrop
// So drop shadow effect has been disabled
_ = _theme.SetBlurForWindowAsync();
OnPropertyChanged(nameof(IsDropShadowEnabled));
}
}
public bool UseSound public bool UseSound
{ {
get => Settings.UseSound; get => Settings.UseSound;
@ -196,6 +260,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set => Settings.SoundVolume = value; set => Settings.SoundVolume = value;
} }
public bool ShowPlaceholder
{
get => Settings.ShowPlaceholder;
set => Settings.ShowPlaceholder = value;
}
public string PlaceholderTextTip
{
get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder"));
}
public string PlaceholderText
{
get => Settings.PlaceholderText;
set => Settings.PlaceholderText = value;
}
public bool UseClock public bool UseClock
{ {
get => Settings.UseClock; get => Settings.UseClock;
@ -219,37 +300,37 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{ {
var results = new List<Result> var results = new List<Result>
{ {
new Result new()
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), Title = App.API.GetTranslation("SampleTitleExplorer"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
) )
}, },
new Result new()
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), Title = App.API.GetTranslation("SampleTitleWebSearch"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
) )
}, },
new Result new()
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), Title = App.API.GetTranslation("SampleTitleProgram"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), SubTitle = App.API.GetTranslation("SampleSubTitleProgram"),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
) )
}, },
new Result new()
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), Title = App.API.GetTranslation("SampleTitleProcessKiller"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"),
IcoPath = Path.Combine( IcoPath = Path.Combine(
Constant.ProgramDirectory, Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
@ -281,7 +362,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set set
{ {
Settings.QueryBoxFont = value.ToString(); Settings.QueryBoxFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
@ -303,7 +384,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
Settings.QueryBoxFontStretch = value.Stretch.ToString(); Settings.QueryBoxFontStretch = value.Stretch.ToString();
Settings.QueryBoxFontWeight = value.Weight.ToString(); Settings.QueryBoxFontWeight = value.Weight.ToString();
Settings.QueryBoxFontStyle = value.Style.ToString(); Settings.QueryBoxFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
@ -325,7 +406,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set set
{ {
Settings.ResultFont = value.ToString(); Settings.ResultFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
@ -347,7 +428,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
Settings.ResultFontStretch = value.Stretch.ToString(); Settings.ResultFontStretch = value.Stretch.ToString();
Settings.ResultFontWeight = value.Weight.ToString(); Settings.ResultFontWeight = value.Weight.ToString();
Settings.ResultFontStyle = value.Style.ToString(); Settings.ResultFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
@ -355,9 +436,9 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{ {
get get
{ {
if (Fonts.SystemFontFamilies.Count(o => if (Fonts.SystemFontFamilies.Any(o =>
o.FamilyNames.Values != null && o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.ResultSubFont)) > 0) o.FamilyNames.Values.Contains(Settings.ResultSubFont)))
{ {
var font = new FontFamily(Settings.ResultSubFont); var font = new FontFamily(Settings.ResultSubFont);
return font; return font;
@ -371,7 +452,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set set
{ {
Settings.ResultSubFont = value.ToString(); Settings.ResultSubFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
@ -392,34 +473,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
Settings.ResultSubFontStretch = value.Stretch.ToString(); Settings.ResultSubFontStretch = value.Stretch.ToString();
Settings.ResultSubFontWeight = value.Weight.ToString(); Settings.ResultSubFontWeight = value.Weight.ToString();
Settings.ResultSubFontStyle = value.Style.ToString(); Settings.ResultSubFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme); _theme.UpdateFonts();
} }
} }
public string ThemeImage => Constant.QueryTextBoxIconImagePath; public string ThemeImage => Constant.QueryTextBoxIconImagePath;
[RelayCommand]
private void OpenThemesFolder()
{
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
}
public void UpdateColorScheme()
{
ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = Settings.ColorScheme switch
{
Constant.Light => ApplicationTheme.Light,
Constant.Dark => ApplicationTheme.Dark,
Constant.System => null,
_ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme
};
}
public SettingsPaneThemeViewModel(Settings settings) public SettingsPaneThemeViewModel(Settings settings)
{ {
Settings = settings; Settings = settings;
} }
[RelayCommand]
private void OpenThemesFolder()
{
App.API.OpenDirectory(DataLocation.ThemesDirectory);
}
[RelayCommand] [RelayCommand]
public void Reset() public void Reset()
{ {

View file

@ -90,6 +90,10 @@
Margin="0 12 0 0" Margin="0 12 0 0"
Icon="&#xf12b;"> Icon="&#xf12b;">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button
Margin="0 0 12 0"
Command="{Binding AskClearCacheFolderConfirmationCommand}"
Content="{Binding CacheFolderSize, Mode=OneWay}" />
<Button <Button
Margin="0 0 12 0" Margin="0 0 12 0"
Command="{Binding AskClearLogFolderConfirmationCommand}" Command="{Binding AskClearLogFolderConfirmationCommand}"

View file

@ -28,21 +28,26 @@
Style="{StaticResource PageTitle}" Style="{StaticResource PageTitle}"
Text="{DynamicResource general}" Text="{DynamicResource general}"
TextAlignment="left" /> TextAlignment="left" />
<cc:ExCard
<cc:Card Title="{DynamicResource startFlowLauncherOnSystemStartup}" Icon="&#xe8fc;"> Title="{DynamicResource startFlowLauncherOnSystemStartup}"
<ui:ToggleSwitch Margin="0 8 0 0"
IsOn="{Binding StartFlowLauncherOnSystemStartup}" Icon="&#xe8fc;">
OffContent="{DynamicResource disable}" <cc:ExCard.SideContent>
OnContent="{DynamicResource enable}" /> <ui:ToggleSwitch
</cc:Card> IsOn="{Binding StartFlowLauncherOnSystemStartup}"
OffContent="{DynamicResource disable}"
<cc:Card Title="{DynamicResource useLogonTaskForStartup}" Sub="{DynamicResource useLogonTaskForStartupTooltip}"> OnContent="{DynamicResource enable}" />
<ui:ToggleSwitch </cc:ExCard.SideContent>
IsOn="{Binding UseLogonTaskForStartup}" <cc:Card
OffContent="{DynamicResource disable}" Title="{DynamicResource useLogonTaskForStartup}"
OnContent="{DynamicResource enable}" /> Sub="{DynamicResource useLogonTaskForStartupTooltip}"
</cc:Card> Type="InsideFit">
<ui:ToggleSwitch
IsOn="{Binding UseLogonTaskForStartup}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
</cc:ExCard>
<cc:Card <cc:Card
Title="{DynamicResource hideOnStartup}" Title="{DynamicResource hideOnStartup}"
Icon="&#xed1a;" Icon="&#xed1a;"

View file

@ -30,6 +30,7 @@
VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel"> VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel> <StackPanel>
<!-- Page title --> <!-- Page title -->
<TextBlock <TextBlock
Margin="5 23 0 5" Margin="5 23 0 5"
@ -38,6 +39,7 @@
Text="{DynamicResource appearance}" Text="{DynamicResource appearance}"
TextAlignment="left" TextAlignment="left"
Visibility="Collapsed" /> Visibility="Collapsed" />
<!-- Theme Preview and Editor --> <!-- Theme Preview and Editor -->
<Grid> <Grid>
<Grid.Style> <Grid.Style>
@ -68,6 +70,7 @@
<ColumnDefinition Width="8*" /> <ColumnDefinition Width="8*" />
<ColumnDefinition MaxWidth="250" Style="{StaticResource SecondColumnStyle}" /> <ColumnDefinition MaxWidth="250" Style="{StaticResource SecondColumnStyle}" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Theme Size Editor --> <!-- Theme Size Editor -->
<Border <Border
Grid.Column="1" Grid.Column="1"
@ -260,7 +263,8 @@
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</Border> </Border>
<!-- Theme Preview -->
<!-- Theme preview -->
<Border <Border
Grid.Column="0" Grid.Column="0"
Background="{Binding PreviewBackground}" Background="{Binding PreviewBackground}"
@ -303,7 +307,7 @@
Width="400" Width="400"
Margin="40 30 0 30" Margin="40 30 0 30"
SnapsToDevicePixels="True" SnapsToDevicePixels="True"
Style="{DynamicResource WindowBorderStyle}"> Style="{DynamicResource PreviewWindowBorderStyle}">
<Border BorderThickness="0" Style="{DynamicResource WindowRadius}"> <Border BorderThickness="0" Style="{DynamicResource WindowRadius}">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -370,17 +374,6 @@
</DockPanel> </DockPanel>
</Border> </Border>
</Grid> </Grid>
<!-- Drop shadow effect -->
<cc:Card
Title="{DynamicResource queryWindowShadowEffect}"
Margin="0 8 0 0"
Icon="&#xeb91;"
Sub="{DynamicResource shadowEffectCPUUsage}">
<ui:ToggleSwitch
IsOn="{Binding DropShadowEffect}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<!-- Theme --> <!-- Theme -->
<cc:ExCard <cc:ExCard
@ -469,42 +462,67 @@
</ListBox.Template> </ListBox.Template>
</ListBox> </ListBox>
</cc:ExCard> </cc:ExCard>
<cc:CardGroup Margin="0 10 0 0">
<!-- Backdrop effect -->
<cc:Card
Title="{DynamicResource BackdropType}"
Margin="0 0 0 0"
Icon="&#xeb42;"
Sub="{Binding BackdropSubText}">
<ComboBox
MinWidth="160"
VerticalAlignment="Center"
DisplayMemberPath="Display"
FontSize="14"
IsEnabled="{Binding IsBackdropEnabled}"
ItemsSource="{Binding BackdropTypesList}"
SelectedValue="{Binding BackdropType, Mode=TwoWay}"
SelectedValuePath="Value" />
</cc:Card>
<!-- Drop shadow effect -->
<cc:Card
Title="{DynamicResource queryWindowShadowEffect}"
Margin="0 0 0 0"
Icon="&#xeb91;">
<ui:ToggleSwitch
IsEnabled="{Binding IsDropShadowEnabled}"
IsOn="{Binding DropShadowEffect}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
</cc:CardGroup>
<cc:HyperLink <cc:HyperLink
Margin="10" Margin="10"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Text="{DynamicResource browserMoreThemes}" Text="{DynamicResource browserMoreThemes}"
Uri="{Binding LinkThemeGallery}" /> Uri="{Binding LinkThemeGallery}" />
<!-- Fixed Height --> <!-- Fixed size -->
<cc:CardGroup Margin="0 20 0 0"> <cc:ExCard
<cc:Card Title="{DynamicResource KeepMaxResults}"
Title="{DynamicResource KeepMaxResults}" Margin="0 20 0 0"
Icon="&#xe8fd;" Icon="&#xE744;"
Sub="{DynamicResource KeepMaxResultsToolTip}"> Sub="{DynamicResource KeepMaxResultsToolTip}">
<ui:ToggleSwitch IsOn="{Binding KeepMaxResults}" /> <cc:ExCard.SideContent>
</cc:Card> <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<ui:ToggleSwitch
IsOn="{Binding KeepMaxResults}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</StackPanel>
</cc:ExCard.SideContent>
<cc:Card <cc:Card
Title="{DynamicResource maxShowResults}" Title="{DynamicResource maxShowResults}"
Sub="{DynamicResource maxShowResultsToolTip}" Sub="{DynamicResource maxShowResultsToolTip}"
Visibility="{Binding KeepMaxResults, Converter={StaticResource BoolToVisibilityConverter}}"> Type="InsideFit">
<ComboBox <ComboBox
Width="100" Width="100"
ItemsSource="{Binding MaxResultsRange}" ItemsSource="{Binding MaxResultsRange}"
SelectedItem="{Binding Settings.MaxResultsToShow}" /> SelectedItem="{Binding Settings.MaxResultsToShow}" />
</cc:Card> </cc:Card>
</cc:CardGroup> </cc:ExCard>
<!-- Fonts and icons -->
<cc:Card
Title="{DynamicResource useGlyphUI}"
Margin="0 14 0 0"
Icon="&#xf6b8;"
Sub="{DynamicResource useGlyphUIEffect}">
<ui:ToggleSwitch
IsOn="{Binding UseGlyphIcons}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<!-- Time and date --> <!-- Time and date -->
<cc:CardGroup Margin="0 14 0 0"> <cc:CardGroup Margin="0 14 0 0">
@ -551,22 +569,45 @@
</cc:Card> </cc:Card>
</cc:CardGroup> </cc:CardGroup>
<!-- Animation --> <!-- Placeholder text -->
<cc:CardGroup Margin="0 14 0 0"> <cc:ExCard
Title="{DynamicResource ShowPlaceholder}"
Margin="0 4 0 0"
Icon="&#xea80;"
Sub="{DynamicResource ShowPlaceholderTip}">
<cc:ExCard.SideContent>
<ui:ToggleSwitch
IsOn="{Binding ShowPlaceholder}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:ExCard.SideContent>
<cc:Card <cc:Card
Title="{DynamicResource Animation}" Title="{DynamicResource PlaceholderText}"
Icon="&#xedb5;" Sub="{Binding PlaceholderTextTip}"
Sub="{DynamicResource AnimationTip}"> Type="InsideFit">
<TextBox
MinWidth="150"
Text="{Binding PlaceholderText}"
TextWrapping="NoWrap" />
</cc:Card>
</cc:ExCard>
<!-- Animation -->
<cc:ExCard
Title="{DynamicResource Animation}"
Margin="0 18 0 0"
Icon="&#xedb5;"
Sub="{DynamicResource AnimationTip}">
<cc:ExCard.SideContent>
<ui:ToggleSwitch <ui:ToggleSwitch
IsOn="{Binding UseAnimation}" IsOn="{Binding UseAnimation}"
OffContent="{DynamicResource disable}" OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" /> OnContent="{DynamicResource enable}" />
</cc:Card> </cc:ExCard.SideContent>
<cc:Card <cc:Card
Title="{DynamicResource AnimationSpeed}" Title="{DynamicResource AnimationSpeed}"
Icon="&#xe916;"
Sub="{DynamicResource AnimationSpeedTip}" Sub="{DynamicResource AnimationSpeedTip}"
Visibility="{Binding UseAnimation, Converter={StaticResource BoolToVisibilityConverter}}"> Type="InsideFit">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ComboBox <ComboBox
MinWidth="160" MinWidth="160"
@ -586,25 +627,25 @@
IsEqualTo={x:Static userSettings:AnimationSpeeds.Custom}}" /> IsEqualTo={x:Static userSettings:AnimationSpeeds.Custom}}" />
</StackPanel> </StackPanel>
</cc:Card> </cc:Card>
</cc:CardGroup> </cc:ExCard>
<!-- SFX --> <!-- SFX -->
<cc:CardGroup Margin="0 14 0 0"> <cc:ExCard
<cc:Card Title="{DynamicResource SoundEffect}"
Title="{DynamicResource SoundEffect}" Margin="0 4 0 0"
Icon="&#xe7f5;" Icon="&#xe7f5;"
Sub="{DynamicResource SoundEffectTip}"> Sub="{DynamicResource SoundEffectTip}">
<cc:ExCard.SideContent>
<ui:ToggleSwitch <ui:ToggleSwitch
IsOn="{Binding UseSound}" IsOn="{Binding UseSound}"
OffContent="{DynamicResource disable}" OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" /> OnContent="{DynamicResource enable}" />
</cc:Card> </cc:ExCard.SideContent>
<cc:Card <cc:Card
Title="{DynamicResource SoundEffectVolume}" Title="{DynamicResource SoundEffectVolume}"
Icon="&#xe994;"
IsEnabled="{Binding EnableVolumeAdjustment}" IsEnabled="{Binding EnableVolumeAdjustment}"
Sub="{DynamicResource SoundEffectVolumeTip}" Sub="{DynamicResource SoundEffectVolumeTip}"
Visibility="{Binding UseSound, Converter={StaticResource BoolToVisibilityConverter}}"> Type="InsideFit">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
Margin="0 0 8 0" Margin="0 0 8 0"
@ -621,7 +662,7 @@
Value="{Binding SoundEffectVolume}" /> Value="{Binding SoundEffectVolume}" />
</StackPanel> </StackPanel>
</cc:Card> </cc:Card>
</cc:CardGroup> </cc:ExCard>
<Border <Border
Name="WMPWarning" Name="WMPWarning"
Padding="0 10" Padding="0 10"
@ -657,6 +698,18 @@
</Grid> </Grid>
</Border> </Border>
<!-- Fonts and icons -->
<cc:Card
Title="{DynamicResource useGlyphUI}"
Margin="0 0 0 0"
Icon="&#xf6b8;"
Sub="{DynamicResource useGlyphUIEffect}">
<ui:ToggleSwitch
IsOn="{Binding UseGlyphIcons}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<!-- Settings color scheme --> <!-- Settings color scheme -->
<cc:Card <cc:Card
Title="{DynamicResource ColorScheme}" Title="{DynamicResource ColorScheme}"
@ -667,9 +720,8 @@
DisplayMemberPath="Display" DisplayMemberPath="Display"
FontSize="14" FontSize="14"
ItemsSource="{Binding ColorSchemes}" ItemsSource="{Binding ColorSchemes}"
SelectedValue="{Binding Settings.ColorScheme}" SelectedValue="{Binding ColorScheme, Mode=TwoWay}"
SelectedValuePath="Value" SelectedValuePath="Value" />
SelectionChanged="Selector_OnSelectionChanged" />
</cc:Card> </cc:Card>
<!-- Theme folder --> <!-- Theme folder -->

View file

@ -1,4 +1,3 @@
using System.Windows.Controls;
using System.Windows.Navigation; using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.SettingPages.ViewModels;
@ -23,9 +22,4 @@ public partial class SettingsPaneTheme : Page
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
} }
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewModel.UpdateColorScheme();
}
} }

View file

@ -18,6 +18,8 @@
Loaded="OnLoaded" Loaded="OnLoaded"
MouseDown="window_MouseDown" MouseDown="window_MouseDown"
ResizeMode="CanResize" ResizeMode="CanResize"
SnapsToDevicePixels="True"
UseLayoutRounding="True"
StateChanged="Window_StateChanged" StateChanged="Window_StateChanged"
Top="{Binding SettingWindowTop, Mode=TwoWay}" Top="{Binding SettingWindowTop, Mode=TwoWay}"
WindowStartupLocation="Manual" WindowStartupLocation="Manual"
@ -54,6 +56,7 @@
Width="16" Width="16"
Height="16" Height="16"
Margin="10 4 4 4" Margin="10 4 4 4"
RenderOptions.BitmapScalingMode="HighQuality"
Source="/Images/app.png" /> Source="/Images/app.png" />
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"

View file

@ -4,7 +4,7 @@ using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.SettingPages.Views; using Flow.Launcher.SettingPages.Views;
@ -92,13 +92,13 @@ public partial class SettingWindow
{ {
if (WindowState == WindowState.Maximized) if (WindowState == WindowState.Maximized)
{ {
MaximizeButton.Visibility = Visibility.Collapsed; MaximizeButton.Visibility = Visibility.Hidden;
RestoreButton.Visibility = Visibility.Visible; RestoreButton.Visibility = Visibility.Visible;
} }
else else
{ {
MaximizeButton.Visibility = Visibility.Visible; MaximizeButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed; RestoreButton.Visibility = Visibility.Hidden;
} }
} }
@ -143,8 +143,8 @@ public partial class SettingWindow
private double WindowLeft() private double WindowLeft()
{ {
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - ActualWidth) / 2 + dip1.X; var left = (dip2.X - ActualWidth) / 2 + dip1.X;
return left; return left;
} }
@ -152,8 +152,8 @@ public partial class SettingWindow
private double WindowTop() private double WindowTop()
{ {
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20; var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20;
return top; return top;
} }

View file

@ -27,7 +27,6 @@
<Style x:Key="BaseQueryBoxStyle" TargetType="{x:Type TextBox}"> <Style x:Key="BaseQueryBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="FontSize" Value="28" /> <Setter Property="FontSize" Value="28" />
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="Margin" Value="16 7 0 7" /> <Setter Property="Margin" Value="16 7 0 7" />
<Setter Property="Padding" Value="0 0 68 0" /> <Setter Property="Padding" Value="0 0 68 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
@ -107,7 +106,7 @@
</Style> </Style>
<Style x:Key="BasePendingLineStyle" TargetType="{x:Type Line}"> <Style x:Key="BasePendingLineStyle" TargetType="{x:Type Line}">
<Setter Property="Stroke" Value="Blue" /> <Setter Property="Stroke" Value="{StaticResource SystemAccentColorLight1Brush}" />
</Style> </Style>
<Style x:Key="BaseClockPanelPosition" TargetType="{x:Type Canvas}" /> <Style x:Key="BaseClockPanelPosition" TargetType="{x:Type Canvas}" />
@ -165,29 +164,6 @@
BasedOn="{StaticResource BaseClockPanel}" BasedOn="{StaticResource BaseClockPanel}"
TargetType="{x:Type StackPanel}"> TargetType="{x:Type StackPanel}">
<Setter Property="Orientation" Value="Vertical" /> <Setter Property="Orientation" Value="Vertical" />
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=QueryTextBox, UpdateSourceTrigger=PropertyChanged, Path=Text.Length}" Value="0" />
<Condition Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Collapsed" />
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1"
Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style> </Style>
<!-- Item Style --> <!-- Item Style -->
@ -204,12 +180,10 @@
<Style x:Key="BaseItemTitleStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemTitleStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#FFFFF8" /> <Setter Property="Foreground" Value="#FFFFF8" />
<Setter Property="FontSize" Value="16" /> <Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Medium" />
</Style> </Style>
<Style x:Key="BaseItemSubTitleStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemSubTitleStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#D9D9D4" /> <Setter Property="Foreground" Value="#D9D9D4" />
<Setter Property="FontSize" Value="13" /> <Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SubTitle, UpdateSourceTrigger=PropertyChanged, Path=Text.Length}" Value="0"> <DataTrigger Binding="{Binding ElementName=SubTitle, UpdateSourceTrigger=PropertyChanged, Path=Text.Length}" Value="0">
<Setter Property="Height" Value="0" /> <Setter Property="Height" Value="0" />
@ -241,7 +215,6 @@
<Style x:Key="BaseItemTitleSelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemTitleSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#FFFFF8" /> <Setter Property="Foreground" Value="#FFFFF8" />
<Setter Property="FontSize" Value="16" /> <Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Normal" />
</Style> </Style>
<Style x:Key="BaseItemSubTitleSelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemSubTitleSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#D9D9D4" /> <Setter Property="Foreground" Value="#D9D9D4" />
@ -378,17 +351,41 @@
<Setter Property="Inline.FontWeight" Value="Bold" /> <Setter Property="Inline.FontWeight" Value="Bold" />
</Style> </Style>
<Style x:Key="BaseItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemHotkeyStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#8f8f8f" /> <Setter Property="Foreground" Value="#8f8f8f" />
</Style> </Style>
<Style x:Key="BaseItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#8f8f8f" /> <Setter Property="Foreground" Value="#8f8f8f" />
</Style> </Style>
<Style x:Key="BaseItemHotkeyBGStyle" TargetType="{x:Type Border}">
<Setter Property="Margin" Value="12 0 12 0" />
<Setter Property="Padding" Value="6 4 6 4" />
<Setter Property="Background" Value="#138A8A8A" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style x:Key="BaseItemHotkeyBGSelectedStyle" TargetType="{x:Type Border}">
<Setter Property="Margin" Value="12 0 12 0" />
<Setter Property="Padding" Value="6 4 6 4" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#138A8A8A" />
</Style>
<Style
x:Key="ItemHotkeyBGStyle"
BasedOn="{StaticResource BaseItemHotkeyBGStyle}"
TargetType="{x:Type Border}">
<Setter Property="Margin" Value="12 0 12 0" />
<Setter Property="Padding" Value="6 4 6 4" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style
x:Key="ItemHotkeyBGSelectedStyle"
BasedOn="{StaticResource BaseItemHotkeyBGSelectedStyle}"
TargetType="{x:Type Border}" />
<!-- DO NOT USE THIS KEY. this key for themes with wrong typo. This key should be removed. Right key is BaseItemHotkeySelectedStyle. --> <!-- DO NOT USE THIS KEY. this key for themes with wrong typo. This key should be removed. Right key is BaseItemHotkeySelectedStyle. -->
<Style x:Key="BaseItemHotkeySelecetedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="BaseItemHotkeySelecetedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#8f8f8f" /> <Setter Property="Foreground" Value="#8f8f8f" />
</Style> </Style>
@ -417,6 +414,7 @@
<Style.Triggers> <Style.Triggers>
<MultiDataTrigger> <MultiDataTrigger>
<MultiDataTrigger.Conditions> <MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />
<Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" /> <Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" />
</MultiDataTrigger.Conditions> </MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters> <MultiDataTrigger.Setters>
@ -458,12 +456,12 @@
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style <Style
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="Gray" /> <Setter Property="BorderBrush" Value="Gray" />
</Style> </Style>
<Style x:Key="PreviewArea" TargetType="{x:Type Grid}"> <Style x:Key="PreviewArea" TargetType="{x:Type Grid}">
@ -473,8 +471,8 @@
<MultiDataTrigger.Conditions> <MultiDataTrigger.Conditions>
<!-- <!--
<Condition Binding="{Binding ElementName=ResultListBox, Path=Visibility}" Value="Collapsed" /> <Condition Binding="{Binding ElementName=ResultListBox, Path=Visibility}" Value="Collapsed" />
<Condition Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Collapsed" /> <Condition Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Collapsed" />-->
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />--> <Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />
<Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" /> <Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" />
</MultiDataTrigger.Conditions> </MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters> <MultiDataTrigger.Setters>
@ -514,7 +512,6 @@
x:Key="ItemHotkeyStyle" x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}" BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" /> <Setter Property="Foreground" Value="#8f8f8f" />
<Setter Property="Opacity" Value="0.5" /> <Setter Property="Opacity" Value="0.5" />
</Style> </Style>
@ -522,7 +519,6 @@
x:Key="ItemHotkeySelectedStyle" x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}" BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" /> <Setter Property="Foreground" Value="#8f8f8f" />
<Setter Property="Opacity" Value="0.5" /> <Setter Property="Opacity" Value="0.5" />
</Style> </Style>
@ -557,6 +553,14 @@
<Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextWrapping" Value="Wrap" />
</Style> </Style>
<Style x:Key="PreviewWindowBorderStyle" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="#2F2F2F" />
<Setter Property="Padding" Value="0 0 0 0" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
</Style>
<!-- Style for old version themes --> <!-- Style for old version themes -->
<Style <Style
x:Key="PreviewItemTitleStyle" x:Key="PreviewItemTitleStyle"

View file

@ -12,6 +12,10 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean> <system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
<system:String x:Key="SystemBG">Dark</system:String>
<Color x:Key="LightBG">#C7000000</Color>
<Color x:Key="DarkBG">#C7000000</Color>
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
<Style x:Key="WindowRadius" TargetType="{x:Type Border}"> <Style x:Key="WindowRadius" TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
@ -51,7 +55,6 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style <Style
x:Key="SeparatorStyle" x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}" BasedOn="{StaticResource BaseSeparatorStyle}"
@ -139,17 +142,21 @@
BasedOn="{StaticResource BaseSearchIconStyle}" BasedOn="{StaticResource BaseSearchIconStyle}"
TargetType="{x:Type Path}"> TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#ffffff" /> <Setter Property="Fill" Value="#ffffff" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="30" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="30" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#ffffff" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#ffffff" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>

View file

@ -10,8 +10,12 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean> <system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
<system:String x:Key="SystemBG">Dark</system:String>
<Color x:Key="LightBG">#B0000000</Color>
<Color x:Key="DarkBG">#B6000000</Color>
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
<Style x:Key="WindowRadius" TargetType="{x:Type Border}"> <Style x:Key="WindowRadius" TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
</Style> </Style>
@ -135,17 +139,21 @@
BasedOn="{StaticResource BaseSearchIconStyle}" BasedOn="{StaticResource BaseSearchIconStyle}"
TargetType="{x:Type Path}"> TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#ffffff" /> <Setter Property="Fill" Value="#ffffff" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="30" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="30" />
<Setter Property="Opacity" Value="0.5" /> <Setter Property="Opacity" Value="0.5" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#ffffff" />
<Setter Property="Opacity" Value="0.5" /> <Setter Property="Opacity" Value="0.5" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#ffffff" />
<Setter Property="Opacity" Value="0.5" /> <Setter Property="Opacity" Value="0.5" />
</Style> </Style>

View file

@ -10,14 +10,26 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean> <system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
<system:String x:Key="SystemBG">Light</system:String>
<Color x:Key="LightBG">#BFFAFAFA</Color>
<Color x:Key="DarkBG">#BFFAFAFA</Color>
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
<Style <Style
x:Key="ItemGlyph" x:Key="ItemGlyph"
BasedOn="{StaticResource BaseGlyphStyle}" BasedOn="{StaticResource BaseGlyphStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#FF000000" /> <Setter Property="Foreground" Value="#FF000000" />
</Style> </Style>
<Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#FF000000" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Width" Value="25" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="25" />
</Style>
<Style x:Key="WindowRadius" TargetType="{x:Type Border}"> <Style x:Key="WindowRadius" TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
</Style> </Style>
@ -33,7 +45,9 @@
<Style <Style
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}" /> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#39000000" />
</Style>
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"
@ -76,7 +90,7 @@
x:Key="ItemSubTitleStyle" x:Key="ItemSubTitleStyle"
BasedOn="{StaticResource BaseItemSubTitleStyle}" BasedOn="{StaticResource BaseItemSubTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#818181" /> <Setter Property="Foreground" Value="#85000000" />
</Style> </Style>
<Style <Style
x:Key="ItemTitleSelectedStyle" x:Key="ItemTitleSelectedStyle"
@ -88,18 +102,18 @@
x:Key="ItemSubTitleSelectedStyle" x:Key="ItemSubTitleSelectedStyle"
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#72767d" /> <Setter Property="Foreground" Value="#85000000" />
</Style> </Style>
<SolidColorBrush <SolidColorBrush
x:Key="ItemSelectedBackgroundColor" x:Key="ItemSelectedBackgroundColor"
Opacity="0.5" Opacity="0.6"
Color="#ccd0d4" /> Color="#33000000" />
<Style <Style
x:Key="SeparatorStyle" x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}" BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#c6c6c6" /> <Setter Property="Fill" Value="#22000000" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="12 0 12 8" /> <Setter Property="Margin" Value="12 0 12 8" />
</Style> </Style>
@ -112,7 +126,7 @@
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}"> <ControlTemplate TargetType="{x:Type Thumb}">
<Border <Border
Background="#bebebe" Background="#3C000000"
BorderBrush="Transparent" BorderBrush="Transparent"
BorderThickness="0" BorderThickness="0"
CornerRadius="2" CornerRadius="2"
@ -132,17 +146,21 @@
BasedOn="{StaticResource BaseSearchIconStyle}" BasedOn="{StaticResource BaseSearchIconStyle}"
TargetType="{x:Type Path}"> TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#000000" /> <Setter Property="Fill" Value="#000000" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="30" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="30" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#000000" /> <Setter Property="Foreground" Value="#000000" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="14" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#000000" /> <Setter Property="Foreground" Value="#000000" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Opacity" Value="0.2" />
</Style> </Style>

View file

@ -1,7 +1,7 @@
<!-- <!--
Name: Circle Name: Circle
IsDark: True IsDark: True
HasBlur: False HasBlur: True
--> -->
<ResourceDictionary <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@ -12,6 +12,10 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
<system:String x:Key="SystemBG">Auto</system:String>
<Color x:Key="LightBG">#E5E6E6E6</Color>
<Color x:Key="DarkBG">#DF1F1F1F</Color>
<Style <Style
x:Key="ItemGlyph" x:Key="ItemGlyph"
BasedOn="{StaticResource BaseGlyphStyle}" BasedOn="{StaticResource BaseGlyphStyle}"
@ -22,8 +26,8 @@
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0 4 50 0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Foreground" Value="{m:DynamicColor SystemControlPageTextBaseHighBrush}" /> <Setter Property="Foreground" Value="{m:DynamicColor SystemControlHighlightBaseHighBrush}" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
</Style> </Style>
@ -32,7 +36,7 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0 4 50 0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -75,7 +79,7 @@
x:Key="SeparatorStyle" x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}" BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="{m:DynamicColor SystemControlBackgroundChromeMediumBrush}" /> <Setter Property="Fill" Value="{DynamicResource SeparatorForeground}" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0 0 0 8" /> <Setter Property="Margin" Value="0 0 0 8" />
</Style> </Style>

View file

@ -33,7 +33,7 @@
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0,0,50,0" />
<Setter Property="CaretBrush" Value="#336766" /> <Setter Property="CaretBrush" Value="#336766" />
<Setter Property="Foreground" Value="#e7e9eb" /> <Setter Property="Foreground" Value="#e7e9eb" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -44,7 +44,7 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0,0,50,0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -56,7 +56,7 @@
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#09181e" /> <Setter Property="BorderBrush" Value="#1e292f" />
<Setter Property="Background" Value="#0f1f26" /> <Setter Property="Background" Value="#0f1f26" />
<Setter Property="CornerRadius" Value="6" /> <Setter Property="CornerRadius" Value="6" />
<Setter Property="UseLayoutRounding" Value="True" /> <Setter Property="UseLayoutRounding" Value="True" />

View file

@ -1,58 +1,57 @@
<!--
Name: Rider
IsDark: False
HasBlur: False
-->
<ResourceDictionary <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"> xmlns:system="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
<Style <Style
x:Key="ItemGlyph" x:Key="ItemGlyph"
BasedOn="{StaticResource BaseGlyphStyle}" BasedOn="{StaticResource BaseGlyphStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Foreground" Value="#dfe1e5" />
</Style> </Style>
<Style <Style
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="#dfe1e5" />
<Setter Property="FontSize" Value="18" />
<Setter Property="Height" Value="38" />
</Style> </Style>
<Style <Style
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="38" />
<Setter Property="FontSize" Value="18" />
<Setter Property="Foreground" Value="#24DFE1E5" />
</Style> </Style>
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="5" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness" Value="1,1,0,0" /> <Setter Property="BorderBrush" Value="#393b40" />
<Setter Property="BorderBrush" Value="#666666" /> <Setter Property="Background" Value="#2b2d30" />
<Setter Property="Background"> <Setter Property="CornerRadius" Value="8" />
<Setter.Value> <Setter Property="UseLayoutRounding" Value="True" />
<SolidColorBrush Opacity="0.95" Color="#333333" />
</Setter.Value>
</Setter>
</Style> </Style>
<Style <Style
x:Key="WindowStyle" x:Key="WindowStyle"
BasedOn="{StaticResource BaseWindowStyle}" BasedOn="{StaticResource BaseWindowStyle}"
TargetType="{x:Type Window}"> TargetType="{x:Type Window}" />
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Opacity="0.5" Color="White" />
</Setter.Value>
</Setter>
</Style>
<Style <Style
x:Key="PendingLineStyle" x:Key="PendingLineStyle"
BasedOn="{StaticResource BasePendingLineStyle}" BasedOn="{StaticResource BasePendingLineStyle}"
@ -63,27 +62,36 @@
x:Key="ItemTitleStyle" x:Key="ItemTitleStyle"
BasedOn="{StaticResource BaseItemTitleStyle}" BasedOn="{StaticResource BaseItemTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Foreground" Value="#dfe1e5" />
</Style> </Style>
<Style <Style
x:Key="ItemSubTitleStyle" x:Key="ItemSubTitleStyle"
BasedOn="{StaticResource BaseItemSubTitleStyle}" BasedOn="{StaticResource BaseItemSubTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style
x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#393b40" />
<Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0 0 0 8" />
</Style>
<Style x:Key="HighlightStyle" />
<Style <Style
x:Key="ItemTitleSelectedStyle" x:Key="ItemTitleSelectedStyle"
BasedOn="{StaticResource BaseItemTitleSelectedStyle}" BasedOn="{StaticResource BaseItemTitleSelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#dfe1e5" />
</Style> </Style>
<Style <Style
x:Key="ItemSubTitleSelectedStyle" x:Key="ItemSubTitleSelectedStyle"
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#949494" /> <Setter Property="Foreground" Value="#dfe1e5" />
</Style> </Style>
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#545454</SolidColorBrush> <SolidColorBrush x:Key="ItemSelectedBackgroundColor">#2e436e</SolidColorBrush>
<!-- button style in the middle of the scrollbar --> <!-- button style in the middle of the scrollbar -->
<Style <Style
@ -94,7 +102,7 @@
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}"> <ControlTemplate TargetType="{x:Type Thumb}">
<Border <Border
Background="#525252" Background="#393b40"
BorderBrush="Transparent" BorderBrush="Transparent"
BorderThickness="0" BorderThickness="0"
CornerRadius="2" CornerRadius="2"
@ -106,69 +114,83 @@
<Style <Style
x:Key="ScrollBarStyle" x:Key="ScrollBarStyle"
BasedOn="{StaticResource BaseScrollBarStyle}" BasedOn="{StaticResource BaseScrollBarStyle}"
TargetType="{x:Type ScrollBar}"> TargetType="{x:Type ScrollBar}" />
<Setter Property="Background" Value="#a0a0a0" />
</Style>
<Style <Style
x:Key="SearchIconStyle" x:Key="SearchIconStyle"
BasedOn="{StaticResource BaseSearchIconStyle}" BasedOn="{StaticResource BaseSearchIconStyle}"
TargetType="{x:Type Path}"> TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#FFFFFF" /> <Setter Property="Fill" Value="#6f737a" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
</Style>
<Style x:Key="SearchIconPosition" TargetType="{x:Type Canvas}">
<Setter Property="Background" Value="#2b2d30" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
<Setter Property="Opacity" Value="0.2" /> <Setter Property="Margin" Value="0 8 8 0" />
</Style> <Setter Property="HorizontalAlignment" Value="Right" />
<Style
x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#474747" />
<Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0,0,0,8" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" /> <Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style>
<Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#dfe1e5" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Width" Value="25" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="25" />
</Style>
<CornerRadius x:Key="ItemRadius">8</CornerRadius>
<Thickness x:Key="ItemMargin">10 0 10 0</Thickness>
<Thickness x:Key="ResultMargin">0 0 0 10</Thickness>
<Style
x:Key="ClockPanel"
BasedOn="{StaticResource BaseClockPanel}"
TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="0 0 54 0" />
</Style> </Style>
<Style <Style
x:Key="ClockBox" x:Key="ClockBox"
BasedOn="{StaticResource BaseClockBox}" BasedOn="{StaticResource BaseClockBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style <Style
x:Key="DateBox" x:Key="DateBox"
BasedOn="{StaticResource BaseDateBox}" BasedOn="{StaticResource BaseDateBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style <Style
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="#474747" /> <Setter Property="Margin" Value="0 0 10 8" />
<Setter Property="BorderBrush" Value="#393b40" />
</Style> </Style>
<Style <Style
x:Key="PreviewItemTitleStyle" x:Key="PreviewItemTitleStyle"
BasedOn="{StaticResource BasePreviewItemTitleStyle}" BasedOn="{StaticResource BasePreviewItemTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style <Style
x:Key="PreviewItemSubTitleStyle" x:Key="PreviewItemSubTitleStyle"
BasedOn="{StaticResource BasePreviewItemSubTitleStyle}" BasedOn="{StaticResource BasePreviewItemSubTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#787878" /> <Setter Property="Foreground" Value="#6f737a" />
</Style> </Style>
<Style <Style
x:Key="PreviewGlyph" x:Key="PreviewGlyph"
BasedOn="{StaticResource BasePreviewGlyph}" BasedOn="{StaticResource BasePreviewGlyph}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ebebeb" /> <Setter Property="Foreground" Value="#dfe1e5" />
</Style> </Style>
</ResourceDictionary> </ResourceDictionary>

View file

@ -29,7 +29,9 @@
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}" /> TargetType="{x:Type Border}">
<Setter Property="Background" Value="#2F2F2F" />
</Style>
<Style <Style
x:Key="WindowStyle" x:Key="WindowStyle"
@ -89,7 +91,7 @@
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#3b3b3b" /> <Setter Property="Fill" Value="#3b3b3b" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="8,0,8,8" /> <Setter Property="Margin" Value="8 0 8 8" />
</Style> </Style>
<Style <Style
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"

View file

@ -21,26 +21,25 @@
<Setter Property="SelectionBrush" Value="#0a68d8" /> <Setter Property="SelectionBrush" Value="#0a68d8" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
<Setter Property="FontSize" Value="24" /> <Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="0,0,66,0" /> <Setter Property="Padding" Value="0 0 66 0" />
</Style> </Style>
<Style <Style
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#72767d" /> <Setter Property="Foreground" Value="#72767d" />
<Setter Property="Background" Value="#36393f" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
<Setter Property="FontSize" Value="24" /> <Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="0,0,66,0" /> <Setter Property="Padding" Value="0 0 66 0" />
</Style> </Style>
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="2" /> <Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="#2f3136" /> <Setter Property="BorderBrush" Value="#3a3a41" />
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="#36393f" /> <Setter Property="Background" Value="#2e2e34" />
</Style> </Style>
<Style <Style
x:Key="WindowStyle" x:Key="WindowStyle"
@ -91,23 +90,25 @@
<Setter Property="Cursor" Value="Arrow" /> <Setter Property="Cursor" Value="Arrow" />
<Setter Property="Foreground" Value="#72767d" /> <Setter Property="Foreground" Value="#72767d" />
</Style> </Style>
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#49443c</SolidColorBrush> <SolidColorBrush x:Key="ItemSelectedBackgroundColor">#36363d</SolidColorBrush>
<Style <Style
x:Key="ItemImageSelectedStyle" x:Key="ItemImageSelectedStyle"
BasedOn="{StaticResource BaseItemImageSelectedStyle}" BasedOn="{StaticResource BaseItemImageSelectedStyle}"
TargetType="{x:Type Image}"> TargetType="{x:Type Image}">
<Setter Property="Cursor" Value="Arrow" /> <Setter Property="Cursor" Value="Arrow" />
</Style> </Style>
<Style x:Key="HighlightStyle"> <Style x:Key="HighlightStyle" />
<Setter Property="Inline.FontWeight" Value="Bold" /> <Style
</Style> x:Key="ItemHotkeyStyle"
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> BasedOn="{StaticResource BaseItemHotkeyStyle}"
<Setter Property="FontSize" Value="15" /> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#72767d" /> <Setter Property="Foreground" Value="#72767d" />
<Setter Property="Opacity" Value="0.8" /> <Setter Property="Opacity" Value="0.8" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="15" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#72767d" /> <Setter Property="Foreground" Value="#72767d" />
<Setter Property="Opacity" Value="0.8" /> <Setter Property="Opacity" Value="0.8" />
</Style> </Style>
@ -124,7 +125,7 @@
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}"> <ControlTemplate TargetType="{x:Type Thumb}">
<Border <Border
Background="#202225" Background="#787881"
BorderBrush="Transparent" BorderBrush="Transparent"
BorderThickness="0" BorderThickness="0"
CornerRadius="2" CornerRadius="2"
@ -141,9 +142,9 @@
x:Key="SeparatorStyle" x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}" BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#42454a" /> <Setter Property="Fill" Value="#3a3a41" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="12,0,12,6" /> <Setter Property="Margin" Value="0 0 0 0" />
</Style> </Style>
<Style <Style
x:Key="SearchIconStyle" x:Key="SearchIconStyle"
@ -170,6 +171,7 @@
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="#42454a" /> <Setter Property="BorderBrush" Value="#42454a" />
<Setter Property="Margin" Value="0 0 0 0" />
</Style> </Style>
<Style <Style
x:Key="PreviewItemTitleStyle" x:Key="PreviewItemTitleStyle"

View file

@ -21,7 +21,7 @@
<Setter Property="Foreground" Value="#f8f8f2" /> <Setter Property="Foreground" Value="#f8f8f2" />
<Setter Property="CaretBrush" Value="#ffb86c" /> <Setter Property="CaretBrush" Value="#ffb86c" />
<Setter Property="FontSize" Value="26" /> <Setter Property="FontSize" Value="26" />
<Setter Property="Padding" Value="0,4,66,0" /> <Setter Property="Padding" Value="0,0,66,0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
<Style <Style
@ -30,7 +30,7 @@
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#6272a4" /> <Setter Property="Foreground" Value="#6272a4" />
<Setter Property="FontSize" Value="26" /> <Setter Property="FontSize" Value="26" />
<Setter Property="Padding" Value="0,4,66,0" /> <Setter Property="Padding" Value="0,0,66,0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
<Style <Style

View file

@ -11,7 +11,7 @@
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#b88f3a" /> <Setter Property="Foreground" Value="#b88f3a" />
<Setter Property="CaretBrush" Value="#b88f3a" /> <Setter Property="CaretBrush" Value="#b88f3a" />
<Setter Property="Padding" Value="0,0,66,0" /> <Setter Property="Padding" Value="0 0 66 0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
<Style <Style
@ -24,9 +24,8 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="#161614" />
<Setter Property="Foreground" Value="#a09b8c" /> <Setter Property="Foreground" Value="#a09b8c" />
<Setter Property="Padding" Value="0,0,66,0" /> <Setter Property="Padding" Value="0 0 66 0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
@ -57,7 +56,7 @@
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#785a28" /> <Setter Property="Fill" Value="#785a28" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0,0,0,0" /> <Setter Property="Margin" Value="0 0 0 0" />
</Style> </Style>
<Style <Style
x:Key="ItemTitleStyle" x:Key="ItemTitleStyle"
@ -111,12 +110,16 @@
<Style x:Key="HighlightStyle"> <Style x:Key="HighlightStyle">
<Setter Property="Inline.FontWeight" Value="Bold" /> <Setter Property="Inline.FontWeight" Value="Bold" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="15" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#5b5a56" /> <Setter Property="Foreground" Value="#5b5a56" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="15" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#5b5a56" /> <Setter Property="Foreground" Value="#5b5a56" />
</Style> </Style>
<Geometry x:Key="SearchIconImg">F1 M12000,12000z M0,0z M10354,10962C10326,10951 10279,10927 10249,10907 10216,10886 9476,10153 8370,9046 7366,8042 6541,7220 6536,7220 6532,7220 6498,7242 6461,7268 6213,7447 5883,7619 5592,7721 5194,7860 4802,7919 4360,7906 3612,7886 2953,7647 2340,7174 2131,7013 1832,6699 1664,6465 1394,6088 1188,5618 1097,5170 1044,4909 1030,4764 1030,4470 1030,4130 1056,3914 1135,3609 1263,3110 1511,2633 1850,2235 1936,2134 2162,1911 2260,1829 2781,1395 3422,1120 4090,1045 4271,1025 4667,1025 4848,1045 5505,1120 6100,1368 6630,1789 6774,1903 7081,2215 7186,2355 7362,2588 7467,2759 7579,2990 7802,3455 7911,3937 7911,4460 7911,4854 7861,5165 7737,5542 7684,5702 7675,5724 7602,5885 7517,6071 7390,6292 7270,6460 7242,6499 7220,6533 7220,6538 7220,6542 8046,7371 9055,8380 10441,9766 10898,10229 10924,10274 10945,10308 10966,10364 10976,10408 10990,10472 10991,10493 10980,10554 10952,10717 10840,10865 10690,10937 10621,10971 10607,10974 10510,10977 10425,10980 10395,10977 10354,10962z M4685,7050C5214,7001 5694,6809 6100,6484 6209,6396 6396,6209 6484,6100 7151,5267 7246,4110 6721,3190 6369,2571 5798,2137 5100,1956 4706,1855 4222,1855 3830,1957 3448,2056 3140,2210 2838,2453 2337,2855 2010,3427 1908,4080 1877,4274 1877,4656 1908,4850 1948,5105 2028,5370 2133,5590 2459,6272 3077,6782 3810,6973 3967,7014 4085,7034 4290,7053 4371,7061 4583,7059 4685,7050z</Geometry> <Geometry x:Key="SearchIconImg">F1 M12000,12000z M0,0z M10354,10962C10326,10951 10279,10927 10249,10907 10216,10886 9476,10153 8370,9046 7366,8042 6541,7220 6536,7220 6532,7220 6498,7242 6461,7268 6213,7447 5883,7619 5592,7721 5194,7860 4802,7919 4360,7906 3612,7886 2953,7647 2340,7174 2131,7013 1832,6699 1664,6465 1394,6088 1188,5618 1097,5170 1044,4909 1030,4764 1030,4470 1030,4130 1056,3914 1135,3609 1263,3110 1511,2633 1850,2235 1936,2134 2162,1911 2260,1829 2781,1395 3422,1120 4090,1045 4271,1025 4667,1025 4848,1045 5505,1120 6100,1368 6630,1789 6774,1903 7081,2215 7186,2355 7362,2588 7467,2759 7579,2990 7802,3455 7911,3937 7911,4460 7911,4854 7861,5165 7737,5542 7684,5702 7675,5724 7602,5885 7517,6071 7390,6292 7270,6460 7242,6499 7220,6533 7220,6538 7220,6542 8046,7371 9055,8380 10441,9766 10898,10229 10924,10274 10945,10308 10966,10364 10976,10408 10990,10472 10991,10493 10980,10554 10952,10717 10840,10865 10690,10937 10621,10971 10607,10974 10510,10977 10425,10980 10395,10977 10354,10962z M4685,7050C5214,7001 5694,6809 6100,6484 6209,6396 6396,6209 6484,6100 7151,5267 7246,4110 6721,3190 6369,2571 5798,2137 5100,1956 4706,1855 4222,1855 3830,1957 3448,2056 3140,2210 2838,2453 2337,2855 2010,3427 1908,4080 1877,4274 1877,4656 1908,4850 1948,5105 2028,5370 2133,5590 2459,6272 3077,6782 3810,6973 3967,7014 4085,7034 4290,7053 4371,7061 4583,7059 4685,7050z</Geometry>
@ -141,7 +144,7 @@
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="Margin" Value="0,0,10,0" /> <Setter Property="Margin" Value="0 0 10 0" />
<Setter Property="BorderBrush" Value="#785a28" /> <Setter Property="BorderBrush" Value="#785a28" />
</Style> </Style>
<Style <Style

View file

@ -16,7 +16,7 @@
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Foreground" Value="#fbfdff" /> <Setter Property="Foreground" Value="#fbfdff" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
@ -26,7 +26,7 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -38,7 +38,7 @@
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#111828" /> <Setter Property="BorderBrush" Value="#202938" />
<Setter Property="Background" Value="#111828" /> <Setter Property="Background" Value="#111828" />
<Setter Property="CornerRadius" Value="8" /> <Setter Property="CornerRadius" Value="8" />
<Setter Property="UseLayoutRounding" Value="True" /> <Setter Property="UseLayoutRounding" Value="True" />
@ -71,7 +71,7 @@
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#202938" /> <Setter Property="Fill" Value="#202938" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0,0,0,8" /> <Setter Property="Margin" Value="0 0 0 8" />
</Style> </Style>
<Style x:Key="HighlightStyle" /> <Style x:Key="HighlightStyle" />
<Style <Style
@ -122,7 +122,7 @@
<Setter Property="Background" Value="#111828" /> <Setter Property="Background" Value="#111828" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
<Setter Property="Margin" Value="0,8,8,0" /> <Setter Property="Margin" Value="0 8 8 0" />
<Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="HorizontalAlignment" Value="Right" />
</Style> </Style>
@ -149,7 +149,7 @@
x:Key="ClockPanel" x:Key="ClockPanel"
BasedOn="{StaticResource ClockPanel}" BasedOn="{StaticResource ClockPanel}"
TargetType="{x:Type StackPanel}"> TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="0,0,54,0" /> <Setter Property="Margin" Value="0 0 54 0" />
</Style> </Style>
<Style <Style
x:Key="ClockBox" x:Key="ClockBox"
@ -167,7 +167,7 @@
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="Margin" Value="0,0,10,8" /> <Setter Property="Margin" Value="0 0 10 8" />
<Setter Property="BorderBrush" Value="#202938" /> <Setter Property="BorderBrush" Value="#202938" />
</Style> </Style>
<Style <Style

View file

@ -40,7 +40,7 @@
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#4c566a" /> <Setter Property="Fill" Value="#4c566a" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="12,0,12,8" /> <Setter Property="Margin" Value="12 0 12 8" />
</Style> </Style>
<Style <Style
x:Key="WindowStyle" x:Key="WindowStyle"
@ -116,12 +116,16 @@
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="15" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#8391AB" /> <Setter Property="Foreground" Value="#8391AB" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="15" /> x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#8391AB" /> <Setter Property="Foreground" Value="#8391AB" />
</Style> </Style>
<Style <Style

View file

@ -2,46 +2,44 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<Thickness x:Key="ResultMargin">0 0 0 0</Thickness> <Thickness x:Key="ResultMargin">0 0 0 4</Thickness>
<Style <Style
x:Key="ItemGlyph" x:Key="ItemGlyph"
BasedOn="{StaticResource BaseGlyphStyle}" BasedOn="{StaticResource BaseGlyphStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0e172c" /> <Setter Property="Foreground" Value="#ffffff" />
</Style> </Style>
<Style <Style
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#0E172C" /> <Setter Property="Foreground" Value="#cc1081" />
<Setter Property="CaretBrush" Value="#0E172C" /> <Setter Property="CaretBrush" Value="#cc1081" />
<Setter Property="SelectionBrush" Value="#a786df" /> <Setter Property="SelectionBrush" Value="#e564b1" />
</Style> </Style>
<Style <Style
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="#71114b" />
<Setter Property="Foreground" Value="#E9819F" />
</Style> </Style>
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"
BasedOn="{StaticResource BaseWindowBorderStyle}" BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8" /> <Setter Property="Background" Value="#1f1d1f" />
<Setter Property="Background" Value="#fec7d7" /> <Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderThickness" Value="3" /> <Setter Property="BorderBrush" Value="#000000" />
<Setter Property="BorderBrush" Value="#0e172c" />
</Style> </Style>
<Style <Style
x:Key="SeparatorStyle" x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}" BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#0e172c" /> <Setter Property="Fill" Value="#000000" />
<Setter Property="Height" Value="3" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0 0 0 0" /> <Setter Property="Margin" Value="12 0 12 4" />
</Style> </Style>
<Style <Style
x:Key="WindowStyle" x:Key="WindowStyle"
@ -51,33 +49,34 @@
x:Key="PendingLineStyle" x:Key="PendingLineStyle"
BasedOn="{StaticResource BasePendingLineStyle}" BasedOn="{StaticResource BasePendingLineStyle}"
TargetType="{x:Type Line}"> TargetType="{x:Type Line}">
<Setter Property="Stroke" Value="#a786df" /> <Setter Property="Stroke" Value="#cc1081" />
</Style> </Style>
<Style <Style
x:Key="ItemTitleStyle" x:Key="ItemTitleStyle"
BasedOn="{StaticResource BaseItemTitleStyle}" BasedOn="{StaticResource BaseItemTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0e172c" /> <Setter Property="Foreground" Value="#f5f5f5" />
</Style> </Style>
<Style <Style
x:Key="ItemSubTitleStyle" x:Key="ItemSubTitleStyle"
BasedOn="{StaticResource BaseItemSubTitleStyle}" BasedOn="{StaticResource BaseItemSubTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0e172c" /> <Setter Property="Foreground" Value="#c2c2c2" />
<Setter Property="Opacity" Value="0.5" />
</Style> </Style>
<Style <Style
x:Key="ItemTitleSelectedStyle" x:Key="ItemTitleSelectedStyle"
BasedOn="{StaticResource BaseItemTitleSelectedStyle}" BasedOn="{StaticResource BaseItemTitleSelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#fffffe" /> <Setter Property="Foreground" Value="#f5f5f5" />
</Style> </Style>
<Style <Style
x:Key="ItemSubTitleSelectedStyle" x:Key="ItemSubTitleSelectedStyle"
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#fffffe" /> <Setter Property="Foreground" Value="#ed92c9" />
</Style> </Style>
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#0e172c</SolidColorBrush> <SolidColorBrush x:Key="ItemSelectedBackgroundColor">#cc1081</SolidColorBrush>
<Style <Style
x:Key="ThumbStyle" x:Key="ThumbStyle"
BasedOn="{StaticResource BaseThumbStyle}" BasedOn="{StaticResource BaseThumbStyle}"
@ -86,7 +85,7 @@
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}"> <ControlTemplate TargetType="{x:Type Thumb}">
<Border <Border
Background="#E9819F" Background="#e564b1"
BorderBrush="Transparent" BorderBrush="Transparent"
BorderThickness="0" BorderThickness="0"
CornerRadius="2" CornerRadius="2"
@ -109,56 +108,57 @@
<Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="HorizontalAlignment" Value="Right" />
</Style> </Style>
<Style x:Key="SearchIconStyle" TargetType="{x:Type Path}"> <Style x:Key="SearchIconStyle" TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#E9819F" /> <Setter Property="Fill" Value="#cc1081" />
<Setter Property="Width" Value="28" /> <Setter Property="Width" Value="28" />
<Setter Property="Height" Value="28" /> <Setter Property="Height" Value="28" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="11" /> x:Key="ItemHotkeyStyle"
<Setter Property="Foreground" Value="#E9819F" /> BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ed92c9" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="11" /> x:Key="ItemHotkeySelectedStyle"
<Setter Property="Foreground" Value="#E9819F" /> BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#ed92c9" />
</Style> </Style>
<Style <Style
x:Key="ClockBox" x:Key="ClockBox"
BasedOn="{StaticResource BaseClockBox}" BasedOn="{StaticResource BaseClockBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#E9819F" /> <Setter Property="Foreground" Value="#71114b" />
</Style> </Style>
<Style <Style
x:Key="DateBox" x:Key="DateBox"
BasedOn="{StaticResource BaseDateBox}" BasedOn="{StaticResource BaseDateBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="#E9819F" /> <Setter Property="Foreground" Value="#71114b" />
</Style> </Style>
<Style <Style
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}" BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="Margin" Value="0 0 10 0" /> <Setter Property="BorderBrush" Value="#000000" />
<Setter Property="Width" Value="5" />
<Setter Property="BorderThickness" Value="3 0 0 0" />
<Setter Property="BorderBrush" Value="#0e172c" />
</Style> </Style>
<Style <Style
x:Key="PreviewItemTitleStyle" x:Key="PreviewItemTitleStyle"
BasedOn="{StaticResource BasePreviewItemTitleStyle}" BasedOn="{StaticResource BasePreviewItemTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0E172C" /> <Setter Property="Foreground" Value="#f5f5f5" />
</Style> </Style>
<Style <Style
x:Key="PreviewItemSubTitleStyle" x:Key="PreviewItemSubTitleStyle"
BasedOn="{StaticResource BasePreviewItemSubTitleStyle}" BasedOn="{StaticResource BasePreviewItemSubTitleStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0E172C" /> <Setter Property="Foreground" Value="#c2c2c2" />
</Style> </Style>
<Style <Style
x:Key="PreviewGlyph" x:Key="PreviewGlyph"
BasedOn="{StaticResource BasePreviewGlyph}" BasedOn="{StaticResource BasePreviewGlyph}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#0E172C" /> <Setter Property="Foreground" Value="#ffffff" />
</Style> </Style>
</ResourceDictionary> </ResourceDictionary>

View file

@ -27,7 +27,7 @@
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,2,42,0" /> <Setter Property="Padding" Value="0,0,42,0" />
<Setter Property="Foreground" Value="#282728" /> <Setter Property="Foreground" Value="#282728" />
<Setter Property="FontSize" Value="16" /> <Setter Property="FontSize" Value="16" />
<Setter Property="Height" Value="24" /> <Setter Property="Height" Value="24" />
@ -37,7 +37,7 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,2,42,0" /> <Setter Property="Padding" Value="0,0,42,0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="24" /> <Setter Property="Height" Value="24" />
<Setter Property="FontSize" Value="16" /> <Setter Property="FontSize" Value="16" />

View file

@ -21,7 +21,7 @@
<Setter Property="CaretBrush" Value="#FFAA47" /> <Setter Property="CaretBrush" Value="#FFAA47" />
<Setter Property="FontSize" Value="26" /> <Setter Property="FontSize" Value="26" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
<Setter Property="Padding" Value="0,4,66,0" /> <Setter Property="Padding" Value="0,0,66,0" />
</Style> </Style>
<Style <Style
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
@ -30,7 +30,7 @@
<Setter Property="Foreground" Value="#798189" /> <Setter Property="Foreground" Value="#798189" />
<Setter Property="FontSize" Value="26" /> <Setter Property="FontSize" Value="26" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
<Setter Property="Padding" Value="0,4,66,0" /> <Setter Property="Padding" Value="0,0,66,0" />
</Style> </Style>
<Style <Style
x:Key="WindowBorderStyle" x:Key="WindowBorderStyle"

View file

@ -32,7 +32,7 @@
x:Key="QueryBoxStyle" x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}" BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="CaretBrush" Value="#fa7941" /> <Setter Property="CaretBrush" Value="#fa7941" />
<Setter Property="Foreground" Value="#ffffff" /> <Setter Property="Foreground" Value="#ffffff" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -43,7 +43,7 @@
x:Key="QuerySuggestionBoxStyle" x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,4,50,0" /> <Setter Property="Padding" Value="0 0 50 0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="38" /> <Setter Property="Height" Value="38" />
<Setter Property="FontSize" Value="18" /> <Setter Property="FontSize" Value="18" />
@ -56,15 +56,7 @@
TargetType="{x:Type Border}"> TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="#e2e2e2" /> <Setter Property="BorderBrush" Value="#e2e2e2" />
<Setter Property="Background"> <Setter Property="Background" Value="#2e2e2e" />
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Offset="0.0" Color="#383838" />
<GradientStop Offset="0.9" Color="#2e2e2e" />
<GradientStop Offset="1.0" Color="#2e2e2e" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="CornerRadius" Value="8" /> <Setter Property="CornerRadius" Value="8" />
<Setter Property="UseLayoutRounding" Value="True" /> <Setter Property="UseLayoutRounding" Value="True" />
</Style> </Style>
@ -96,7 +88,7 @@
TargetType="{x:Type Rectangle}"> TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#3b3b3b" /> <Setter Property="Fill" Value="#3b3b3b" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0,0,0,8" /> <Setter Property="Margin" Value="0 0 0 8" />
</Style> </Style>
<Style x:Key="HighlightStyle" /> <Style x:Key="HighlightStyle" />
<Style <Style
@ -147,7 +139,7 @@
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Width" Value="32" /> <Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
<Setter Property="Margin" Value="0,8,8,0" /> <Setter Property="Margin" Value="0 8 8 0" />
<Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="HorizontalAlignment" Value="Right" />
</Style> </Style>
@ -174,7 +166,7 @@
x:Key="ClockPanel" x:Key="ClockPanel"
BasedOn="{StaticResource ClockPanel}" BasedOn="{StaticResource ClockPanel}"
TargetType="{x:Type StackPanel}"> TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="0,0,54,2" /> <Setter Property="Margin" Value="0 0 54 2" />
</Style> </Style>
<Style <Style
x:Key="ClockBox" x:Key="ClockBox"

View file

@ -11,6 +11,10 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">False</system:Boolean>
<system:String x:Key="SystemBG">Auto</system:String>
<Color x:Key="LightBG">#FFFAFAFA</Color>
<Color x:Key="DarkBG">#FF202020</Color>
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness> <Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
<Style <Style
x:Key="ItemGlyph" x:Key="ItemGlyph"
@ -32,7 +36,7 @@
<Setter Property="FontSize" Value="22" /> <Setter Property="FontSize" Value="22" />
<Setter Property="Foreground" Value="{DynamicResource Color05B}" /> <Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="CaretBrush" Value="{DynamicResource Color05B}" /> <Setter Property="CaretBrush" Value="{DynamicResource Color05B}" />
<Setter Property="Padding" Value="0 4 66 0" /> <Setter Property="Padding" Value="0 0 66 0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
<Style <Style
@ -41,7 +45,7 @@
TargetType="{x:Type TextBox}"> TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource QuerySuggestionBoxForeground}" /> <Setter Property="Foreground" Value="{DynamicResource QuerySuggestionBoxForeground}" />
<Setter Property="FontSize" Value="22" /> <Setter Property="FontSize" Value="22" />
<Setter Property="Padding" Value="0 4 66 0" /> <Setter Property="Padding" Value="0 0 66 0" />
<Setter Property="Height" Value="42" /> <Setter Property="Height" Value="42" />
</Style> </Style>
<Style <Style
@ -109,15 +113,18 @@
<Style x:Key="HighlightStyle"> <Style x:Key="HighlightStyle">
<Setter Property="Inline.FontWeight" Value="SemiBold" /> <Setter Property="Inline.FontWeight" Value="SemiBold" />
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style
<Setter Property="FontSize" Value="13" /> x:Key="ItemHotkeyStyle"
BasedOn="{StaticResource BaseItemHotkeyStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="11" />
<Setter Property="Foreground" Value="{DynamicResource HotkeyForeground}" /> <Setter Property="Foreground" Value="{DynamicResource HotkeyForeground}" />
</Style> </Style>
<Style <Style
x:Key="ItemHotkeySelectedStyle" x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}" BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="13" /> <Setter Property="FontSize" Value="11" />
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" /> <Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" />
</Style> </Style>
<!-- button style in the middle of the scrollbar --> <!-- button style in the middle of the scrollbar -->
@ -175,13 +182,13 @@
x:Key="ClockBox" x:Key="ClockBox"
BasedOn="{StaticResource BaseClockBox}" BasedOn="{StaticResource BaseClockBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource ClockDateForeground}" /> <Setter Property="Foreground" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
</Style> </Style>
<Style <Style
x:Key="DateBox" x:Key="DateBox"
BasedOn="{StaticResource BaseDateBox}" BasedOn="{StaticResource BaseDateBox}"
TargetType="{x:Type TextBlock}"> TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource ClockDateForeground}" /> <Setter Property="Foreground" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
</Style> </Style>
<Style <Style
x:Key="PreviewBorderStyle" x:Key="PreviewBorderStyle"

View file

@ -1,7 +1,7 @@
<!-- <!--
Name: Windows 11 Name: Windows 11
IsDark: True IsDark: True
HasBlur: False HasBlur: True
--> -->
<ResourceDictionary <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@ -12,6 +12,10 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
<system:String x:Key="SystemBG">Auto</system:String>
<Color x:Key="LightBG">#BFFAFAFA</Color>
<Color x:Key="DarkBG">#DD202020</Color>
<Style <Style
x:Key="BulletStyle" x:Key="BulletStyle"
BasedOn="{StaticResource BaseBulletStyle}" BasedOn="{StaticResource BaseBulletStyle}"
@ -152,13 +156,26 @@
</Style> </Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="11" />
<Setter Property="Foreground" Value="{DynamicResource HotkeyForeground}" /> <Setter Property="Foreground" Value="{DynamicResource NewHotkeyForeground}" />
</Style> </Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="11" />
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" /> <Setter Property="Foreground" Value="{DynamicResource NewHotkeyForeground}" />
</Style> </Style>
<Style
x:Key="ItemHotkeyBGStyle"
BasedOn="{StaticResource BaseItemHotkeyBGStyle}"
TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource BasicHotkeyBGColor}" />
</Style>
<Style
x:Key="ItemHotkeyBGSelectedStyle"
BasedOn="{StaticResource BaseItemHotkeyBGSelectedStyle}"
TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource BasicHotkeyBGColor}" />
</Style>
<Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color05B}" /> <Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Core.Plugin;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Plugin;
using Flow.Launcher.Resources.Controls; using Flow.Launcher.Resources.Controls;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
@ -28,7 +28,7 @@ namespace Flow.Launcher.ViewModel
} }
} }
private string PluginManagerActionKeyword private static string PluginManagerActionKeyword
{ {
get get
{ {
@ -43,10 +43,10 @@ namespace Flow.Launcher.ViewModel
} }
} }
private async Task LoadIconAsync()
private async void LoadIconAsync()
{ {
Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath); Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath);
OnPropertyChanged(nameof(Image));
} }
public ImageSource Image public ImageSource Image
@ -54,12 +54,13 @@ namespace Flow.Launcher.ViewModel
get get
{ {
if (_image == ImageLoader.MissingImage) if (_image == ImageLoader.MissingImage)
LoadIconAsync(); _ = LoadIconAsync();
return _image; return _image;
} }
set => _image = value; set => _image = value;
} }
public bool PluginState public bool PluginState
{ {
get => !PluginPair.Metadata.Disabled; get => !PluginPair.Metadata.Disabled;
@ -69,6 +70,7 @@ namespace Flow.Launcher.ViewModel
PluginSettingsObject.Disabled = !value; PluginSettingsObject.Disabled = !value;
} }
} }
public bool IsExpanded public bool IsExpanded
{ {
get => _isExpanded; get => _isExpanded;
@ -81,6 +83,16 @@ namespace Flow.Launcher.ViewModel
} }
} }
public SearchDelayTime? PluginSearchDelayTime
{
get => PluginPair.Metadata.SearchDelayTime;
set
{
PluginPair.Metadata.SearchDelayTime = value;
PluginSettingsObject.SearchDelayTime = value;
}
}
private Control _settingControl; private Control _settingControl;
private bool _isExpanded; private bool _isExpanded;
@ -88,9 +100,13 @@ namespace Flow.Launcher.ViewModel
public Control BottomPart1 => IsExpanded ? _bottomPart1 ??= new InstalledPluginDisplayKeyword() : null; public Control BottomPart1 => IsExpanded ? _bottomPart1 ??= new InstalledPluginDisplayKeyword() : null;
private Control _bottomPart2; private Control _bottomPart2;
public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginSearchDelay() : null;
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel()); private Control _bottomPart3;
public Control BottomPart3 => IsExpanded ? _bottomPart3 ??= new InstalledPluginDisplayBottomData() : null;
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider &&
(PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel());
public Control SettingControl public Control SettingControl
=> IsExpanded => IsExpanded
? _settingControl ? _settingControl
@ -100,21 +116,33 @@ namespace Flow.Launcher.ViewModel
: null; : null;
private ImageSource _image = ImageLoader.MissingImage; private ImageSource _image = ImageLoader.MissingImage;
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed; public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
public string InitilizaTime => PluginPair.Metadata.InitTime + "ms"; Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms"; public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
public string Version => InternationalizationManager.Instance.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version; public string Version => App.API.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version;
public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string InitAndQueryTime =>
App.API.GetTranslation("plugin_init_time") + " " +
PluginPair.Metadata.InitTime + "ms, " +
App.API.GetTranslation("plugin_query_time") + " " +
PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public int Priority => PluginPair.Metadata.Priority; public int Priority => PluginPair.Metadata.Priority;
public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; } public string SearchDelayTimeText => PluginPair.Metadata.SearchDelayTime == null ?
App.API.GetTranslation("default") :
App.API.GetTranslation($"SearchDelayTime{PluginPair.Metadata.SearchDelayTime}");
public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; }
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) public void OnActionKeywordsChanged()
{ {
PluginManager.ReplaceActionKeyword(PluginPair.Metadata.ID, oldActionKeyword, newActionKeyword);
OnPropertyChanged(nameof(ActionKeywordsText)); OnPropertyChanged(nameof(ActionKeywordsText));
} }
public void OnSearchDelayTimeChanged()
{
OnPropertyChanged(nameof(SearchDelayTimeText));
}
public void ChangePriority(int newPriority) public void ChangePriority(int newPriority)
{ {
PluginPair.Metadata.Priority = newPriority; PluginPair.Metadata.Priority = newPriority;
@ -125,7 +153,7 @@ namespace Flow.Launcher.ViewModel
[RelayCommand] [RelayCommand]
private void EditPluginPriority() private void EditPluginPriority()
{ {
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this); var priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
priorityChangeWindow.ShowDialog(); priorityChangeWindow.ShowDialog();
} }
@ -150,13 +178,18 @@ namespace Flow.Launcher.ViewModel
App.API.ShowMainWindow(); App.API.ShowMainWindow();
} }
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
[RelayCommand] [RelayCommand]
private void SetActionKeywords() private void SetActionKeywords()
{ {
ActionKeywords changeKeywordsWindow = new ActionKeywords(this); var changeKeywordsWindow = new ActionKeywords(this);
changeKeywordsWindow.ShowDialog(); changeKeywordsWindow.ShowDialog();
} }
[RelayCommand]
private void SetSearchDelayTime()
{
var searchDelayTimeWindow = new SearchDelayTimeWindow(this);
searchDelayTimeWindow.ShowDialog();
}
} }
} }

View file

@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -6,25 +9,20 @@ using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.IO;
using System.Drawing.Text;
using System.Collections.Generic;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
public class ResultViewModel : BaseModel public class ResultViewModel : BaseModel
{ {
private static PrivateFontCollection fontCollection = new(); private static readonly PrivateFontCollection FontCollection = new();
private static Dictionary<string, string> fonts = new(); private static readonly Dictionary<string, string> Fonts = new();
public ResultViewModel(Result result, Settings settings) public ResultViewModel(Result result, Settings settings)
{ {
Settings = settings; Settings = settings;
if (result == null) if (result == null) return;
{
return;
}
Result = result; Result = result;
if (Result.Glyph is { FontFamily: not null } glyph) if (Result.Glyph is { FontFamily: not null } glyph)
@ -39,20 +37,20 @@ namespace Flow.Launcher.ViewModel
fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath); fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath);
} }
if (fonts.ContainsKey(fontFamilyPath)) if (Fonts.TryGetValue(fontFamilyPath, out var value))
{ {
Glyph = glyph with Glyph = glyph with
{ {
FontFamily = fonts[fontFamilyPath] FontFamily = value
}; };
} }
else else
{ {
fontCollection.AddFontFile(fontFamilyPath); FontCollection.AddFontFile(fontFamilyPath);
fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}"; Fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{FontCollection.Families[^1].Name}";
Glyph = glyph with Glyph = glyph with
{ {
FontFamily = fonts[fontFamilyPath] FontFamily = Fonts[fontFamilyPath]
}; };
} }
} }
@ -61,7 +59,6 @@ namespace Flow.Launcher.ViewModel
Glyph = glyph; Glyph = glyph;
} }
} }
} }
public Settings Settings { get; } public Settings Settings { get; }
@ -95,14 +92,10 @@ namespace Flow.Launcher.ViewModel
get get
{ {
if (PreviewImageAvailable) if (PreviewImageAvailable)
{
return Visibility.Visible; return Visibility.Visible;
}
else // Fall back to icon
{ return ShowIcon;
// Fall back to icon
return ShowIcon;
}
} }
} }
@ -111,9 +104,8 @@ namespace Flow.Launcher.ViewModel
get get
{ {
if (Result.RoundedIcon) if (Result.RoundedIcon)
{
return IconXY / 2; return IconXY / 2;
}
return IconXY; return IconXY;
} }
@ -148,31 +140,40 @@ namespace Flow.Launcher.ViewModel
? Result.SubTitle ? Result.SubTitle
: Result.SubTitleToolTip; : Result.SubTitleToolTip;
private volatile bool ImageLoaded; private volatile bool _imageLoaded;
private volatile bool PreviewImageLoaded; private volatile bool _previewImageLoaded;
private ImageSource image = ImageLoader.LoadingImage; private ImageSource _image = ImageLoader.LoadingImage;
private ImageSource previewImage = ImageLoader.LoadingImage; private ImageSource _previewImage = ImageLoader.LoadingImage;
public ImageSource Image public ImageSource Image
{ {
get get
{ {
if (!ImageLoaded) if (!_imageLoaded)
{ {
ImageLoaded = true; _imageLoaded = true;
_ = LoadImageAsync(); _ = LoadImageAsync();
} }
return image; return _image;
} }
private set => image = value; private set => _image = value;
} }
public ImageSource PreviewImage public ImageSource PreviewImage
{ {
get => previewImage; get
private set => previewImage = value; {
if (!_previewImageLoaded)
{
_previewImageLoaded = true;
_ = LoadPreviewImageAsync();
}
return _previewImage;
}
private set => _previewImage = value;
} }
/// <summary> /// <summary>
@ -188,8 +189,7 @@ namespace Flow.Launcher.ViewModel
{ {
try try
{ {
var image = icon(); return icon();
return image;
} }
catch (Exception e) catch (Exception e)
{ {
@ -208,7 +208,7 @@ namespace Flow.Launcher.ViewModel
var iconDelegate = Result.Icon; var iconDelegate = Result.Icon;
if (ImageLoader.TryGetValue(imagePath, false, out ImageSource img)) if (ImageLoader.TryGetValue(imagePath, false, out ImageSource img))
{ {
image = img; _image = img;
} }
else else
{ {
@ -223,7 +223,7 @@ namespace Flow.Launcher.ViewModel
var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon; var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon;
if (ImageLoader.TryGetValue(imagePath, true, out ImageSource img)) if (ImageLoader.TryGetValue(imagePath, true, out ImageSource img))
{ {
previewImage = img; _previewImage = img;
} }
else else
{ {
@ -234,13 +234,10 @@ namespace Flow.Launcher.ViewModel
public void LoadPreviewImage() public void LoadPreviewImage()
{ {
if (ShowDefaultPreview == Visibility.Visible) if (ShowDefaultPreview == Visibility.Visible && !_previewImageLoaded && ShowPreviewImage == Visibility.Visible)
{ {
if (!PreviewImageLoaded && ShowPreviewImage == Visibility.Visible) _previewImageLoaded = true;
{ _ = LoadPreviewImageAsync();
PreviewImageLoaded = true;
_ = LoadPreviewImageAsync();
}
} }
} }

Some files were not shown because too many files have changed in this diff Show more