Merge pull request #3350 from Jack251970/search_delay

Input Smooth & Search delay
This commit is contained in:
Jack Ye 2025-03-31 18:00:06 +08:00 committed by GitHub
commit 6a964b031a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 632 additions and 118 deletions

View file

@ -622,7 +622,7 @@ namespace Flow.Launcher.Core.Plugin
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
}
Settings.Plugins.Remove(plugin.ID);
Settings.RemovePluginSettings(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
}

View file

@ -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>();
@ -132,7 +132,7 @@ 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))
@ -146,7 +146,7 @@ namespace Flow.Launcher.Core.Plugin
});
}
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))

View file

@ -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,17 +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.TryGetValue(metadata.ID, out var settings))
{
// 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;
@ -49,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>

View file

@ -112,7 +112,7 @@ 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
@ -310,7 +310,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
bool _hideNotifyIcon { get; set; }
public bool HideNotifyIcon
{
get { return _hideNotifyIcon; }
get => _hideNotifyIcon;
set
{
_hideNotifyIcon = value;
@ -320,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;
@ -342,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();

View file

@ -92,12 +92,18 @@ namespace Flow.Launcher.Plugin
/// All action keywords of plugin.
/// </summary>
public List<string> ActionKeywords { get; set; }
/// <summary>
/// Hide plugin keyword setting panel.
/// </summary>
public bool HideActionKeywordPanel { get; set; }
/// <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>

View file

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

View file

@ -102,6 +102,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="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>
@ -118,6 +127,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>
@ -133,6 +144,7 @@
<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>
@ -353,6 +365,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>

View file

@ -251,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>

View file

@ -22,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;
@ -165,6 +167,8 @@ namespace Flow.Launcher
// 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)
@ -227,6 +231,7 @@ namespace Flow.Launcher
}
};
// Settings property changed event
_settings.PropertyChanged += (o, e) =>
{
switch (e.PropertyName)
@ -1050,7 +1055,7 @@ namespace Flow.Launcher
{
e.Handled = true;
}
#endregion
#region Placeholder
@ -1101,6 +1106,17 @@ namespace Flow.Launcher
}
}
#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
#region IDisposable

View file

@ -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>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
using System;
using System.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);
}
}
}

View file

@ -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

View file

@ -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

View file

@ -172,6 +172,30 @@
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:CardGroup Margin="0 14 0 0">
<cc:Card
Title="{DynamicResource searchDelay}"
Icon="&#xE961;"
Sub="{DynamicResource searchDelayToolTip}">
<ui:ToggleSwitch
IsOn="{Binding Settings.SearchQueryResultsWithDelay}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Title="{DynamicResource searchDelayTime}"
Icon="&#xE916;"
Sub="{DynamicResource searchDelayTimeToolTip}">
<ComboBox
MaxWidth="200"
DisplayMemberPath="Display"
ItemsSource="{Binding SearchDelayTimes}"
SelectedItem="{Binding SearchDelayTime}"
SelectedValuePath="Value" />
</cc:Card>
</cc:CardGroup>
<cc:CardGroup Margin="0 14 0 0">
<cc:Card Title="{DynamicResource querySearchPrecision}" Sub="{DynamicResource querySearchPrecisionToolTip}">
<ComboBox

View file

@ -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)
{
@ -298,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]
@ -313,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++;
@ -326,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--;
@ -577,7 +579,6 @@ namespace Flow.Launcher.ViewModel
{
_queryText = value;
OnPropertyChanged();
Query();
}
}
@ -646,19 +647,21 @@ namespace Flow.Launcher.ViewModel
return;
}
BackToQueryResults();
if (QueryText != queryText)
{
// re-query is done in QueryText's setter method
// Change query text first
QueryText = queryText;
// When we are changing query from codes, we should not delay the query
await QueryAsync(false, isReQuery: false);
// set to false so the subsequent set true triggers
// PropertyChanged and MoveQueryTextToEnd is called
QueryTextCursorMovedToEnd = false;
}
else if (isReQuery)
{
await QueryAsync(isReQuery: true);
// When we are re-querying, we should not delay the query
await QueryAsync(false, isReQuery: true);
}
QueryTextCursorMovedToEnd = true;
@ -727,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())
{
@ -754,7 +753,7 @@ namespace Flow.Launcher.ViewModel
public Visibility ProgressBarVisibility { get; set; }
public Visibility MainWindowVisibility { get; set; }
// 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;
@ -1041,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())
{
@ -1072,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));
@ -1151,13 +1148,18 @@ 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)
{
// TODO: Remove debug codes.
System.Diagnostics.Debug.WriteLine("!!!QueryResults");
_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;
@ -1166,6 +1168,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();
@ -1190,21 +1204,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
@ -1215,22 +1214,36 @@ 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();
// TODO: Remove debug codes.
System.Diagnostics.Debug.Write($"!!!Querying {query.RawQuery}: search delay {searchDelay}");
foreach (var plugin in plugins)
{
if (!plugin.Metadata.Disabled)
{
System.Diagnostics.Debug.Write($"{plugin.Metadata.Name}, ");
}
}
System.Diagnostics.Debug.Write("\n");
try
{
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
@ -1254,16 +1267,43 @@ 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
};
// TODO: Remove debug codes.
System.Diagnostics.Debug.WriteLine($"!!!{plugin.Metadata.Name} Waiting {searchDelayTime} ms");
await Task.Delay(searchDelayTime, token);
// TODO: Remove debug codes.
System.Diagnostics.Debug.WriteLine($"!!!{plugin.Metadata.Name} Waited {searchDelayTime} ms");
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();
// TODO: Remove debug codes.
System.Diagnostics.Debug.WriteLine($"!!!{query.RawQuery} Querying {plugin.Metadata.Name}");
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)
@ -1273,11 +1313,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");
}
@ -1591,7 +1631,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)
{

View file

@ -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();
}
}
}

View file

@ -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"
}