mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into force_foreground_window
This commit is contained in:
commit
f64ca66743
92 changed files with 2272 additions and 897 deletions
|
|
@ -22,7 +22,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private UpdateManager NewUpdateManager()
|
||||
private static UpdateManager NewUpdateManager()
|
||||
{
|
||||
var applicationFolderName = Constant.ApplicationDirectory
|
||||
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
|
||||
|
|
@ -81,20 +81,16 @@ namespace Flow.Launcher.Core.Configuration
|
|||
|
||||
public void RemoveShortcuts()
|
||||
{
|
||||
using (var portabilityUpdater = NewUpdateManager())
|
||||
{
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
|
||||
}
|
||||
using var portabilityUpdater = NewUpdateManager();
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu);
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop);
|
||||
portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup);
|
||||
}
|
||||
|
||||
public void RemoveUninstallerEntry()
|
||||
{
|
||||
using (var portabilityUpdater = NewUpdateManager())
|
||||
{
|
||||
portabilityUpdater.RemoveUninstallerRegistryEntry();
|
||||
}
|
||||
using var portabilityUpdater = NewUpdateManager();
|
||||
portabilityUpdater.RemoveUninstallerRegistryEntry();
|
||||
}
|
||||
|
||||
public void MoveUserDataFolder(string fromLocation, string toLocation)
|
||||
|
|
@ -110,12 +106,10 @@ namespace Flow.Launcher.Core.Configuration
|
|||
|
||||
public void CreateShortcuts()
|
||||
{
|
||||
using (var portabilityUpdater = NewUpdateManager())
|
||||
{
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
|
||||
}
|
||||
using var portabilityUpdater = NewUpdateManager();
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false);
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false);
|
||||
portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false);
|
||||
}
|
||||
|
||||
public void CreateUninstallerEntry()
|
||||
|
|
@ -129,18 +123,14 @@ namespace Flow.Launcher.Core.Configuration
|
|||
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
|
||||
}
|
||||
|
||||
using (var portabilityUpdater = NewUpdateManager())
|
||||
{
|
||||
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
|
||||
}
|
||||
using var portabilityUpdater = NewUpdateManager();
|
||||
_ = portabilityUpdater.CreateUninstallerRegistryEntry();
|
||||
}
|
||||
|
||||
internal void IndicateDeletion(string filePathTodelete)
|
||||
private static void IndicateDeletion(string filePathTodelete)
|
||||
{
|
||||
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
|
||||
using (var _ = File.CreateText(deleteFilePath))
|
||||
{
|
||||
}
|
||||
using var _ = File.CreateText(deleteFilePath);
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
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
|
||||
{
|
||||
|
|
@ -42,8 +42,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
internal IEnumerable<PluginPair> Setup()
|
||||
{
|
||||
// If no plugin is using the language, return empty list
|
||||
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return new List<PluginPair>();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
|
||||
{
|
||||
|
|
@ -55,24 +58,55 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
}
|
||||
|
||||
var noRuntimeMessage = string.Format(
|
||||
InternationalizationManager.Instance.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
|
||||
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
|
||||
Language,
|
||||
EnvName,
|
||||
Environment.NewLine
|
||||
);
|
||||
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
||||
string selectedFile;
|
||||
var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
||||
|
||||
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
||||
var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedFile))
|
||||
{
|
||||
PluginsSettingsFilePath = selectedFile;
|
||||
|
||||
}
|
||||
// Nothing selected because user pressed cancel from the file dialog window
|
||||
if (string.IsNullOrEmpty(selectedFile))
|
||||
InstallEnvironment();
|
||||
else
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -85,7 +119,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
}
|
||||
else
|
||||
{
|
||||
API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
||||
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
||||
Log.Error("PluginsLoader",
|
||||
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
||||
$"{Language}Environment");
|
||||
|
|
@ -98,13 +132,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
|
||||
{
|
||||
if (expectedPath == currentPath)
|
||||
return;
|
||||
if (expectedPath == currentPath) return;
|
||||
|
||||
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
|
||||
|
||||
InstallEnvironment();
|
||||
|
||||
}
|
||||
|
||||
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
|
||||
|
|
@ -116,13 +148,16 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
foreach (var metadata in PluginMetadataList)
|
||||
{
|
||||
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
metadata.AssemblyName = string.Empty;
|
||||
pluginPairs.Add(CreatePluginPair(filePath, metadata));
|
||||
}
|
||||
}
|
||||
|
||||
return pluginPairs;
|
||||
}
|
||||
|
||||
private string GetFileFromDialog(string title, string filter = "")
|
||||
private static string GetFileFromDialog(string title, string filter = "")
|
||||
{
|
||||
var dlg = new OpenFileDialog
|
||||
{
|
||||
|
|
@ -136,7 +171,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
|
||||
var result = dlg.ShowDialog();
|
||||
return result == DialogResult.OK ? dlg.FileName : string.Empty;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -179,31 +213,33 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
else
|
||||
{
|
||||
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
|
||||
{
|
||||
settings.PluginSettings.PythonExecutablePath
|
||||
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
|
||||
}
|
||||
|
||||
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
|
||||
{
|
||||
settings.PluginSettings.NodeExecutablePath
|
||||
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
|
||||
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
|
||||
// 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);
|
||||
}
|
||||
|
||||
private static bool IsUsingRoamingPath(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
|
||||
return filePath.StartsWith(DataLocation.RoamingDataPath);
|
||||
}
|
||||
|
|
@ -213,8 +249,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
|
||||
|
||||
// get the substring after "Environments" because we can not determine it dynamically
|
||||
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count());
|
||||
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}";
|
||||
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
|
||||
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Droplex;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Droplex;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
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 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) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using Droplex;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.IO;
|
||||
using Droplex;
|
||||
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
|
||||
{
|
||||
|
|
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
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 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) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using Droplex;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.IO;
|
||||
using Droplex;
|
||||
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
|
||||
{
|
||||
|
|
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
|||
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 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) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,16 @@
|
|||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Microsoft.IO;
|
||||
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
|
||||
{
|
||||
|
|
@ -42,7 +30,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
private int RequestId { get; set; }
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,32 +1,15 @@
|
|||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Microsoft.IO;
|
||||
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;
|
||||
using static System.Windows.Forms.LinkLabel;
|
||||
using Droplex;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -44,8 +27,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
private string SettingConfigurationPath =>
|
||||
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||
|
||||
private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory,
|
||||
Context.CurrentPluginMetadata.Name);
|
||||
private string SettingDirectory => Context.CurrentPluginMetadata.PluginSettingsDirectoryPath;
|
||||
|
||||
private string SettingPath => Path.Combine(SettingDirectory, "Settings.json");
|
||||
|
||||
|
|
@ -166,13 +148,5 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
return Settings.CreateSettingPanel();
|
||||
}
|
||||
|
||||
public void DeletePluginSettingsDirectory()
|
||||
{
|
||||
if (Directory.Exists(SettingDirectory))
|
||||
{
|
||||
Directory.Delete(SettingDirectory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
protected ConcurrentDictionary<string, object?> Settings { get; set; } = null!;
|
||||
public required IPublicAPI API { get; init; }
|
||||
|
||||
private static readonly string ClassName = nameof(JsonRPCPluginSettings);
|
||||
|
||||
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
|
||||
|
||||
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
|
||||
|
|
@ -122,12 +124,26 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public async Task SaveAsync()
|
||||
{
|
||||
await _storage.SaveAsync();
|
||||
try
|
||||
{
|
||||
await _storage.SaveAsync();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
try
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedCreateSettingPanel()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
|
@ -9,7 +9,6 @@ using System.Text.Json;
|
|||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
||||
internal abstract class PluginConfig
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -112,7 +111,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
|
||||
metadata.PluginDirectory = pluginDirectory;
|
||||
// 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
|
||||
metadata.ActionKeyword = metadata.ActionKeywords?[0];
|
||||
}
|
||||
|
|
@ -137,4 +136,4 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System.Text.Json;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -35,7 +34,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static PluginsSettings Settings;
|
||||
private static List<PluginMetadata> _metadatas;
|
||||
private static List<string> _modifiedPlugins = new List<string>();
|
||||
private static List<string> _modifiedPlugins = new();
|
||||
|
||||
/// <summary>
|
||||
/// Directories that will hold Flow Launcher plugin directory
|
||||
|
|
@ -72,15 +71,20 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
foreach (var pluginPair in AllPlugins)
|
||||
{
|
||||
switch (pluginPair.Plugin)
|
||||
{
|
||||
case IDisposable disposable:
|
||||
disposable.Dispose();
|
||||
break;
|
||||
case IAsyncDisposable asyncDisposable:
|
||||
await asyncDisposable.DisposeAsync();
|
||||
break;
|
||||
}
|
||||
await DisposePluginAsync(pluginPair);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DisposePluginAsync(PluginPair pluginPair)
|
||||
{
|
||||
switch (pluginPair.Plugin)
|
||||
{
|
||||
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.UpdatePluginSettings(_metadatas);
|
||||
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>
|
||||
|
|
@ -225,10 +248,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
if (query is null)
|
||||
return Array.Empty<PluginPair>();
|
||||
|
||||
if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword))
|
||||
if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
|
||||
return GlobalPlugins;
|
||||
|
||||
var plugin = NonGlobalPlugins[query.ActionKeyword];
|
||||
return new List<PluginPair>
|
||||
{
|
||||
plugin
|
||||
|
|
@ -442,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,
|
||||
/// unless it's a local path installation
|
||||
/// </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);
|
||||
UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
|
||||
await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
|
||||
_modifiedPlugins.Add(existingVersion.ID);
|
||||
}
|
||||
|
||||
|
|
@ -460,9 +482,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
/// <summary>
|
||||
/// Uninstall a plugin.
|
||||
/// </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
|
||||
|
|
@ -543,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))
|
||||
{
|
||||
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 (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 pluginJsonStorage = method?.Invoke(API, new object[] { 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));
|
||||
}
|
||||
}
|
||||
method?.Invoke(API, new object[] { plugin.AssemblyName });
|
||||
}
|
||||
else // the plugin with json prc interface
|
||||
|
||||
try
|
||||
{
|
||||
var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID);
|
||||
if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
jsonRpcPlugin.DeletePluginSettingsDirectory();
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
var pluginSettingsDirectory = plugin.PluginSettingsDirectoryPath;
|
||||
if (Directory.Exists(pluginSettingsDirectory))
|
||||
Directory.Delete(pluginSettingsDirectory, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e);
|
||||
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return plugins;
|
||||
}
|
||||
|
||||
public static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
|
||||
private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
|
||||
{
|
||||
var erroredPlugins = new List<string>();
|
||||
|
||||
|
|
@ -74,9 +74,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
typeof(IAsyncPlugin));
|
||||
|
||||
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
|
||||
|
||||
metadata.AssemblyName = assembly.GetName().Name;
|
||||
}
|
||||
#if DEBUG
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
|
@ -112,7 +114,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
if (erroredPlugins.Count > 0)
|
||||
{
|
||||
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);
|
||||
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
|
||||
|
||||
var errorMessage = "The following "
|
||||
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
|
||||
|
|
@ -130,23 +132,31 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return plugins;
|
||||
}
|
||||
|
||||
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||
private static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||
{
|
||||
return source
|
||||
.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
|
||||
.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
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public bool BlurEnabled { get; set; }
|
||||
public bool BlurEnabled { get; private set; }
|
||||
|
||||
private const string ThemeMetadataNamePrefix = "Name:";
|
||||
private const string ThemeMetadataIsDarkPrefix = "IsDark:";
|
||||
|
|
@ -42,6 +42,8 @@ namespace Flow.Launcher.Core.Resource
|
|||
private static string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
|
||||
private static string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
|
||||
|
||||
private Thickness _themeResizeBorderThickness;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
|
@ -463,7 +465,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
var effectSetter = new Setter
|
||||
{
|
||||
Property = Border.EffectProperty,
|
||||
Property = UIElement.EffectProperty,
|
||||
Value = new DropShadowEffect
|
||||
{
|
||||
Opacity = 0.3,
|
||||
|
|
@ -473,12 +475,12 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
};
|
||||
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is not Setter marginSetter)
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is not Setter marginSetter)
|
||||
{
|
||||
var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin);
|
||||
marginSetter = new Setter()
|
||||
{
|
||||
Property = Border.MarginProperty,
|
||||
Property = FrameworkElement.MarginProperty,
|
||||
Value = margin,
|
||||
};
|
||||
windowBorderStyle.Setters.Add(marginSetter);
|
||||
|
|
@ -508,12 +510,12 @@ namespace Flow.Launcher.Core.Resource
|
|||
var dict = GetCurrentResourceDictionary();
|
||||
var windowBorderStyle = dict["WindowBorderStyle"] as Style;
|
||||
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) is Setter effectSetter)
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == UIElement.EffectProperty) is Setter effectSetter)
|
||||
{
|
||||
windowBorderStyle.Setters.Remove(effectSetter);
|
||||
}
|
||||
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is Setter marginSetter)
|
||||
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is Setter marginSetter)
|
||||
{
|
||||
var currentMargin = (Thickness)marginSetter.Value;
|
||||
var newMargin = new Thickness(
|
||||
|
|
@ -529,28 +531,41 @@ namespace Flow.Launcher.Core.Resource
|
|||
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,
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
thickness = SystemParameters.WindowResizeBorderThickness;
|
||||
_themeResizeBorderThickness = SystemParameters.WindowResizeBorderThickness;
|
||||
}
|
||||
else
|
||||
{
|
||||
thickness = new Thickness(
|
||||
_themeResizeBorderThickness = new Thickness(
|
||||
effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left,
|
||||
effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top,
|
||||
effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right,
|
||||
effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||
}
|
||||
|
||||
windowChrome.ResizeBorderThickness = thickness;
|
||||
// Apply the resize border thickness to the window chrome
|
||||
SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -582,7 +597,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
AutoDropShadow(useDropShadowEffect);
|
||||
}
|
||||
}, DispatcherPriority.Normal);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -596,7 +611,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
var (backdropType, _) = GetActualValue();
|
||||
|
||||
SetBlurForWindow(GetCurrentTheme(), backdropType);
|
||||
}, DispatcherPriority.Normal);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using JetBrains.Annotations;
|
||||
using Squirrel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
|
|
@ -15,8 +17,8 @@ using Flow.Launcher.Infrastructure.Http;
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Squirrel;
|
||||
|
||||
namespace Flow.Launcher.Core
|
||||
{
|
||||
|
|
@ -51,7 +53,7 @@ namespace Flow.Launcher.Core
|
|||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = Version.Parse(Constant.Version);
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
|
||||
Log.Info($"|Updater.UpdateApp|Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
|
||||
|
||||
if (newReleaseVersion <= currentVersion)
|
||||
{
|
||||
|
|
@ -70,7 +72,7 @@ namespace Flow.Launcher.Core
|
|||
|
||||
if (DataLocation.PortableDataLocationInUse())
|
||||
{
|
||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
|
||||
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||
|
|
@ -122,7 +124,7 @@ namespace Flow.Launcher.Core
|
|||
}
|
||||
|
||||
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
|
||||
private async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
|
||||
private static async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
|
||||
{
|
||||
var uri = new Uri(repository);
|
||||
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
|
||||
|
|
@ -144,12 +146,22 @@ namespace Flow.Launcher.Core
|
|||
return manager;
|
||||
}
|
||||
|
||||
public string NewVersionTips(string version)
|
||||
private static string NewVersionTips(string version)
|
||||
{
|
||||
var translator = InternationalizationManager.Instance;
|
||||
var translator = Ioc.Default.GetRequiredService<Internationalization>();
|
||||
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);
|
||||
|
||||
return tips;
|
||||
}
|
||||
|
||||
private static string Formatted<T>(T t)
|
||||
{
|
||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
public const string Themes = "Themes";
|
||||
public const string Settings = "Settings";
|
||||
public const string Logs = "Logs";
|
||||
public const string Cache = "Cache";
|
||||
|
||||
public const string Website = "https://flowlauncher.com";
|
||||
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";
|
||||
|
|
|
|||
|
|
@ -1,19 +1,11 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
static Helper()
|
||||
{
|
||||
jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
||||
/// </summary>
|
||||
|
|
@ -36,55 +28,5 @@ namespace Flow.Launcher.Infrastructure
|
|||
throw new NullReferenceException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory)
|
||||
{
|
||||
if (!Directory.Exists(dataDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
}
|
||||
|
||||
foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory))
|
||||
{
|
||||
var data = Path.GetFileName(bundledDataPath);
|
||||
var dataPath = Path.Combine(dataDirectory, data.NonNull());
|
||||
if (!File.Exists(dataPath))
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc;
|
||||
var time2 = new FileInfo(dataPath).LastWriteTimeUtc;
|
||||
if (time1 != time2)
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static string Formatted<T>(this T t)
|
||||
{
|
||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
});
|
||||
}
|
||||
|
||||
public static async Task Save()
|
||||
public static async Task SaveAsync()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
|
||||
|
|
@ -71,12 +71,22 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
.Select(x => x.Key)
|
||||
.ToList());
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
storageLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WaitSaveAsync()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
storageLock.Release();
|
||||
}
|
||||
|
||||
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
|
||||
{
|
||||
await storageLock.WaitAsync();
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
{
|
||||
public static class Log
|
||||
{
|
||||
public const string DirectoryName = "Logs";
|
||||
public const string DirectoryName = Constant.Logs;
|
||||
|
||||
public static string CurrentLogDirectory { get; }
|
||||
|
||||
static Log()
|
||||
{
|
||||
CurrentLogDirectory = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Version);
|
||||
CurrentLogDirectory = DataLocation.VersionLogDirectory;
|
||||
if (!Directory.Exists(CurrentLogDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(CurrentLogDirectory);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using MemoryPack;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
|
|
@ -16,19 +12,17 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// Normally, it has better performance, but not readable
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable
|
||||
/// https://github.com/Cysharp/MemoryPack
|
||||
/// It utilize MemoryPack, which means the object must be MemoryPackSerializable <see href="https://github.com/Cysharp/MemoryPack"/>
|
||||
/// </remarks>
|
||||
public class BinaryStorage<T>
|
||||
{
|
||||
const string DirectoryName = "Cache";
|
||||
public const string FileSuffix = ".cache";
|
||||
|
||||
const string FileSuffix = ".cache";
|
||||
|
||||
public BinaryStorage(string filename)
|
||||
// Let the derived class to set the file path
|
||||
public BinaryStorage(string filename, string directoryPath = null)
|
||||
{
|
||||
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||
Helper.ValidateDirectory(directoryPath);
|
||||
directoryPath ??= DataLocation.CacheDirectory;
|
||||
FilesFolders.ValidateDirectory(directoryPath);
|
||||
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
|
@ -58,14 +52,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
|
||||
{
|
||||
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
|
||||
return t;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
catch (System.Exception)
|
||||
{
|
||||
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
|
||||
return defaultData;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,51 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
public class FlowLauncherJsonStorage<T> : JsonStorage<T> where T : new()
|
||||
{
|
||||
private static readonly string ClassName = "FlowLauncherJsonStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public FlowLauncherJsonStorage()
|
||||
{
|
||||
var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
|
||||
Helper.ValidateDirectory(directoryPath);
|
||||
FilesFolders.ValidateDirectory(directoryPath);
|
||||
|
||||
var filename = typeof(T).Name;
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
|
||||
public new void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Save();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public new async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await base.SaveAsync();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
protected T? Data;
|
||||
|
||||
// need a new directory name
|
||||
public const string DirectoryName = "Settings";
|
||||
public const string DirectoryName = Constant.Settings;
|
||||
public const string FileSuffix = ".json";
|
||||
|
||||
protected string FilePath { get; init; } = null!;
|
||||
|
|
@ -37,7 +38,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
FilePath = filePath;
|
||||
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
|
||||
|
||||
Helper.ValidateDirectory(DirectoryPath);
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
}
|
||||
|
||||
public async Task<T> LoadAsync()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
{
|
||||
|
|
@ -8,13 +12,19 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
// Use assembly name to check which plugin is using this storage
|
||||
public readonly string AssemblyName;
|
||||
|
||||
private static readonly string ClassName = "PluginJsonStorage";
|
||||
|
||||
// We should not initialize API in static constructor because it will create another API instance
|
||||
private static IPublicAPI api = null;
|
||||
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
|
||||
public PluginJsonStorage()
|
||||
{
|
||||
// C# related, add python related below
|
||||
var dataType = typeof(T);
|
||||
AssemblyName = dataType.Assembly.GetName().Name;
|
||||
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName);
|
||||
Helper.ValidateDirectory(DirectoryPath);
|
||||
DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName);
|
||||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
||||
}
|
||||
|
|
@ -24,11 +34,27 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
Data = data;
|
||||
}
|
||||
|
||||
public void DeleteDirectory()
|
||||
public new void Save()
|
||||
{
|
||||
if (Directory.Exists(DirectoryPath))
|
||||
try
|
||||
{
|
||||
Directory.Delete(DirectoryPath, true);
|
||||
base.Save();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public new async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await base.SaveAsync();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
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 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 NodeEnvironmentName = "Node.js";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||
|
|
@ -6,8 +7,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public class PluginsSettings : BaseModel
|
||||
{
|
||||
private string pythonExecutablePath = string.Empty;
|
||||
public string PythonExecutablePath {
|
||||
get { return pythonExecutablePath; }
|
||||
public string PythonExecutablePath
|
||||
{
|
||||
get => pythonExecutablePath;
|
||||
set
|
||||
{
|
||||
pythonExecutablePath = value;
|
||||
|
|
@ -18,7 +20,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
private string nodeExecutablePath = string.Empty;
|
||||
public string NodeExecutablePath
|
||||
{
|
||||
get { return nodeExecutablePath; }
|
||||
get => nodeExecutablePath;
|
||||
set
|
||||
{
|
||||
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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
metadata.ActionKeywords = settings.ActionKeywords;
|
||||
|
|
@ -51,30 +66,65 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
}
|
||||
metadata.Disabled = settings.Disabled;
|
||||
metadata.Priority = settings.Priority;
|
||||
metadata.SearchDelayTime = settings.SearchDelayTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If settings does not exist, create a new one
|
||||
Plugins[metadata.ID] = new Plugin
|
||||
{
|
||||
ID = metadata.ID,
|
||||
Name = metadata.Name,
|
||||
Version = metadata.Version,
|
||||
ActionKeywords = metadata.ActionKeywords,
|
||||
DefaultActionKeywords = metadata.ActionKeywords, // metadata provides default values
|
||||
ActionKeywords = metadata.ActionKeywords, // use default value
|
||||
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 string ID { get; set; }
|
||||
|
||||
public string Name { 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; }
|
||||
|
||||
[JsonIgnore]
|
||||
public SearchDelayTime? DefaultSearchDelayTime { get; set; }
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public SearchDelayTime? SearchDelayTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used only to save the state of the plugin in settings
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -68,11 +68,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
get => _theme;
|
||||
set
|
||||
{
|
||||
if (value == _theme)
|
||||
return;
|
||||
_theme = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(MaxResultsToShow));
|
||||
if (value != _theme)
|
||||
{
|
||||
_theme = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(MaxResultsToShow));
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool UseDropShadowEffect { get; set; } = true;
|
||||
|
|
@ -111,7 +112,34 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public double SettingWindowHeight { get; set; } = 700;
|
||||
public double? SettingWindowTop { 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;
|
||||
|
||||
|
|
@ -241,10 +269,26 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
/// </summary>
|
||||
public double CustomWindowTop { get; set; } = 0;
|
||||
|
||||
public bool KeepMaxResults { get; set; } = false;
|
||||
public int MaxResultsToShow { get; set; } = 5;
|
||||
public int ActivateTimes { get; set; }
|
||||
/// <summary>
|
||||
/// Fixed window size
|
||||
/// </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>();
|
||||
|
||||
|
|
@ -266,7 +310,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
bool _hideNotifyIcon { get; set; }
|
||||
public bool HideNotifyIcon
|
||||
{
|
||||
get { return _hideNotifyIcon; }
|
||||
get => _hideNotifyIcon;
|
||||
set
|
||||
{
|
||||
_hideNotifyIcon = value;
|
||||
|
|
@ -276,6 +320,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public bool LeaveCmdOpen { get; set; }
|
||||
public bool HideWhenDeactivated { get; set; } = true;
|
||||
|
||||
public bool SearchQueryResultsWithDelay { get; set; }
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public SearchDelayTime SearchDelayTime { get; set; } = SearchDelayTime.Normal;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
|
||||
|
||||
|
|
@ -298,7 +347,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
[JsonIgnore]
|
||||
public bool WMPInstalled { get; set; } = true;
|
||||
|
||||
|
||||
// This needs to be loaded last by staying at the bottom
|
||||
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
|
||||
|
||||
|
|
|
|||
|
|
@ -506,5 +506,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification
|
||||
|
||||
public static bool IsNotificationSupported()
|
||||
{
|
||||
// Notifications only supported on Windows 10 19041+
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
Environment.OSVersion.Version.Build >= 19041;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ namespace Flow.Launcher.Plugin
|
|||
(WinPressed ? ModifierKeys.Windows : ModifierKeys.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="SpecialKeyState"/> object with all keys not pressed.
|
||||
/// </summary>
|
||||
public static readonly SpecialKeyState Default = new () {
|
||||
CtrlPressed = false,
|
||||
ShiftPressed = false,
|
||||
|
|
|
|||
|
|
@ -4,17 +4,42 @@ using System.Threading;
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for plugins that want to manually update their results
|
||||
/// </summary>
|
||||
public interface IResultUpdated : IFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is triggered when the results are updated
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for the ResultsUpdated event
|
||||
/// </summary>
|
||||
public class ResultUpdatedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// List of results that should be displayed
|
||||
/// </summary>
|
||||
public List<Result> Results;
|
||||
|
||||
/// <summary>
|
||||
/// Query that triggered the update
|
||||
/// </summary>
|
||||
public Query Query;
|
||||
|
||||
/// <summary>
|
||||
/// Token that can be used to cancel the update
|
||||
/// </summary>
|
||||
public CancellationToken Token { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used to create settings panel for .Net plugins
|
||||
/// </summary>
|
||||
public interface ISettingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Create settings panel control for .Net plugins
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Control CreateSettingPanel();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,18 @@
|
|||
/// </summary>
|
||||
public class PluginInitContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public PluginInitContext()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="currentPluginMetadata"></param>
|
||||
/// <param name="api"></param>
|
||||
public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api)
|
||||
{
|
||||
CurrentPluginMetadata = currentPluginMetadata;
|
||||
|
|
|
|||
|
|
@ -4,24 +4,77 @@ using System.Text.Json.Serialization;
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin metadata
|
||||
/// </summary>
|
||||
public class PluginMetadata : BaseModel
|
||||
{
|
||||
private string _pluginDirectory;
|
||||
/// <summary>
|
||||
/// Plugin ID.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <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
|
||||
{
|
||||
get { return _pluginDirectory; }
|
||||
get => _pluginDirectory;
|
||||
internal set
|
||||
{
|
||||
_pluginDirectory = value;
|
||||
|
|
@ -30,30 +83,78 @@ namespace Flow.Launcher.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The first action keyword of plugin.
|
||||
/// </summary>
|
||||
public string ActionKeyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All action keywords of plugin.
|
||||
/// </summary>
|
||||
public List<string> ActionKeywords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hide plugin keyword setting panel.
|
||||
/// </summary>
|
||||
public bool HideActionKeywordPanel { get; set; }
|
||||
|
||||
public string IcoPath { get; set;}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
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]
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Init time include both plugin load time and init time
|
||||
/// Init time include both plugin load time and init time.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public long InitTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Average query time.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public long AvgQueryTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Query count.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,37 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin instance and plugin metadata
|
||||
/// </summary>
|
||||
public class PluginPair
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin instance
|
||||
/// </summary>
|
||||
public IAsyncPlugin Plugin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin metadata
|
||||
/// </summary>
|
||||
public PluginMetadata Metadata { get; internal set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert to string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Metadata.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare by plugin metadata ID
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
PluginPair r = obj as PluginPair;
|
||||
if (r != null)
|
||||
if (obj is PluginPair r)
|
||||
{
|
||||
return string.Equals(r.Metadata.ID, Metadata.ID);
|
||||
}
|
||||
|
|
@ -25,6 +41,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get hash code
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a query that is sent to a plugin.
|
||||
/// </summary>
|
||||
public class Query
|
||||
{
|
||||
public Query() { }
|
||||
|
||||
/// <summary>
|
||||
/// Raw query, this includes action keyword if it has
|
||||
/// We didn't recommend use this property directly. You should always use Search property.
|
||||
|
|
@ -54,13 +55,13 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public string ActionKeyword { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
/// <summary>
|
||||
/// Splits <see cref="SearchTerms"/> by spaces and returns the first item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// returns an empty string when <see cref="SearchTerms"/> does not have enough items.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public string FirstSearch => SplitSearch(0);
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -13,7 +12,6 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
|
||||
private string _pluginDirectory;
|
||||
|
||||
private string _icoPath;
|
||||
|
|
|
|||
32
Flow.Launcher.Plugin/SearchDelayTime.cs
Normal file
32
Flow.Launcher.Plugin/SearchDelayTime.cs
Normal 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
|
||||
}
|
||||
|
|
@ -318,5 +318,51 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
{
|
||||
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a directory, creating it if it doesn't exist
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public static void ValidateDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a data directory, synchronizing it by ensuring all files from a bundled source directory exist in it.
|
||||
/// If files are missing or outdated, they are copied from the bundled directory to the data directory.
|
||||
/// </summary>
|
||||
/// <param name="bundledDataDirectory"></param>
|
||||
/// <param name="dataDirectory"></param>
|
||||
public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory)
|
||||
{
|
||||
if (!Directory.Exists(dataDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
}
|
||||
|
||||
foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory))
|
||||
{
|
||||
var data = Path.GetFileName(bundledDataPath);
|
||||
if (data == null) continue;
|
||||
var dataPath = Path.Combine(dataDirectory, data);
|
||||
if (!File.Exists(dataPath))
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc;
|
||||
var time2 = new FileInfo(dataPath).LastWriteTimeUtc;
|
||||
if (time1 != time2)
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using System.Linq;
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods to open a search in a new browser window or tab.
|
||||
/// </summary>
|
||||
public static class SearchWeb
|
||||
{
|
||||
private static string GetDefaultBrowserPath()
|
||||
|
|
@ -106,4 +109,4 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,26 @@ using Windows.Win32.Foundation;
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods for running shell commands
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
processStartInfo.Verb = "RunAsUser";
|
||||
|
|
@ -65,6 +79,15 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
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 = "",
|
||||
string arguments = "", string verb = "", bool createNoWindow = false)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,14 +2,29 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin.SharedModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the result of a match operation.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
Success = success;
|
||||
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)
|
||||
{
|
||||
Success = success;
|
||||
|
|
@ -18,6 +33,9 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
RawScore = rawScore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the match operation was successful.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,6 +48,9 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
/// <summary>
|
||||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
|
|
@ -45,8 +66,15 @@ namespace Flow.Launcher.Plugin.SharedModels
|
|||
/// </summary>
|
||||
public List<int> MatchData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The search precision score used to filter the search results.
|
||||
/// </summary>
|
||||
public SearchPrecisionScore SearchPrecision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the search precision score is met.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsSearchPrecisionScoreMet()
|
||||
{
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// The highest search precision score.
|
||||
/// </summary>
|
||||
Regular = 50,
|
||||
|
||||
/// <summary>
|
||||
/// The medium search precision score.
|
||||
/// </summary>
|
||||
Low = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The lowest search precision score.
|
||||
/// </summary>
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,11 @@ namespace Flow.Launcher
|
|||
{
|
||||
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);
|
||||
|
||||
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||
|
|
@ -177,7 +182,6 @@ namespace Flow.Launcher
|
|||
HotKeyMapper.Initialize();
|
||||
|
||||
// main windows needs initialized before theme change because of blur settings
|
||||
// TODO: Clean ThemeManager.Instance in future
|
||||
Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
|
@ -300,6 +304,14 @@ namespace Flow.Launcher
|
|||
return;
|
||||
}
|
||||
|
||||
// If we call Environment.Exit(0), the application dispose will be called before _mainWindow.Close()
|
||||
// Accessing _mainWindow?.Dispatcher will cause the application stuck
|
||||
// So here we need to check it and just return so that we will not acees _mainWindow?.Dispatcher
|
||||
if (!_mainWindow.CanClose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ namespace Flow.Launcher.Helper;
|
|||
|
||||
public static class WallpaperPathRetrieval
|
||||
{
|
||||
private static readonly int MAX_CACHE_SIZE = 3;
|
||||
|
||||
private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new();
|
||||
private const int MaxCacheSize = 3;
|
||||
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
|
||||
private static readonly object CacheLock = new();
|
||||
|
||||
public static Brush GetWallpaperBrush()
|
||||
{
|
||||
|
|
@ -27,46 +27,71 @@ public static class WallpaperPathRetrieval
|
|||
try
|
||||
{
|
||||
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),
|
||||
// we need to add the last modified date to differentiate them
|
||||
var dateModified = File.GetLastWriteTime(wallpaperPath);
|
||||
wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper);
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Wallpaper path is invalid: {wallpaperPath}");
|
||||
var wallpaperColor = GetWallpaperColor();
|
||||
return new SolidColorBrush(wallpaperColor);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath));
|
||||
var bitmap = new BitmapImage();
|
||||
bitmap.BeginInit();
|
||||
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
|
||||
if (originalWidth == 0 || originalHeight == 0)
|
||||
{
|
||||
App.API.LogInfo(nameof(WallpaperPathRetrieval), $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
||||
return new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
// Manage cache size
|
||||
if (wallpaperCache.Count >= MAX_CACHE_SIZE)
|
||||
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
|
||||
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
|
||||
var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
|
||||
var oldestCache = WallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
|
||||
if (oldestCache != default)
|
||||
{
|
||||
wallpaperCache.Remove(oldestCache);
|
||||
WallpaperCache.Remove(oldestCache);
|
||||
}
|
||||
}
|
||||
|
||||
wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
|
||||
WallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
|
||||
return wallpaperBrush;
|
||||
}
|
||||
|
||||
var wallpaperColor = GetWallpaperColor();
|
||||
return new SolidColorBrush(wallpaperColor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -77,7 +102,7 @@ public static class WallpaperPathRetrieval
|
|||
|
||||
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);
|
||||
if (result is string strResult)
|
||||
{
|
||||
|
|
@ -86,8 +111,9 @@ public static class WallpaperPathRetrieval
|
|||
var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
|
||||
return Color.FromRgb(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.API.LogException(nameof(WallpaperPathRetrieval), "Error parsing wallpaper color", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
</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="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>
|
||||
|
|
@ -39,7 +44,7 @@
|
|||
<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="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 -->
|
||||
<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="LastQueryActionKeywordPreserved">Preserve 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="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>
|
||||
|
|
@ -104,11 +107,15 @@
|
|||
<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="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
|
||||
<system:String x:Key="BackdropType">Backdrop Type</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="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 -->
|
||||
<system:String x:Key="searchplugin">Search Plugin</system:String>
|
||||
|
|
@ -125,6 +132,8 @@
|
|||
<system:String x:Key="currentActionKeywords">Current 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="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="newPriority">New Priority</system:String>
|
||||
<system:String x:Key="priority">Priority</system:String>
|
||||
|
|
@ -138,6 +147,9 @@
|
|||
<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="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 -->
|
||||
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
||||
|
|
@ -200,8 +212,20 @@
|
|||
<system:String x:Key="AnimationSpeedCustom">Custom</system:String>
|
||||
<system:String x:Key="Clock">Clock</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="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 -->
|
||||
<system:String x:Key="hotkey">Hotkey</system:String>
|
||||
|
|
@ -301,6 +325,9 @@
|
|||
<system:String x:Key="logfolder">Log Folder</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="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="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>
|
||||
|
|
@ -347,6 +374,12 @@
|
|||
<system:String x:Key="completedSuccessfully">Completed successfully</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 -->
|
||||
<system:String x:Key="customeQueryHotkeyTitle">Custom Query Hotkey</system:String>
|
||||
<system:String x:Key="customeQueryHotkeyTips">Press a custom hotkey to open Flow Launcher and input the specified query automatically.</system:String>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Loaded="OnLoaded"
|
||||
LocationChanged="OnLocationChanged"
|
||||
Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
PreviewKeyDown="OnKeyDown"
|
||||
PreviewKeyUp="OnKeyUp"
|
||||
PreviewMouseMove="OnPreviewMouseMove"
|
||||
|
|
@ -219,6 +218,14 @@
|
|||
<Grid x:Name="QueryBoxArea">
|
||||
<Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}">
|
||||
<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
|
||||
x:Name="QueryTextSuggestionBox"
|
||||
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
|
|
@ -244,7 +251,8 @@
|
|||
PreviewDragOver="QueryTextBox_OnPreviewDragOver"
|
||||
PreviewKeyUp="QueryTextBox_KeyUp"
|
||||
Style="{DynamicResource QueryBoxStyle}"
|
||||
Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Text="{Binding QueryText, Mode=OneWay}"
|
||||
TextChanged="QueryTextBox_TextChanged1"
|
||||
Visibility="Visible"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<TextBox.CommandBindings>
|
||||
|
|
@ -290,7 +298,9 @@
|
|||
<StackPanel
|
||||
x:Name="ClockPanel"
|
||||
IsHitTestVisible="False"
|
||||
Style="{DynamicResource ClockPanel}">
|
||||
Opacity="{Binding ClockPanelOpacity}"
|
||||
Style="{DynamicResource ClockPanel}"
|
||||
Visibility="{Binding ClockPanelVisibility}">
|
||||
<TextBlock
|
||||
x:Name="ClockBox"
|
||||
Style="{DynamicResource ClockBox}"
|
||||
|
|
@ -318,6 +328,7 @@
|
|||
Name="SearchIcon"
|
||||
Margin="0"
|
||||
Data="{DynamicResource SearchIconImg}"
|
||||
Opacity="{Binding SearchIconOpacity}"
|
||||
Stretch="Fill"
|
||||
Style="{DynamicResource SearchIconStyle}"
|
||||
Visibility="{Binding SearchIconVisibility}" />
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Windows.Interop;
|
|||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
using System.Windows.Threading;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
|
|
@ -21,6 +22,8 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf.Controls;
|
||||
using DataObject = System.Windows.DataObject;
|
||||
using Key = System.Windows.Input.Key;
|
||||
using MouseButtons = System.Windows.Forms.MouseButtons;
|
||||
using NotifyIcon = System.Windows.Forms.NotifyIcon;
|
||||
using Screen = System.Windows.Forms.Screen;
|
||||
|
|
@ -29,6 +32,13 @@ namespace Flow.Launcher
|
|||
{
|
||||
public partial class MainWindow : IDisposable
|
||||
{
|
||||
#region Public Property
|
||||
|
||||
// Window Event: Close Event
|
||||
public bool CanClose { get; set; } = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
// Dependency Injection
|
||||
|
|
@ -42,8 +52,6 @@ namespace Flow.Launcher
|
|||
private readonly ContextMenu _contextMenu = new();
|
||||
private readonly MainViewModel _viewModel;
|
||||
|
||||
// Window Event: Close Event
|
||||
private bool _canClose = false;
|
||||
// Window Event: Key Event
|
||||
private bool _isArrowKeyPressed = false;
|
||||
|
||||
|
|
@ -101,15 +109,24 @@ namespace Flow.Launcher
|
|||
// Check first launch
|
||||
if (_settings.FirstLaunch)
|
||||
{
|
||||
// Set First Launch to false
|
||||
_settings.FirstLaunch = false;
|
||||
|
||||
// Set Backdrop Type to Acrylic for Windows 11 when First Launch. Default is None
|
||||
if (Win32Helper.IsBackdropSupported()) _settings.BackdropType = BackdropTypes.Acrylic;
|
||||
|
||||
// Save settings
|
||||
App.API.SaveAppAllSettings();
|
||||
/* Set Backdrop Type to Acrylic for Windows 11 when First Launch. Default is None. */
|
||||
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
|
||||
_settings.BackdropType = BackdropTypes.Acrylic;
|
||||
var WelcomeWindow = new WelcomeWindow();
|
||||
WelcomeWindow.Show();
|
||||
|
||||
// Show Welcome Window
|
||||
var welcomeWindow = new WelcomeWindow();
|
||||
welcomeWindow.Show();
|
||||
}
|
||||
|
||||
// Initialize place holder
|
||||
SetupPlaceholderText();
|
||||
_viewModel.PlaceholderText = _settings.PlaceholderText;
|
||||
|
||||
// Hide window if need
|
||||
UpdatePosition();
|
||||
if (_settings.HideOnStartup)
|
||||
|
|
@ -141,16 +158,22 @@ namespace Flow.Launcher
|
|||
UpdatePosition();
|
||||
|
||||
// Refresh frame
|
||||
await Ioc.Default.GetRequiredService<Theme>().RefreshFrameAsync();
|
||||
await _theme.RefreshFrameAsync();
|
||||
|
||||
// Initialize resize mode after refreshing frame
|
||||
SetupResizeMode();
|
||||
|
||||
// Reset preview
|
||||
_viewModel.ResetPreview();
|
||||
|
||||
// Since the default main window visibility is visible, so we need set focus during startup
|
||||
QueryTextBox.Focus();
|
||||
|
||||
// Set the initial state of the QueryTextBoxCursorMovedToEnd property
|
||||
// Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
|
||||
_viewModel.QueryTextCursorMovedToEnd = false;
|
||||
|
||||
// View model property changed event
|
||||
_viewModel.PropertyChanged += (o, e) =>
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
|
|
@ -161,24 +184,37 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (_viewModel.MainWindowVisibilityStatus)
|
||||
{
|
||||
// Play sound effect before activing the window
|
||||
if (_settings.UseSound)
|
||||
{
|
||||
SoundPlay();
|
||||
}
|
||||
|
||||
// Update position & Activate
|
||||
UpdatePosition();
|
||||
_viewModel.ResetPreview();
|
||||
Activate();
|
||||
QueryTextBox.Focus();
|
||||
_settings.ActivateTimes++;
|
||||
|
||||
// Reset preview
|
||||
_viewModel.ResetPreview();
|
||||
|
||||
// Select last query if need
|
||||
if (!_viewModel.LastQuerySelected)
|
||||
{
|
||||
QueryTextBox.SelectAll();
|
||||
_viewModel.LastQuerySelected = true;
|
||||
}
|
||||
|
||||
// Focus query box
|
||||
QueryTextBox.Focus();
|
||||
|
||||
// Play window animation
|
||||
if (_settings.UseAnimation)
|
||||
{
|
||||
WindowAnimation();
|
||||
}
|
||||
|
||||
// Update activate times
|
||||
_settings.ActivateTimes++;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
|
@ -191,7 +227,6 @@ namespace Flow.Launcher
|
|||
Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
|
||||
_viewModel.QueryTextCursorMovedToEnd = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(MainViewModel.GameModeStatus):
|
||||
_notifyIcon.Icon = _viewModel.GameModeStatus
|
||||
|
|
@ -201,6 +236,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
};
|
||||
|
||||
// Settings property changed event
|
||||
_settings.PropertyChanged += (o, e) =>
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
|
|
@ -220,11 +256,20 @@ namespace Flow.Launcher
|
|||
case nameof(Settings.WindowTop):
|
||||
Top = _settings.WindowTop;
|
||||
break;
|
||||
case nameof(Settings.ShowPlaceholder):
|
||||
SetupPlaceholderText();
|
||||
break;
|
||||
case nameof(Settings.PlaceholderText):
|
||||
_viewModel.PlaceholderText = _settings.PlaceholderText;
|
||||
break;
|
||||
case nameof(Settings.KeepMaxResults):
|
||||
SetupResizeMode();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// QueryTextBox.Text change detection (modified to only work when character count is 1 or higher)
|
||||
QueryTextBox.TextChanged += (sender, e) => UpdateClockPanelVisibility();
|
||||
QueryTextBox.TextChanged += (s, e) => UpdateClockPanelVisibility();
|
||||
|
||||
// Detecting ContextMenu.Visibility changes
|
||||
DependencyPropertyDescriptor
|
||||
|
|
@ -239,7 +284,7 @@ namespace Flow.Launcher
|
|||
|
||||
private async void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (!_canClose)
|
||||
if (!CanClose)
|
||||
{
|
||||
_notifyIcon.Visible = false;
|
||||
App.API.SaveAppAllSettings();
|
||||
|
|
@ -247,8 +292,9 @@ namespace Flow.Launcher
|
|||
await PluginManager.DisposePluginsAsync();
|
||||
Notification.Uninstall();
|
||||
// After plugins are all disposed, we can close the main window
|
||||
_canClose = true;
|
||||
Close();
|
||||
CanClose = true;
|
||||
// Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,8 +326,8 @@ namespace Flow.Launcher
|
|||
_settings.WindowLeft = Left;
|
||||
_settings.WindowTop = Top;
|
||||
|
||||
ClockPanel.Opacity = 0;
|
||||
SearchIcon.Opacity = 0;
|
||||
_viewModel.ClockPanelOpacity = 0.0;
|
||||
_viewModel.SearchIconOpacity = 0.0;
|
||||
|
||||
// This condition stops extra hide call when animator is on,
|
||||
// which causes the toggling to occasional hide instead of show.
|
||||
|
|
@ -291,7 +337,9 @@ namespace Flow.Launcher
|
|||
// This also stops the mainwindow from flickering occasionally after Settings window is opened
|
||||
// and always after Settings window is closed.
|
||||
if (_settings.UseAnimation)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible)
|
||||
{
|
||||
|
|
@ -331,7 +379,6 @@ namespace Flow.Launcher
|
|||
_viewModel.LoadContextMenuCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Key.Left:
|
||||
if (!_viewModel.QueryResultsSelected() && QueryTextBox.CaretIndex == 0)
|
||||
|
|
@ -339,7 +386,6 @@ namespace Flow.Launcher
|
|||
_viewModel.EscCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Key.Back:
|
||||
if (specialKeyState.CtrlPressed)
|
||||
|
|
@ -358,7 +404,6 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -420,23 +465,25 @@ namespace Flow.Launcher
|
|||
{
|
||||
_initialWidth = (int)Width;
|
||||
_initialHeight = (int)Height;
|
||||
|
||||
handled = true;
|
||||
}
|
||||
else if (msg == Win32Helper.WM_EXITSIZEMOVE)
|
||||
{
|
||||
if (_initialHeight != (int)Height)
|
||||
{
|
||||
var shadowMargin = 0;
|
||||
var (_, useDropShadowEffect) = _theme.GetActualValue();
|
||||
if (useDropShadowEffect)
|
||||
{
|
||||
shadowMargin = 32;
|
||||
}
|
||||
|
||||
if (!_settings.KeepMaxResults)
|
||||
{
|
||||
var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize;
|
||||
// Get shadow margin
|
||||
var shadowMargin = 0;
|
||||
var (_, useDropShadowEffect) = _theme.GetActualValue();
|
||||
if (useDropShadowEffect)
|
||||
{
|
||||
shadowMargin = 32;
|
||||
}
|
||||
|
||||
// Calculate max results to show
|
||||
var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize;
|
||||
if (itemCount < 2)
|
||||
{
|
||||
_settings.MaxResultsToShow = 2;
|
||||
|
|
@ -448,11 +495,16 @@ namespace Flow.Launcher
|
|||
}
|
||||
|
||||
SizeToContent = SizeToContent.Height;
|
||||
_viewModel.MainWindowWidth = Width;
|
||||
}
|
||||
|
||||
if (_initialWidth != (int)Width)
|
||||
{
|
||||
if (!_settings.KeepMaxResults)
|
||||
{
|
||||
// Update width
|
||||
_viewModel.MainWindowWidth = Width;
|
||||
}
|
||||
|
||||
SizeToContent = SizeToContent.Height;
|
||||
}
|
||||
|
||||
|
|
@ -765,12 +817,6 @@ namespace Flow.Launcher
|
|||
{
|
||||
_isArrowKeyPressed = true;
|
||||
|
||||
UpdatePosition();
|
||||
|
||||
var opacity = _settings.UseAnimation ? 0.0 : 1.0;
|
||||
ClockPanel.Opacity = opacity;
|
||||
SearchIcon.Opacity = opacity;
|
||||
|
||||
var clocksb = new Storyboard();
|
||||
var iconsb = new Storyboard();
|
||||
var easing = new CircleEase { EasingMode = EasingMode.EaseInOut };
|
||||
|
|
@ -850,8 +896,11 @@ namespace Flow.Launcher
|
|||
private void UpdateClockPanelVisibility()
|
||||
{
|
||||
if (QueryTextBox == null || ContextMenu == null || History == null || ClockPanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Initialize animation length & duration
|
||||
var animationLength = _settings.AnimationSpeed switch
|
||||
{
|
||||
AnimationSpeeds.Slow => 560,
|
||||
|
|
@ -859,32 +908,37 @@ namespace Flow.Launcher
|
|||
AnimationSpeeds.Fast => 160,
|
||||
_ => _settings.CustomAnimationLength
|
||||
};
|
||||
|
||||
var animationDuration = TimeSpan.FromMilliseconds(animationLength * 2 / 3);
|
||||
|
||||
// ✅ Conditions for showing ClockPanel (No query input & ContextMenu, History are closed)
|
||||
bool shouldShowClock = QueryTextBox.Text.Length == 0 &&
|
||||
var shouldShowClock = QueryTextBox.Text.Length == 0 &&
|
||||
ContextMenu.Visibility != Visibility.Visible &&
|
||||
History.Visibility != Visibility.Visible;
|
||||
|
||||
// ✅ 1. When ContextMenu opens, immediately set Visibility.Hidden (force hide without animation)
|
||||
if (ContextMenu.Visibility == Visibility.Visible)
|
||||
{
|
||||
ClockPanel.Visibility = Visibility.Hidden;
|
||||
ClockPanel.Opacity = 0.0; // Set to 0 in case Opacity animation affects it
|
||||
_viewModel.ClockPanelVisibility = Visibility.Hidden;
|
||||
_viewModel.ClockPanelOpacity = 0.0; // Set to 0 in case Opacity animation affects it
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 2. When ContextMenu is closed, keep it Hidden if there's text in the query (remember previous state)
|
||||
if (ContextMenu.Visibility != Visibility.Visible && QueryTextBox.Text.Length > 0)
|
||||
else if (QueryTextBox.Text.Length > 0)
|
||||
{
|
||||
_viewModel.ClockPanelVisibility = Visibility.Hidden;
|
||||
_viewModel.ClockPanelOpacity = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Prevent multiple animations
|
||||
if (_isClockPanelAnimating)
|
||||
{
|
||||
ClockPanel.Visibility = Visibility.Hidden;
|
||||
ClockPanel.Opacity = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 3. When hiding ClockPanel (apply fade-out animation)
|
||||
if ((!shouldShowClock) && ClockPanel.Visibility == Visibility.Visible && !_isClockPanelAnimating)
|
||||
if ((!shouldShowClock) && _viewModel.ClockPanelVisibility == Visibility.Visible)
|
||||
{
|
||||
_isClockPanelAnimating = true;
|
||||
|
||||
|
|
@ -898,40 +952,40 @@ namespace Flow.Launcher
|
|||
|
||||
fadeOut.Completed += (s, e) =>
|
||||
{
|
||||
ClockPanel.Visibility = Visibility.Hidden; // ✅ Completely hide after animation
|
||||
_viewModel.ClockPanelVisibility = Visibility.Hidden; // ✅ Completely hide after animation
|
||||
_isClockPanelAnimating = false;
|
||||
};
|
||||
|
||||
ClockPanel.BeginAnimation(OpacityProperty, fadeOut);
|
||||
}
|
||||
|
||||
// ✅ 4. When showing ClockPanel (apply fade-in animation)
|
||||
else if (shouldShowClock && ClockPanel.Visibility != Visibility.Visible && !_isClockPanelAnimating)
|
||||
else if (shouldShowClock && _viewModel.ClockPanelVisibility != Visibility.Visible)
|
||||
{
|
||||
_isClockPanelAnimating = true;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
_viewModel.ClockPanelVisibility = Visibility.Visible; // ✅ Set Visibility to Visible first
|
||||
|
||||
var fadeIn = new DoubleAnimation
|
||||
{
|
||||
ClockPanel.Visibility = Visibility.Visible; // ✅ Set Visibility to Visible first
|
||||
From = 0.0,
|
||||
To = 1.0,
|
||||
Duration = animationDuration,
|
||||
FillBehavior = FillBehavior.HoldEnd
|
||||
};
|
||||
|
||||
var fadeIn = new DoubleAnimation
|
||||
{
|
||||
From = 0.0,
|
||||
To = 1.0,
|
||||
Duration = animationDuration,
|
||||
FillBehavior = FillBehavior.HoldEnd
|
||||
};
|
||||
fadeIn.Completed += (s, e) => _isClockPanelAnimating = false;
|
||||
|
||||
fadeIn.Completed += (s, e) => _isClockPanelAnimating = false;
|
||||
ClockPanel.BeginAnimation(OpacityProperty, fadeIn);
|
||||
}, DispatcherPriority.Render);
|
||||
ClockPanel.BeginAnimation(OpacityProperty, fadeIn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static double GetOpacityFromStyle(Style style, double defaultOpacity = 1.0)
|
||||
{
|
||||
if (style == null)
|
||||
{
|
||||
return defaultOpacity;
|
||||
}
|
||||
|
||||
foreach (Setter setter in style.Setters.Cast<Setter>())
|
||||
{
|
||||
|
|
@ -947,7 +1001,9 @@ namespace Flow.Launcher
|
|||
private static Thickness GetThicknessFromStyle(Style style, Thickness defaultThickness)
|
||||
{
|
||||
if (style == null)
|
||||
{
|
||||
return defaultThickness;
|
||||
}
|
||||
|
||||
foreach (Setter setter in style.Setters.Cast<Setter>())
|
||||
{
|
||||
|
|
@ -1004,6 +1060,67 @@ namespace Flow.Launcher
|
|||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Placeholder
|
||||
|
||||
private void SetupPlaceholderText()
|
||||
{
|
||||
if (_settings.ShowPlaceholder)
|
||||
{
|
||||
QueryTextBox.TextChanged += QueryTextBox_TextChanged;
|
||||
QueryTextSuggestionBox.TextChanged += QueryTextSuggestionBox_TextChanged;
|
||||
SetPlaceholderText();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryTextBox.TextChanged -= QueryTextBox_TextChanged;
|
||||
QueryTextSuggestionBox.TextChanged -= QueryTextSuggestionBox_TextChanged;
|
||||
QueryTextPlaceholderBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetPlaceholderText();
|
||||
}
|
||||
|
||||
private void QueryTextSuggestionBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetPlaceholderText();
|
||||
}
|
||||
|
||||
private void SetPlaceholderText()
|
||||
{
|
||||
var queryText = QueryTextBox.Text;
|
||||
var suggestionText = QueryTextSuggestionBox.Text;
|
||||
QueryTextPlaceholderBox.Visibility = string.IsNullOrEmpty(queryText) && string.IsNullOrEmpty(suggestionText) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resize Mode
|
||||
|
||||
private void SetupResizeMode()
|
||||
{
|
||||
ResizeMode = _settings.KeepMaxResults ? ResizeMode.NoResize : ResizeMode.CanResize;
|
||||
if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome)
|
||||
{
|
||||
_theme.SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search Delay
|
||||
|
||||
private void QueryTextBox_TextChanged1(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var textBox = (TextBox)sender;
|
||||
_viewModel.QueryText = textBox.Text;
|
||||
_viewModel.Query(_settings.SearchQueryResultsWithDelay);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ namespace Flow.Launcher
|
|||
{
|
||||
internal static class Notification
|
||||
{
|
||||
internal static bool legacy = Environment.OSVersion.Version.Build < 19041;
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
|
||||
internal static bool legacy = !Win32Helper.IsNotificationSupported();
|
||||
|
||||
internal static void Uninstall()
|
||||
{
|
||||
if (!legacy)
|
||||
|
|
@ -25,7 +25,6 @@ namespace Flow.Launcher
|
|||
});
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
|
||||
private static void ShowInternal(string title, string subTitle, string iconPath = null)
|
||||
{
|
||||
// Handle notification for win7/8/early win10
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ namespace Flow.Launcher
|
|||
private readonly Internationalization _translater;
|
||||
private readonly MainViewModel _mainVM;
|
||||
|
||||
private readonly object _saveSettingsLock = new();
|
||||
|
||||
#region Constructor
|
||||
|
||||
public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM)
|
||||
|
|
@ -57,21 +59,28 @@ namespace Flow.Launcher
|
|||
_mainVM.ChangeQueryText(query, requery);
|
||||
}
|
||||
|
||||
public void RestartApp()
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
public async void RestartApp()
|
||||
{
|
||||
_mainVM.Hide();
|
||||
|
||||
// we must manually save
|
||||
// We must manually save
|
||||
// UpdateManager.RestartApp() will call Environment.Exit(0)
|
||||
// which will cause ungraceful exit
|
||||
SaveAppAllSettings();
|
||||
|
||||
// Wait for all image caches to be saved before restarting
|
||||
await ImageLoader.WaitSaveAsync();
|
||||
|
||||
// Restart requires Squirrel's Update.exe to be present in the parent folder,
|
||||
// it is only published from the project's release pipeline. When debugging without it,
|
||||
// the project may not restart or just terminates. This is expected.
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
|
||||
#pragma warning restore VSTHRD100 // Avoid async void methods
|
||||
|
||||
public void ShowMainWindow() => _mainVM.Show();
|
||||
|
||||
public void HideMainWindow() => _mainVM.Hide();
|
||||
|
|
@ -85,10 +94,13 @@ namespace Flow.Launcher
|
|||
|
||||
public void SaveAppAllSettings()
|
||||
{
|
||||
PluginManager.Save();
|
||||
_mainVM.Save();
|
||||
_settings.Save();
|
||||
_ = ImageLoader.Save();
|
||||
lock (_saveSettingsLock)
|
||||
{
|
||||
_settings.Save();
|
||||
PluginManager.Save();
|
||||
_mainVM.Save();
|
||||
}
|
||||
_ = ImageLoader.SaveAsync();
|
||||
}
|
||||
|
||||
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
||||
|
|
@ -192,7 +204,7 @@ namespace Flow.Launcher
|
|||
|
||||
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
|
||||
|
||||
public object RemovePluginSettings(string assemblyName)
|
||||
public void RemovePluginSettings(string assemblyName)
|
||||
{
|
||||
foreach (var keyValuePair in _pluginJsonStorages)
|
||||
{
|
||||
|
|
@ -202,11 +214,8 @@ namespace Flow.Launcher
|
|||
if (name == assemblyName)
|
||||
{
|
||||
_pluginJsonStorages.Remove(key, out var pluginJsonStorage);
|
||||
return pluginJsonStorage;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -20,21 +20,21 @@
|
|||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="MinHeight" Value="68" />
|
||||
<Setter Property="Padding" Value="0,15,0,15" />
|
||||
<Setter Property="Margin" Value="0,4,0,0" />
|
||||
<Setter Property="Padding" Value="0 15 0 15" />
|
||||
<Setter Property="Margin" Value="0 4 0 0" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Style.Triggers>
|
||||
<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="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</DataTrigger>
|
||||
<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="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Padding" Value="38,0,26,0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
<Setter Property="Padding" Value="35 0 26 0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</DataTrigger>
|
||||
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<ContentControl
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,16,0"
|
||||
Margin="0 0 16 0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Content}" />
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<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="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
|
@ -107,8 +107,8 @@
|
|||
</Style.Triggers>
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Padding" Value="0,0,24,0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
<Setter Property="Padding" Value="0 0 24 0" />
|
||||
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
|
|
@ -120,11 +120,11 @@
|
|||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Style.Triggers>
|
||||
<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>
|
||||
</Style.Triggers>
|
||||
<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="FontSize" Value="20" />
|
||||
<Setter Property="FontFamily" Value="/Resources/#Segoe Fluent Icons" />
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<UserControl x:Class="Flow.Launcher.Resources.Controls.InstalledPluginDisplay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel"
|
||||
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance viewModel:PluginViewModel}"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<UserControl
|
||||
x:Class="Flow.Launcher.Resources.Controls.InstalledPluginDisplay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel"
|
||||
d:DataContext="{d:DesignInstance viewModel:PluginViewModel}"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="300"
|
||||
mc:Ignorable="d">
|
||||
<Expander
|
||||
Padding="0"
|
||||
BorderThickness="0"
|
||||
|
|
@ -28,7 +30,7 @@
|
|||
Grid.Column="0"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Image, IsAsync=True}" />
|
||||
Source="{Binding Image, Mode=OneWay, IsAsync=True}" />
|
||||
<StackPanel Grid.Column="1" Margin="16 0 14 0">
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource Color05B}"
|
||||
|
|
@ -37,12 +39,15 @@
|
|||
ToolTip="{Binding PluginPair.Metadata.Version}" />
|
||||
<TextBlock
|
||||
Margin="0 2 0 0"
|
||||
Foreground="{DynamicResource Color04B}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource Color04B}"
|
||||
Text="{Binding PluginPair.Metadata.Description}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0 0 8 0"
|
||||
VerticalAlignment="Center"
|
||||
|
|
@ -70,9 +75,7 @@
|
|||
<Setter Property="FontWeight" Value="DemiBold" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger
|
||||
Binding="{Binding ElementName=PriorityButton, UpdateSourceTrigger=PropertyChanged, Path=Content}"
|
||||
Value="0">
|
||||
<DataTrigger Binding="{Binding ElementName=PriorityButton, UpdateSourceTrigger=PropertyChanged, Path=Content}" Value="0">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color08B}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
</DataTrigger>
|
||||
|
|
@ -95,10 +98,12 @@
|
|||
<StackPanel>
|
||||
<ContentControl Content="{Binding BottomPart1}" />
|
||||
|
||||
<ContentControl Content="{Binding BottomPart2}" />
|
||||
|
||||
<Border
|
||||
BorderThickness="0 1 0 0"
|
||||
Background="{DynamicResource Color00B}"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
Background="{DynamicResource Color00B}">
|
||||
BorderThickness="0 1 0 0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
|
|
@ -115,7 +120,7 @@
|
|||
Content="{Binding SettingControl}" />
|
||||
</Border>
|
||||
|
||||
<ContentControl Content="{Binding BottomPart2}" />
|
||||
<ContentControl Content="{Binding BottomPart3}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -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}">
|
||||

|
||||
</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>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace Flow.Launcher.Resources.Controls;
|
||||
|
||||
public partial class InstalledPluginSearchDelay : UserControl
|
||||
{
|
||||
public InstalledPluginSearchDelay()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,9 @@
|
|||
<SolidColorBrush x:Key="BasicSeparatorColor" 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="PopuBGColor" Color="#202020" />
|
||||
<SolidColorBrush x:Key="PopupBGColor" Color="#202020" />
|
||||
|
|
@ -115,7 +118,7 @@
|
|||
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#FCE100" />
|
||||
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
|
||||
<SolidColorBrush x:Key="InfoBarBD" Color="#19000000" />
|
||||
|
||||
|
||||
<SolidColorBrush x:Key="MouseOverWindowCloseButtonForegroundBrush" Color="#ffffff" />
|
||||
|
||||
<SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" />
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@
|
|||
<SolidColorBrush x:Key="PluginInfoColor" Color="#8f8f8f" />
|
||||
<SolidColorBrush x:Key="BasicLabelColor" Color="#1b1b1b" />
|
||||
|
||||
<SolidColorBrush x:Key="NewHotkeyForeground" Color="#5E000000" />
|
||||
<SolidColorBrush x:Key="BasicHotkeyBGColor" Color="#0A000000" />
|
||||
|
||||
<SolidColorBrush x:Key="ThemeHoverButton" Color="#f6f6f6" />
|
||||
<!-- Typo -->
|
||||
<SolidColorBrush x:Key="PopuBGColor" Color="#ffffff" />
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@
|
|||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.0" Color="#1494df" />
|
||||
<GradientStop Offset="1.0" Color="#1073bd" />
|
||||
<GradientStop Offset="0.0" Color="#2A4D8C" />
|
||||
<GradientStop Offset="1.0" Color="#1E3160" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Border Width="450" Style="{DynamicResource WindowBorderStyle}">
|
||||
<Border Width="450" Style="{DynamicResource PreviewWindowBorderStyle}">
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@
|
|||
|
||||
<Border Grid.Row="0" HorizontalAlignment="Stretch">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
|
||||
<LinearGradientBrush StartPoint="0 1" EndPoint="0 0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.0" Color="#7b83eb" />
|
||||
<GradientStop Offset="1.0" Color="#555dc0" />
|
||||
<GradientStop Offset="0.0" Color="#E5F3F7" />
|
||||
<GradientStop Offset="1.0" Color="#FAFAFD" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
|
|
|||
|
|
@ -65,23 +65,24 @@
|
|||
Margin="0 0 10 0"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding ShowOpenResultHotkey}">
|
||||
<TextBlock
|
||||
x:Name="Hotkey"
|
||||
Margin="12 0 12 0"
|
||||
Padding="0 0 0 0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Style="{DynamicResource ItemHotkeyStyle}">
|
||||
<TextBlock.Visibility>
|
||||
<Border x:Name="HotkeyBG" Style="{DynamicResource ItemHotkeyBGStyle}">
|
||||
<Border.Visibility>
|
||||
<Binding Converter="{StaticResource ResourceKey=OpenResultHotkeyVisibilityConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" />
|
||||
</TextBlock.Visibility>
|
||||
<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.Visibility>
|
||||
<TextBlock
|
||||
x:Name="Hotkey"
|
||||
Padding="0 0 0 0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Style="{DynamicResource ItemHotkeyStyle}">
|
||||
<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>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
|
|
@ -214,6 +215,7 @@
|
|||
<Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
|
||||
<Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
|
||||
<Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" />
|
||||
<Setter TargetName="HotkeyBG" Property="Style" Value="{DynamicResource ItemHotkeyBGSelectedStyle}" />
|
||||
<Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
|
|
|
|||
138
Flow.Launcher/SearchDelayTimeWindow.xaml
Normal file
138
Flow.Launcher/SearchDelayTimeWindow.xaml
Normal 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>
|
||||
57
Flow.Launcher/SearchDelayTimeWindow.xaml.cs
Normal file
57
Flow.Launcher/SearchDelayTimeWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.SettingPages.ViewModels;
|
||||
|
|
@ -9,7 +8,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
|
|||
{
|
||||
public string Display { get; set; }
|
||||
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()
|
||||
{
|
||||
|
|
@ -19,7 +18,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
|
|||
foreach (var value in enumValues)
|
||||
{
|
||||
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 });
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +29,7 @@ public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
|
|||
{
|
||||
foreach (var item in options)
|
||||
{
|
||||
item.Display = InternationalizationManager.Instance.GetTranslation(item.LocalizationKey);
|
||||
item.Display = App.API.GetTranslation(item.LocalizationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
|
@ -16,6 +15,8 @@ namespace Flow.Launcher.SettingPages.ViewModels;
|
|||
|
||||
public partial class SettingsPaneAboutViewModel : BaseModel
|
||||
{
|
||||
private static readonly string ClassName = nameof(SettingsPaneAboutViewModel);
|
||||
|
||||
private readonly Settings _settings;
|
||||
private readonly Updater _updater;
|
||||
|
||||
|
|
@ -24,7 +25,16 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
get
|
||||
{
|
||||
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(
|
||||
InternationalizationManager.Instance.GetTranslation("about_activate_times"),
|
||||
App.API.GetTranslation("about_activate_times"),
|
||||
_settings.ActivateTimes
|
||||
);
|
||||
|
||||
|
|
@ -88,32 +98,52 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
private void AskClearLogFolderConfirmation()
|
||||
{
|
||||
var confirmResult = App.API.ShowMsgBox(
|
||||
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
|
||||
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
|
||||
App.API.GetTranslation("clearlogfolderMessage"),
|
||||
App.API.GetTranslation("clearlogfolder"),
|
||||
MessageBoxButton.YesNo
|
||||
);
|
||||
|
||||
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]
|
||||
private void OpenSettingsFolder()
|
||||
{
|
||||
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
|
||||
App.API.OpenDirectory(DataLocation.SettingsDirectory);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenParentOfSettingsFolder(object parameter)
|
||||
{
|
||||
string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings);
|
||||
string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectory);
|
||||
string parentFolderPath = Path.GetDirectoryName(settingsFolderPath);
|
||||
App.API.OpenDirectory(parentFolderPath);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenLogsFolder()
|
||||
{
|
||||
|
|
@ -121,26 +151,52 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
}
|
||||
|
||||
[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 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)
|
||||
// Do not clean log files of current version
|
||||
.Where(dir => !Constant.Version.Equals(dir.Name))
|
||||
.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));
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
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 = "")
|
||||
|
|
@ -148,6 +204,55 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
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)
|
||||
{
|
||||
const int scale = 1024;
|
||||
|
|
@ -156,8 +261,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
|
||||
foreach (string order in orders)
|
||||
{
|
||||
if (bytes > max)
|
||||
return $"{decimal.Divide(bytes, max):##.##} {order}";
|
||||
if (bytes > max) return $"{decimal.Divide(bytes, max):##.##} {order}";
|
||||
|
||||
max /= scale;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core;
|
||||
|
|
@ -30,6 +31,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
public class SearchWindowAlignData : DropdownDataGeneric<SearchWindowAligns> { }
|
||||
public class SearchPrecisionData : DropdownDataGeneric<SearchPrecisionScore> { }
|
||||
public class LastQueryModeData : DropdownDataGeneric<LastQueryMode> { }
|
||||
public class SearchDelayTimeData : DropdownDataGeneric<SearchDelayTime> { }
|
||||
|
||||
public bool StartFlowLauncherOnSystemStartup
|
||||
{
|
||||
|
|
@ -142,12 +144,33 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
public List<LastQueryModeData> LastQueryModes { get; } =
|
||||
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()
|
||||
{
|
||||
DropdownDataGeneric<SearchWindowScreens>.UpdateLabels(SearchWindowScreens);
|
||||
DropdownDataGeneric<SearchWindowAligns>.UpdateLabels(SearchWindowAligns);
|
||||
DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores);
|
||||
DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes);
|
||||
DropdownDataGeneric<SearchDelayTime>.UpdateLabels(SearchDelayTimes);
|
||||
}
|
||||
|
||||
public string Language
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@ public class SettingsPanePluginsViewModel : BaseModel
|
|||
.Select(plugin => new PluginViewModel
|
||||
{
|
||||
PluginPair = plugin,
|
||||
PluginSettingsObject = _settings.PluginSettings.Plugins[plugin.Metadata.ID]
|
||||
PluginSettingsObject = _settings.PluginSettings.GetPluginSettings(plugin.Metadata.ID)
|
||||
})
|
||||
.Where(plugin => plugin.PluginSettingsObject != null)
|
||||
.ToList();
|
||||
|
||||
public List<PluginViewModel> FilteredPluginViewModels => PluginViewModels
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ namespace Flow.Launcher.SettingPages.ViewModels;
|
|||
public partial class SettingsPaneThemeViewModel : BaseModel
|
||||
{
|
||||
private const string DefaultFont = "Segoe UI";
|
||||
public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : "";
|
||||
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";
|
||||
|
||||
private List<Theme.ThemeData> _themes;
|
||||
|
|
@ -259,6 +260,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
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
|
||||
{
|
||||
get => Settings.UseClock;
|
||||
|
|
@ -469,7 +487,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
[RelayCommand]
|
||||
private void OpenThemesFolder()
|
||||
{
|
||||
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
|
||||
App.API.OpenDirectory(DataLocation.ThemesDirectory);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@
|
|||
Margin="0 12 0 0"
|
||||
Icon="">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
Margin="0 0 12 0"
|
||||
Command="{Binding AskClearCacheFolderConfirmationCommand}"
|
||||
Content="{Binding CacheFolderSize, Mode=OneWay}" />
|
||||
<Button
|
||||
Margin="0 0 12 0"
|
||||
Command="{Binding AskClearLogFolderConfirmationCommand}"
|
||||
|
|
|
|||
|
|
@ -28,21 +28,26 @@
|
|||
Style="{StaticResource PageTitle}"
|
||||
Text="{DynamicResource general}"
|
||||
TextAlignment="left" />
|
||||
|
||||
<cc:Card Title="{DynamicResource startFlowLauncherOnSystemStartup}" Icon="">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding StartFlowLauncherOnSystemStartup}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card Title="{DynamicResource useLogonTaskForStartup}" Sub="{DynamicResource useLogonTaskForStartupTooltip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseLogonTaskForStartup}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource startFlowLauncherOnSystemStartup}"
|
||||
Margin="0 8 0 0"
|
||||
Icon="">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding StartFlowLauncherOnSystemStartup}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource useLogonTaskForStartup}"
|
||||
Sub="{DynamicResource useLogonTaskForStartupTooltip}"
|
||||
Type="InsideFit">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseLogonTaskForStartup}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:ExCard>
|
||||
<cc:Card
|
||||
Title="{DynamicResource hideOnStartup}"
|
||||
Icon=""
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
VirtualizingStackPanel.IsVirtualizing="True"
|
||||
VirtualizingStackPanel.ScrollUnit="Pixel">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Page title -->
|
||||
<TextBlock
|
||||
Margin="5 23 0 5"
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
Text="{DynamicResource appearance}"
|
||||
TextAlignment="left"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<!-- Theme Preview and Editor -->
|
||||
<Grid>
|
||||
<Grid.Style>
|
||||
|
|
@ -68,6 +70,7 @@
|
|||
<ColumnDefinition Width="8*" />
|
||||
<ColumnDefinition MaxWidth="250" Style="{StaticResource SecondColumnStyle}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Theme Size Editor -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
|
|
@ -260,7 +263,8 @@
|
|||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<!-- Theme Preview -->
|
||||
|
||||
<!-- Theme preview -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Background="{Binding PreviewBackground}"
|
||||
|
|
@ -464,7 +468,8 @@
|
|||
<cc:Card
|
||||
Title="{DynamicResource BackdropType}"
|
||||
Margin="0 0 0 0"
|
||||
Icon="">
|
||||
Icon=""
|
||||
Sub="{Binding BackdropSubText}">
|
||||
<ComboBox
|
||||
MinWidth="160"
|
||||
VerticalAlignment="Center"
|
||||
|
|
@ -474,10 +479,8 @@
|
|||
ItemsSource="{Binding BackdropTypesList}"
|
||||
SelectedValue="{Binding BackdropType, Mode=TwoWay}"
|
||||
SelectedValuePath="Value" />
|
||||
<!-- ✅ 추가 -->
|
||||
</cc:Card>
|
||||
|
||||
|
||||
<!-- Drop shadow effect -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource queryWindowShadowEffect}"
|
||||
|
|
@ -496,38 +499,30 @@
|
|||
Text="{DynamicResource browserMoreThemes}"
|
||||
Uri="{Binding LinkThemeGallery}" />
|
||||
|
||||
|
||||
|
||||
<!-- Fixed Height -->
|
||||
<cc:CardGroup Margin="0 20 0 0">
|
||||
<cc:Card
|
||||
Title="{DynamicResource KeepMaxResults}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource KeepMaxResultsToolTip}">
|
||||
<ui:ToggleSwitch IsOn="{Binding KeepMaxResults}" />
|
||||
</cc:Card>
|
||||
<!-- Fixed size -->
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource KeepMaxResults}"
|
||||
Margin="0 20 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource KeepMaxResultsToolTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding KeepMaxResults}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</StackPanel>
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource maxShowResults}"
|
||||
Sub="{DynamicResource maxShowResultsToolTip}"
|
||||
Visibility="{Binding KeepMaxResults, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Type="InsideFit">
|
||||
<ComboBox
|
||||
Width="100"
|
||||
ItemsSource="{Binding MaxResultsRange}"
|
||||
SelectedItem="{Binding Settings.MaxResultsToShow}" />
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
|
||||
<!-- Fonts and icons -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource useGlyphUI}"
|
||||
Margin="0 14 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource useGlyphUIEffect}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseGlyphIcons}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:ExCard>
|
||||
|
||||
<!-- Time and date -->
|
||||
<cc:CardGroup Margin="0 14 0 0">
|
||||
|
|
@ -574,22 +569,45 @@
|
|||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
|
||||
<!-- Animation -->
|
||||
<cc:CardGroup Margin="0 14 0 0">
|
||||
<!-- Placeholder text -->
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource ShowPlaceholder}"
|
||||
Margin="0 4 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource ShowPlaceholderTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding ShowPlaceholder}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource Animation}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource AnimationTip}">
|
||||
Title="{DynamicResource PlaceholderText}"
|
||||
Sub="{Binding PlaceholderTextTip}"
|
||||
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=""
|
||||
Sub="{DynamicResource AnimationTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseAnimation}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource AnimationSpeed}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource AnimationSpeedTip}"
|
||||
Visibility="{Binding UseAnimation, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Type="InsideFit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox
|
||||
MinWidth="160"
|
||||
|
|
@ -609,25 +627,25 @@
|
|||
IsEqualTo={x:Static userSettings:AnimationSpeeds.Custom}}" />
|
||||
</StackPanel>
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
</cc:ExCard>
|
||||
|
||||
<!-- SFX -->
|
||||
<cc:CardGroup Margin="0 14 0 0">
|
||||
<cc:Card
|
||||
Title="{DynamicResource SoundEffect}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource SoundEffectTip}">
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource SoundEffect}"
|
||||
Margin="0 4 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource SoundEffectTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseSound}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card
|
||||
Title="{DynamicResource SoundEffectVolume}"
|
||||
Icon=""
|
||||
IsEnabled="{Binding EnableVolumeAdjustment}"
|
||||
Sub="{DynamicResource SoundEffectVolumeTip}"
|
||||
Visibility="{Binding UseSound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Type="InsideFit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0 0 8 0"
|
||||
|
|
@ -644,7 +662,7 @@
|
|||
Value="{Binding SoundEffectVolume}" />
|
||||
</StackPanel>
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
</cc:ExCard>
|
||||
<Border
|
||||
Name="WMPWarning"
|
||||
Padding="0 10"
|
||||
|
|
@ -680,6 +698,18 @@
|
|||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- Fonts and icons -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource useGlyphUI}"
|
||||
Margin="0 0 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource useGlyphUIEffect}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseGlyphIcons}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
<!-- Settings color scheme -->
|
||||
<cc:Card
|
||||
Title="{DynamicResource ColorScheme}"
|
||||
|
|
|
|||
|
|
@ -351,17 +351,41 @@
|
|||
<Setter Property="Inline.FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="BaseItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
</Style>
|
||||
<Style x:Key="BaseItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
</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. -->
|
||||
<Style x:Key="BaseItemHotkeySelecetedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
</Style>
|
||||
|
||||
|
|
@ -488,7 +512,6 @@
|
|||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
|
|
@ -496,7 +519,6 @@
|
|||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<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}">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
|
|
@ -145,13 +146,17 @@
|
|||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
<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}">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
|
|
@ -142,13 +143,17 @@
|
|||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
<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
|
||||
x:Key="ItemGlyph"
|
||||
BasedOn="{StaticResource BaseGlyphStyle}"
|
||||
|
|
@ -148,13 +150,17 @@
|
|||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
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 SystemControlHighlightBaseHighBrush}" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="Height" Value="38" />
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
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="Height" Value="38" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
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="Foreground" Value="#e7e9eb" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
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="Height" Value="38" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
BasedOn="{StaticResource BaseWindowBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#09181e" />
|
||||
<Setter Property="BorderBrush" Value="#1e292f" />
|
||||
<Setter Property="Background" Value="#0f1f26" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
|
|
|
|||
|
|
@ -1,58 +1,57 @@
|
|||
<!--
|
||||
Name: Rider
|
||||
IsDark: False
|
||||
HasBlur: False
|
||||
-->
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Thickness x:Key="ResultMargin">0 0 0 8</Thickness>
|
||||
<Style
|
||||
x:Key="ItemGlyph"
|
||||
BasedOn="{StaticResource BaseGlyphStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="0 0 50 0" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="Height" Value="38" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
<Setter Property="Padding" Value="0 0 50 0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Height" Value="38" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="Foreground" Value="#24DFE1E5" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="WindowBorderStyle"
|
||||
BasedOn="{StaticResource BaseWindowBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="BorderThickness" Value="1,1,0,0" />
|
||||
<Setter Property="BorderBrush" Value="#666666" />
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.95" Color="#333333" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#393b40" />
|
||||
<Setter Property="Background" Value="#2b2d30" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="WindowStyle"
|
||||
BasedOn="{StaticResource BaseWindowStyle}"
|
||||
TargetType="{x:Type Window}">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.5" Color="White" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
TargetType="{x:Type Window}" />
|
||||
<Style
|
||||
x:Key="PendingLineStyle"
|
||||
BasedOn="{StaticResource BasePendingLineStyle}"
|
||||
|
|
@ -63,27 +62,36 @@
|
|||
x:Key="ItemTitleStyle"
|
||||
BasedOn="{StaticResource BaseItemTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemSubTitleStyle"
|
||||
BasedOn="{StaticResource BaseItemSubTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</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
|
||||
x:Key="ItemTitleSelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemTitleSelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemSubTitleSelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#949494" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#545454</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#2e436e</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style
|
||||
|
|
@ -94,7 +102,7 @@
|
|||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border
|
||||
Background="#525252"
|
||||
Background="#393b40"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="2"
|
||||
|
|
@ -106,69 +114,83 @@
|
|||
<Style
|
||||
x:Key="ScrollBarStyle"
|
||||
BasedOn="{StaticResource BaseScrollBarStyle}"
|
||||
TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="#a0a0a0" />
|
||||
</Style>
|
||||
TargetType="{x:Type ScrollBar}" />
|
||||
<Style
|
||||
x:Key="SearchIconStyle"
|
||||
BasedOn="{StaticResource BaseSearchIconStyle}"
|
||||
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="Height" Value="32" />
|
||||
<Setter Property="Opacity" Value="0.2" />
|
||||
</Style>
|
||||
<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" />
|
||||
<Setter Property="Margin" Value="0 8 8 0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<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
|
||||
x:Key="ClockBox"
|
||||
BasedOn="{StaticResource BaseClockBox}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="DateBox"
|
||||
BasedOn="{StaticResource BaseDateBox}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewBorderStyle"
|
||||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#474747" />
|
||||
<Setter Property="Margin" Value="0 0 10 8" />
|
||||
<Setter Property="BorderBrush" Value="#393b40" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="PreviewItemTitleStyle"
|
||||
BasedOn="{StaticResource BasePreviewItemTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewItemSubTitleStyle"
|
||||
BasedOn="{StaticResource BasePreviewItemSubTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#787878" />
|
||||
<Setter Property="Foreground" Value="#6f737a" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewGlyph"
|
||||
BasedOn="{StaticResource BasePreviewGlyph}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ebebeb" />
|
||||
<Setter Property="Foreground" Value="#dfe1e5" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -21,26 +21,25 @@
|
|||
<Setter Property="SelectionBrush" Value="#0a68d8" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
<Setter Property="Padding" Value="0 0 66 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#72767d" />
|
||||
<Setter Property="Background" Value="#36393f" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
<Setter Property="Padding" Value="0 0 66 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowBorderStyle"
|
||||
BasedOn="{StaticResource BaseWindowBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="BorderBrush" Value="#2f3136" />
|
||||
<Setter Property="BorderBrush" Value="#3a3a41" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Background" Value="#36393f" />
|
||||
<Setter Property="Background" Value="#2e2e34" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowStyle"
|
||||
|
|
@ -91,23 +90,25 @@
|
|||
<Setter Property="Cursor" Value="Arrow" />
|
||||
<Setter Property="Foreground" Value="#72767d" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#49443c</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#36363d</SolidColorBrush>
|
||||
<Style
|
||||
x:Key="ItemImageSelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemImageSelectedStyle}"
|
||||
TargetType="{x:Type Image}">
|
||||
<Setter Property="Cursor" Value="Arrow" />
|
||||
</Style>
|
||||
<Style x:Key="HighlightStyle">
|
||||
<Setter Property="Inline.FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style x:Key="HighlightStyle" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#72767d" />
|
||||
<Setter Property="Opacity" Value="0.8" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#72767d" />
|
||||
<Setter Property="Opacity" Value="0.8" />
|
||||
</Style>
|
||||
|
|
@ -124,7 +125,7 @@
|
|||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border
|
||||
Background="#202225"
|
||||
Background="#787881"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="2"
|
||||
|
|
@ -141,9 +142,9 @@
|
|||
x:Key="SeparatorStyle"
|
||||
BasedOn="{StaticResource BaseSeparatorStyle}"
|
||||
TargetType="{x:Type Rectangle}">
|
||||
<Setter Property="Fill" Value="#42454a" />
|
||||
<Setter Property="Fill" Value="#3a3a41" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="12,0,12,6" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SearchIconStyle"
|
||||
|
|
@ -170,6 +171,7 @@
|
|||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#42454a" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewItemTitleStyle"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<Setter Property="Foreground" Value="#f8f8f2" />
|
||||
<Setter Property="CaretBrush" Value="#ffb86c" />
|
||||
<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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#6272a4" />
|
||||
<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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" 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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
@ -24,9 +24,8 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#161614" />
|
||||
<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" />
|
||||
</Style>
|
||||
|
||||
|
|
@ -57,7 +56,7 @@
|
|||
TargetType="{x:Type Rectangle}">
|
||||
<Setter Property="Fill" Value="#785a28" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Margin" Value="0 0 0 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemTitleStyle"
|
||||
|
|
@ -111,12 +110,16 @@
|
|||
<Style x:Key="HighlightStyle">
|
||||
<Setter Property="Inline.FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#5b5a56" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#5b5a56" />
|
||||
</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>
|
||||
|
|
@ -141,7 +144,7 @@
|
|||
x:Key="PreviewBorderStyle"
|
||||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
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="FontSize" Value="18" />
|
||||
<Setter Property="Height" Value="38" />
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
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="Height" Value="38" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
BasedOn="{StaticResource BaseWindowBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#111828" />
|
||||
<Setter Property="BorderBrush" Value="#202938" />
|
||||
<Setter Property="Background" Value="#111828" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
TargetType="{x:Type Rectangle}">
|
||||
<Setter Property="Fill" Value="#202938" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="0,0,0,8" />
|
||||
<Setter Property="Margin" Value="0 0 0 8" />
|
||||
</Style>
|
||||
<Style x:Key="HighlightStyle" />
|
||||
<Style
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
<Setter Property="Background" Value="#111828" />
|
||||
<Setter Property="Width" 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" />
|
||||
</Style>
|
||||
|
||||
|
|
@ -149,7 +149,7 @@
|
|||
x:Key="ClockPanel"
|
||||
BasedOn="{StaticResource ClockPanel}"
|
||||
TargetType="{x:Type StackPanel}">
|
||||
<Setter Property="Margin" Value="0,0,54,0" />
|
||||
<Setter Property="Margin" Value="0 0 54 0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ClockBox"
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
x:Key="PreviewBorderStyle"
|
||||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
TargetType="{x:Type Rectangle}">
|
||||
<Setter Property="Fill" Value="#4c566a" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="12,0,12,8" />
|
||||
<Setter Property="Margin" Value="12 0 12 8" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowStyle"
|
||||
|
|
@ -116,12 +116,16 @@
|
|||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#8391AB" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#8391AB" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#1f1d1f" />
|
||||
<Setter Property="Foreground" Value="#71114b" />
|
||||
</Style>
|
||||
|
||||
|
|
@ -113,12 +112,16 @@
|
|||
<Setter Property="Width" Value="28" />
|
||||
<Setter Property="Height" Value="28" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ed92c9" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ed92c9" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
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="FontSize" Value="16" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
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="Height" Value="24" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<Setter Property="CaretBrush" Value="#FFAA47" />
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
<Setter Property="Padding" Value="0,4,66,0" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="QuerySuggestionBoxStyle"
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<Setter Property="Foreground" Value="#798189" />
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
<Setter Property="Height" Value="42" />
|
||||
<Setter Property="Padding" Value="0,4,66,0" />
|
||||
<Setter Property="Padding" Value="0,0,66,0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowBorderStyle"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
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="Foreground" Value="#ffffff" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
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="Height" Value="38" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<Setter Property="FontSize" Value="22" />
|
||||
<Setter Property="Foreground" 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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource QuerySuggestionBoxForeground}" />
|
||||
<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" />
|
||||
</Style>
|
||||
<Style
|
||||
|
|
@ -113,15 +113,18 @@
|
|||
<Style x:Key="HighlightStyle">
|
||||
<Setter Property="Inline.FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Style
|
||||
x:Key="ItemHotkeyStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeyStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource HotkeyForeground}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" />
|
||||
</Style>
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
|
|
|
|||
|
|
@ -156,13 +156,26 @@
|
|||
</Style>
|
||||
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource HotkeyForeground}" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource NewHotkeyForeground}" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource NewHotkeyForeground}" />
|
||||
</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}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ namespace Flow.Launcher.ViewModel
|
|||
};
|
||||
_selectedResults = Results;
|
||||
|
||||
Results.PropertyChanged += (_, args) =>
|
||||
Results.PropertyChanged += (o, args) =>
|
||||
{
|
||||
switch (args.PropertyName)
|
||||
{
|
||||
|
|
@ -171,7 +171,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
};
|
||||
|
||||
History.PropertyChanged += (_, args) =>
|
||||
History.PropertyChanged += (o, args) =>
|
||||
{
|
||||
switch (args.PropertyName)
|
||||
{
|
||||
|
|
@ -213,7 +213,8 @@ namespace Flow.Launcher.ViewModel
|
|||
queue.Clear();
|
||||
}
|
||||
|
||||
Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
|
||||
if (!_disposed)
|
||||
Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
|
||||
}
|
||||
|
||||
void continueAction(Task t)
|
||||
|
|
@ -297,14 +298,16 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
_ = QueryResultsAsync(isReQuery: true);
|
||||
// When we are re-querying, we should not delay the query
|
||||
_ = QueryResultsAsync(false, isReQuery: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReQuery(bool reselect)
|
||||
{
|
||||
BackToQueryResults();
|
||||
_ = QueryResultsAsync(isReQuery: true, reSelect: reselect);
|
||||
// When we are re-querying, we should not delay the query
|
||||
_ = QueryResultsAsync(false, isReQuery: true, reSelect: reselect);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -312,7 +315,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (_history.Items.Count > 0)
|
||||
{
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query.ToString());
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query);
|
||||
if (lastHistoryIndex < _history.Items.Count)
|
||||
{
|
||||
lastHistoryIndex++;
|
||||
|
|
@ -325,7 +328,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (_history.Items.Count > 0)
|
||||
{
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query.ToString());
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query);
|
||||
if (lastHistoryIndex > 1)
|
||||
{
|
||||
lastHistoryIndex--;
|
||||
|
|
@ -576,14 +579,13 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
_queryText = value;
|
||||
OnPropertyChanged();
|
||||
Query();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void IncreaseWidth()
|
||||
{
|
||||
Settings.WindowSize += 100;
|
||||
MainWindowWidth += 100;
|
||||
Settings.WindowLeft -= 50;
|
||||
OnPropertyChanged(nameof(MainWindowWidth));
|
||||
}
|
||||
|
|
@ -591,14 +593,14 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void DecreaseWidth()
|
||||
{
|
||||
if (MainWindowWidth - 100 < 400 || Settings.WindowSize == 400)
|
||||
if (MainWindowWidth - 100 < 400 || MainWindowWidth == 400)
|
||||
{
|
||||
Settings.WindowSize = 400;
|
||||
MainWindowWidth = 400;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindowWidth -= 100;
|
||||
Settings.WindowLeft += 50;
|
||||
Settings.WindowSize -= 100;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(MainWindowWidth));
|
||||
|
|
@ -638,25 +640,31 @@ namespace Flow.Launcher.ViewModel
|
|||
/// </summary>
|
||||
private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false)
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(async () =>
|
||||
// Must check access so that we will not block the UI thread which cause window visibility issue
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
BackToQueryResults();
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery));
|
||||
return;
|
||||
}
|
||||
|
||||
if (QueryText != queryText)
|
||||
{
|
||||
// re-query is done in QueryText's setter method
|
||||
QueryText = queryText;
|
||||
// set to false so the subsequent set true triggers
|
||||
// PropertyChanged and MoveQueryTextToEnd is called
|
||||
QueryTextCursorMovedToEnd = false;
|
||||
}
|
||||
else if (isReQuery)
|
||||
{
|
||||
await QueryAsync(isReQuery: true);
|
||||
}
|
||||
if (QueryText != queryText)
|
||||
{
|
||||
// Change query text first
|
||||
QueryText = queryText;
|
||||
// When we are changing query from codes, we should not delay the query
|
||||
await QueryAsync(false, isReQuery: false);
|
||||
|
||||
QueryTextCursorMovedToEnd = true;
|
||||
});
|
||||
// set to false so the subsequent set true triggers
|
||||
// PropertyChanged and MoveQueryTextToEnd is called
|
||||
QueryTextCursorMovedToEnd = false;
|
||||
}
|
||||
else if (isReQuery)
|
||||
{
|
||||
// When we are re-querying, we should not delay the query
|
||||
await QueryAsync(false, isReQuery: true);
|
||||
}
|
||||
|
||||
QueryTextCursorMovedToEnd = true;
|
||||
}
|
||||
|
||||
public bool LastQuerySelected { get; set; }
|
||||
|
|
@ -722,14 +730,10 @@ namespace Flow.Launcher.ViewModel
|
|||
// setter won't be called when property value is not changed.
|
||||
// so we need manually call Query()
|
||||
// http://stackoverflow.com/posts/25895769/revisions
|
||||
if (string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
Query();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryText = string.Empty;
|
||||
}
|
||||
QueryText = string.Empty;
|
||||
// When we are changing query because selected results are changed to history or context menu,
|
||||
// we should not delay the query
|
||||
Query(false);
|
||||
|
||||
if (HistorySelected())
|
||||
{
|
||||
|
|
@ -748,15 +752,28 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public Visibility ProgressBarVisibility { get; set; }
|
||||
public Visibility MainWindowVisibility { get; set; }
|
||||
public double MainWindowOpacity { get; set; } = 1;
|
||||
|
||||
// This is to be used for determining the visibility status of the mainwindow instead of MainWindowVisibility
|
||||
|
||||
// This is to be used for determining the visibility status of the main window instead of MainWindowVisibility
|
||||
// because it is more accurate and reliable representation than using Visibility as a condition check
|
||||
public bool MainWindowVisibilityStatus { get; set; } = true;
|
||||
|
||||
public event VisibilityChangedEventHandler VisibilityChanged;
|
||||
|
||||
public Visibility ClockPanelVisibility { get; set; }
|
||||
public Visibility SearchIconVisibility { get; set; }
|
||||
public double ClockPanelOpacity { get; set; } = 1;
|
||||
public double SearchIconOpacity { get; set; } = 1;
|
||||
|
||||
private string _placeholderText;
|
||||
public string PlaceholderText
|
||||
{
|
||||
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText;
|
||||
set
|
||||
{
|
||||
_placeholderText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public double MainWindowWidth
|
||||
{
|
||||
|
|
@ -1023,16 +1040,16 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region Query
|
||||
|
||||
private void Query(bool isReQuery = false)
|
||||
public void Query(bool searchDelay, bool isReQuery = false)
|
||||
{
|
||||
_ = QueryAsync(isReQuery);
|
||||
_ = QueryAsync(searchDelay, isReQuery);
|
||||
}
|
||||
|
||||
private async Task QueryAsync(bool isReQuery = false)
|
||||
private async Task QueryAsync(bool searchDelay, bool isReQuery = false)
|
||||
{
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
await QueryResultsAsync(isReQuery);
|
||||
await QueryResultsAsync(searchDelay, isReQuery);
|
||||
}
|
||||
else if (ContextMenuSelected())
|
||||
{
|
||||
|
|
@ -1054,9 +1071,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
if (selected != null) // SelectedItem returns null if selection is empty.
|
||||
{
|
||||
List<Result> results;
|
||||
|
||||
results = PluginManager.GetContextMenusForPlugin(selected);
|
||||
var results = PluginManager.GetContextMenusForPlugin(selected);
|
||||
results.Add(ContextMenuTopMost(selected));
|
||||
results.Add(ContextMenuPluginInfo(selected.PluginID));
|
||||
|
||||
|
|
@ -1133,13 +1148,15 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private async Task QueryResultsAsync(bool isReQuery = false, bool reSelect = true)
|
||||
private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true)
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
|
||||
var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
|
||||
|
||||
if (query == null) // shortcut expanded
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
if (query == null || plugins.Count == 0) // shortcut expanded
|
||||
{
|
||||
Results.Clear();
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
|
|
@ -1148,6 +1165,18 @@ namespace Flow.Launcher.ViewModel
|
|||
SearchIconVisibility = Visibility.Visible;
|
||||
return;
|
||||
}
|
||||
else if (plugins.Count == 1)
|
||||
{
|
||||
PluginIconPath = plugins.Single().Metadata.IcoPath;
|
||||
PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath);
|
||||
SearchIconVisibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
_updateSource?.Dispose();
|
||||
|
||||
|
|
@ -1172,21 +1201,6 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
_lastQuery = query;
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
if (plugins.Count == 1)
|
||||
{
|
||||
PluginIconPath = plugins.Single().Metadata.IcoPath;
|
||||
PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath);
|
||||
SearchIconVisibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
||||
{
|
||||
// Wait 45 millisecond for query change in global query
|
||||
|
|
@ -1197,19 +1211,22 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
_ = Task.Delay(200, _updateSource.Token).ContinueWith(_ =>
|
||||
{
|
||||
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (!_updateSource.Token.IsCancellationRequested && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
}, _updateSource.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
|
||||
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (!_updateSource.Token.IsCancellationRequested && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
},
|
||||
_updateSource.Token,
|
||||
TaskContinuationOptions.NotOnCanceled,
|
||||
TaskScheduler.Default);
|
||||
|
||||
// plugins is ICollection, meaning LINQ will get the Count and preallocate Array
|
||||
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
|
||||
|
||||
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
|
||||
{
|
||||
false => QueryTaskAsync(plugin, reSelect),
|
||||
false => QueryTaskAsync(plugin, _updateSource.Token),
|
||||
true => Task.CompletedTask
|
||||
}).ToArray();
|
||||
|
||||
|
|
@ -1236,16 +1253,35 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
// Local function
|
||||
async Task QueryTaskAsync(PluginPair plugin, bool reSelect = true)
|
||||
async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
|
||||
{
|
||||
if (searchDelay)
|
||||
{
|
||||
var searchDelayTime = (plugin.Metadata.SearchDelayTime ?? Settings.SearchDelayTime) switch
|
||||
{
|
||||
SearchDelayTime.VeryLong => 250,
|
||||
SearchDelayTime.Long => 200,
|
||||
SearchDelayTime.Normal => 150,
|
||||
SearchDelayTime.Short => 100,
|
||||
SearchDelayTime.VeryShort => 50,
|
||||
_ => 150
|
||||
};
|
||||
|
||||
await Task.Delay(searchDelayTime, token);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
|
||||
// Since it is wrapped within a ThreadPool Thread, the synchronous context is null
|
||||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
IReadOnlyList<Result> results =
|
||||
await PluginManager.QueryForPluginAsync(plugin, query, _updateSource.Token);
|
||||
await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
|
||||
_updateSource.Token.ThrowIfCancellationRequested();
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
IReadOnlyList<Result> resultsCopy;
|
||||
if (results == null)
|
||||
|
|
@ -1255,11 +1291,11 @@ namespace Flow.Launcher.ViewModel
|
|||
else
|
||||
{
|
||||
// make a copy of results to avoid possible issue that FL changes some properties of the records, like score, etc.
|
||||
resultsCopy = DeepCloneResults(results);
|
||||
resultsCopy = DeepCloneResults(results, token);
|
||||
}
|
||||
|
||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
|
||||
_updateSource.Token, reSelect)))
|
||||
token, reSelect)))
|
||||
{
|
||||
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
||||
}
|
||||
|
|
@ -1444,50 +1480,41 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
public async void Show()
|
||||
public void Show()
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
// When application is exiting, the Application.Current will be null
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
// When application is exiting, the Application.Current will be null
|
||||
if (Application.Current?.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// 📌 Remove DWM Cloak (Make the window visible normally)
|
||||
Win32Helper.DWMSetCloakForWindow(mainWindow, false);
|
||||
|
||||
// Clock and SearchIcon hide when show situation
|
||||
// Set clock and search icon opacity
|
||||
var opacity = Settings.UseAnimation ? 0.0 : 1.0;
|
||||
mainWindow.ClockPanel.Opacity = opacity;
|
||||
mainWindow.SearchIcon.Opacity = opacity;
|
||||
|
||||
// QueryText sometimes is null when it is just initialized
|
||||
if (QueryText != null && QueryText.Length != 0)
|
||||
{
|
||||
mainWindow.ClockPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainWindow.ClockPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
ClockPanelOpacity = opacity;
|
||||
SearchIconOpacity = opacity;
|
||||
|
||||
// Set clock and search icon visibility
|
||||
ClockPanelVisibility = string.IsNullOrEmpty(QueryText) ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (PluginIconSource != null)
|
||||
{
|
||||
mainWindow.SearchIcon.Opacity = 0;
|
||||
SearchIconOpacity = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 📌 Restore UI elements
|
||||
//mainWindow.SearchIcon.Visibility = Visibility.Visible;
|
||||
}
|
||||
}, DispatcherPriority.Render);
|
||||
|
||||
// Update WPF properties
|
||||
MainWindowVisibility = Visibility.Visible;
|
||||
MainWindowOpacity = 1;
|
||||
MainWindowVisibilityStatus = true;
|
||||
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true });
|
||||
|
||||
// Switch keyboard layout
|
||||
if (StartWithEnglishMode)
|
||||
{
|
||||
Win32Helper.SwitchToEnglishKeyboardLayout(true);
|
||||
|
|
@ -1530,15 +1557,19 @@ namespace Flow.Launcher.ViewModel
|
|||
break;
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
// When application is exiting, the Application.Current will be null
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
// When application is exiting, the Application.Current will be null
|
||||
if (Application.Current?.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// 📌 Set Opacity of icon and clock to 0 and apply Visibility.Hidden
|
||||
// Set clock and search icon opacity
|
||||
var opacity = Settings.UseAnimation ? 0.0 : 1.0;
|
||||
mainWindow.ClockPanel.Opacity = opacity;
|
||||
mainWindow.SearchIcon.Opacity = opacity;
|
||||
mainWindow.ClockPanel.Visibility = Visibility.Hidden;
|
||||
ClockPanelOpacity = opacity;
|
||||
SearchIconOpacity = opacity;
|
||||
|
||||
// Set clock and search icon visibility
|
||||
ClockPanelVisibility = Visibility.Hidden;
|
||||
SearchIconVisibility = Visibility.Hidden;
|
||||
|
||||
// Force UI update
|
||||
|
|
@ -1548,15 +1579,18 @@ namespace Flow.Launcher.ViewModel
|
|||
// 📌 Apply DWM Cloak (Completely hide the window)
|
||||
Win32Helper.DWMSetCloakForWindow(mainWindow, true);
|
||||
}
|
||||
});
|
||||
}, DispatcherPriority.Render);
|
||||
|
||||
// Switch keyboard layout
|
||||
if (StartWithEnglishMode)
|
||||
{
|
||||
Win32Helper.RestorePreviousKeyboardLayout();
|
||||
}
|
||||
|
||||
// Delay for a while to make sure clock will not flicker
|
||||
await Task.Delay(50);
|
||||
|
||||
// Update WPF properties
|
||||
//MainWindowOpacity = 0;
|
||||
MainWindowVisibilityStatus = false;
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false });
|
||||
|
|
@ -1575,7 +1609,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// To avoid deadlock, this method should not be called from main thread
|
||||
/// </summary>
|
||||
public void UpdateResultView(ICollection<ResultsForUpdate> resultsForUpdates)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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.Media;
|
||||
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;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
|
|
@ -28,7 +28,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private string PluginManagerActionKeyword
|
||||
private static string PluginManagerActionKeyword
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -43,9 +43,10 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private async void LoadIconAsync()
|
||||
private async Task LoadIconAsync()
|
||||
{
|
||||
Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath);
|
||||
OnPropertyChanged(nameof(Image));
|
||||
}
|
||||
|
||||
public ImageSource Image
|
||||
|
|
@ -53,12 +54,13 @@ namespace Flow.Launcher.ViewModel
|
|||
get
|
||||
{
|
||||
if (_image == ImageLoader.MissingImage)
|
||||
LoadIconAsync();
|
||||
_ = LoadIconAsync();
|
||||
|
||||
return _image;
|
||||
}
|
||||
set => _image = value;
|
||||
}
|
||||
|
||||
public bool PluginState
|
||||
{
|
||||
get => !PluginPair.Metadata.Disabled;
|
||||
|
|
@ -68,6 +70,7 @@ namespace Flow.Launcher.ViewModel
|
|||
PluginSettingsObject.Disabled = !value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
|
|
@ -80,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 bool _isExpanded;
|
||||
|
||||
|
|
@ -87,9 +100,13 @@ namespace Flow.Launcher.ViewModel
|
|||
public Control BottomPart1 => IsExpanded ? _bottomPart1 ??= new InstalledPluginDisplayKeyword() : null;
|
||||
|
||||
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
|
||||
=> IsExpanded
|
||||
? _settingControl
|
||||
|
|
@ -99,20 +116,33 @@ namespace Flow.Launcher.ViewModel
|
|||
: null;
|
||||
private ImageSource _image = ImageLoader.MissingImage;
|
||||
|
||||
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ? Visibility.Collapsed : Visibility.Visible;
|
||||
public string InitilizaTime => PluginPair.Metadata.InitTime + "ms";
|
||||
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
|
||||
Visibility.Collapsed : Visibility.Visible;
|
||||
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
|
||||
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
|
||||
public string Version => InternationalizationManager.Instance.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 Version => App.API.GetTranslation("plugin_query_version") + " " + PluginPair.Metadata.Version;
|
||||
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 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 OnActionKeywordsChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(ActionKeywordsText));
|
||||
}
|
||||
|
||||
public void OnSearchDelayTimeChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(SearchDelayTimeText));
|
||||
}
|
||||
|
||||
public void ChangePriority(int newPriority)
|
||||
{
|
||||
PluginPair.Metadata.Priority = newPriority;
|
||||
|
|
@ -123,7 +153,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void EditPluginPriority()
|
||||
{
|
||||
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
|
||||
var priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
|
||||
priorityChangeWindow.ShowDialog();
|
||||
}
|
||||
|
||||
|
|
@ -151,8 +181,15 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void SetActionKeywords()
|
||||
{
|
||||
ActionKeywords changeKeywordsWindow = new ActionKeywords(this);
|
||||
var changeKeywordsWindow = new ActionKeywords(this);
|
||||
changeKeywordsWindow.ShowDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetSearchDelayTime()
|
||||
{
|
||||
var searchDelayTimeWindow = new SearchDelayTimeWindow(this);
|
||||
searchDelayTimeWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
}
|
||||
else
|
||||
{
|
||||
PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin,
|
||||
await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
|
||||
downloadToFilePath);
|
||||
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
|
|
@ -433,7 +433,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
if (cts.IsCancellationRequested)
|
||||
return;
|
||||
else
|
||||
PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
|
||||
await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
|
||||
downloadToFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -684,7 +684,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Title = $"{x.Metadata.Name} by {x.Metadata.Author}",
|
||||
SubTitle = x.Metadata.Description,
|
||||
IcoPath = x.Metadata.IcoPath,
|
||||
Action = e =>
|
||||
AsyncAction = async e =>
|
||||
{
|
||||
string message;
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
|
|
@ -707,7 +707,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
Context.API.HideMainWindow();
|
||||
Uninstall(x.Metadata);
|
||||
await UninstallAsync(x.Metadata);
|
||||
if (Settings.AutoRestartAfterChanging)
|
||||
{
|
||||
Context.API.RestartApp();
|
||||
|
|
@ -732,7 +732,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return Search(results, search);
|
||||
}
|
||||
|
||||
private void Uninstall(PluginMetadata plugin)
|
||||
private async Task UninstallAsync(PluginMetadata plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -740,7 +740,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
|
||||
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
|
||||
PluginManager.UninstallPlugin(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
|
||||
await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
using Flow.Launcher.Plugin.Program.Views;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Path = System.IO.Path;
|
||||
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
||||
|
|
@ -188,9 +191,61 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
|
||||
{
|
||||
_win32Storage = new BinaryStorage<Win32[]>("Win32");
|
||||
FilesFolders.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
|
||||
static void MoveFile(string sourcePath, string destinationPath)
|
||||
{
|
||||
if (!File.Exists(sourcePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(destinationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(sourcePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore, we will handle next time we start the plugin
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var destinationDirectory = Path.GetDirectoryName(destinationPath);
|
||||
if (!Directory.Exists(destinationDirectory) && (!string.IsNullOrEmpty(destinationDirectory)))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destinationDirectory);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore, we will handle next time we start the plugin
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
File.Move(sourcePath, destinationPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore, we will handle next time we start the plugin
|
||||
}
|
||||
}
|
||||
|
||||
// Move old cache files to the new cache directory
|
||||
var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"Win32.cache");
|
||||
var newWin32CacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"Win32.cache");
|
||||
MoveFile(oldWin32CacheFile, newWin32CacheFile);
|
||||
var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache");
|
||||
var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache");
|
||||
MoveFile(oldUWPCacheFile, newUWPCacheFile);
|
||||
|
||||
_win32Storage = new BinaryStorage<Win32[]>("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_win32s = await _win32Storage.TryLoadAsync(Array.Empty<Win32>());
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP");
|
||||
_uwpStorage = new BinaryStorage<UWPApp[]>("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
||||
_uwps = await _uwpStorage.TryLoadAsync(Array.Empty<UWPApp>());
|
||||
});
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
|
@ -191,8 +190,6 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private List<Result> Commands()
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version);
|
||||
var userDataPath = DataLocation.DataDirectory();
|
||||
var recycleBinFolder = "shell:RecycleBinFolder";
|
||||
results.AddRange(new[]
|
||||
{
|
||||
|
|
@ -302,7 +299,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
new Result
|
||||
{
|
||||
Title = "Hibernate",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe945"),
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe8be"),
|
||||
IcoPath = "Images\\hibernate.png",
|
||||
Action= c =>
|
||||
{
|
||||
|
|
@ -325,7 +322,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
{
|
||||
Title = "Empty Recycle Bin",
|
||||
IcoPath = "Images\\recyclebin.png",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xea99"),
|
||||
Action = c =>
|
||||
{
|
||||
// http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html
|
||||
|
|
@ -361,6 +358,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
{
|
||||
Title = "Exit",
|
||||
IcoPath = "Images\\app.png",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"),
|
||||
Action = c =>
|
||||
{
|
||||
Application.Current.MainWindow.Close();
|
||||
|
|
@ -370,6 +368,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
new Result
|
||||
{
|
||||
Title = "Save Settings",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xea35"),
|
||||
IcoPath = "Images\\app.png",
|
||||
Action = c =>
|
||||
{
|
||||
|
|
@ -381,6 +380,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
},
|
||||
new Result
|
||||
{
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72c"),
|
||||
Title = "Restart Flow Launcher",
|
||||
IcoPath = "Images\\app.png",
|
||||
Action = c =>
|
||||
|
|
@ -392,6 +392,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
new Result
|
||||
{
|
||||
Title = "Settings",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf210"),
|
||||
IcoPath = "Images\\app.png",
|
||||
Action = c =>
|
||||
{
|
||||
|
|
@ -403,6 +404,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
{
|
||||
Title = "Reload Plugin Data",
|
||||
IcoPath = "Images\\app.png",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe72c"),
|
||||
Action = c =>
|
||||
{
|
||||
// Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen.
|
||||
|
|
@ -421,6 +423,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
new Result
|
||||
{
|
||||
Title = "Check For Update",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xede4"),
|
||||
IcoPath = "Images\\checkupdate.png",
|
||||
Action = c =>
|
||||
{
|
||||
|
|
@ -431,19 +434,21 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
},
|
||||
new Result
|
||||
{
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
|
||||
Title = "Open Log Location",
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = logPath,
|
||||
AutoCompleteText = logPath,
|
||||
CopyText = DataLocation.VersionLogDirectory,
|
||||
AutoCompleteText = DataLocation.VersionLogDirectory,
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(logPath);
|
||||
_context.API.OpenDirectory(DataLocation.VersionLogDirectory);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "Flow Launcher Tips",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe897"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = Constant.Documentation,
|
||||
AutoCompleteText = Constant.Documentation,
|
||||
|
|
@ -456,12 +461,13 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
new Result
|
||||
{
|
||||
Title = "Flow Launcher UserData Folder",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = userDataPath,
|
||||
AutoCompleteText = userDataPath,
|
||||
CopyText = DataLocation.DataDirectory(),
|
||||
AutoCompleteText = DataLocation.DataDirectory(),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(userDataPath);
|
||||
_context.API.OpenDirectory(DataLocation.DataDirectory());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -480,7 +486,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
{
|
||||
Title = "Set Flow Launcher Theme",
|
||||
IcoPath = "Images\\app.png",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"),
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue790"),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.ChangeQuery($"{ThemeSelector.Keyword} ");
|
||||
|
|
|
|||
|
|
@ -9,9 +9,13 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
{
|
||||
public const string Keyword = "fltheme";
|
||||
|
||||
private readonly Theme _theme;
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
// Do not initialize it in the constructor, because it will cause null reference in
|
||||
// var dicts = Application.Current.Resources.MergedDictionaries; line of Theme
|
||||
private Theme theme = null;
|
||||
private Theme Theme => theme ??= Ioc.Default.GetRequiredService<Theme>();
|
||||
|
||||
#region Theme Selection
|
||||
|
||||
// Theme select codes simplified from SettingsPaneThemeViewModel.cs
|
||||
|
|
@ -19,24 +23,23 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private Theme.ThemeData _selectedTheme;
|
||||
public Theme.ThemeData SelectedTheme
|
||||
{
|
||||
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
|
||||
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme());
|
||||
set
|
||||
{
|
||||
_selectedTheme = value;
|
||||
_theme.ChangeTheme(value.FileNameWithoutExtension);
|
||||
Theme.ChangeTheme(value.FileNameWithoutExtension);
|
||||
|
||||
_ = _theme.RefreshFrameAsync();
|
||||
_ = Theme.RefreshFrameAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Theme.ThemeData> Themes => _theme.LoadAvailableThemes();
|
||||
private List<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();
|
||||
|
||||
#endregion
|
||||
|
||||
public ThemeSelector(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
_theme = Ioc.Default.GetRequiredService<Theme>();
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Plugin.WebSearch
|
||||
|
|
@ -181,11 +179,10 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
|
||||
// Default images directory is in the WebSearch's application folder
|
||||
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
|
||||
Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
|
||||
FilesFolders.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
|
||||
|
||||
// Custom images directory is in the WebSearch's data location folder
|
||||
var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName);
|
||||
CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons");
|
||||
// Custom images directory is in the WebSearch's data location folder
|
||||
CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons");
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,5 +31,6 @@
|
|||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",
|
||||
"IcoPath": "Images\\web_search.png"
|
||||
"IcoPath": "Images\\web_search.png",
|
||||
"SearchDelayTime": "VeryLong"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue