Skip to content

Input Smooth & Search delay #3350

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 57 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b1a4681
Support search delay
Jack251970 Mar 15, 2025
3abcebd
Add settings ui
Jack251970 Mar 15, 2025
a41356c
Improve strings
Jack251970 Mar 15, 2025
dc3f663
Use property changed to change
Jack251970 Mar 16, 2025
e98b144
Improve code quality
Jack251970 Mar 16, 2025
35617e9
Merge branch 'dev' into search_delay
Jack251970 Mar 17, 2025
c114f2d
Fix build issue
Jack251970 Mar 17, 2025
8cdb872
Remove auto complete text clear because FL uses binding
Jack251970 Mar 18, 2025
e98964a
Change to one way binding mode
Jack251970 Mar 18, 2025
fc4b5c9
Fix
Jack251970 Mar 18, 2025
5dd9e8d
Add plugin search delay settings panel
Jack251970 Mar 18, 2025
2c949d6
Fix setting control
Jack251970 Mar 18, 2025
933582b
Change name to search delay & Support plugin search delay
Jack251970 Mar 18, 2025
2738636
Improve debug codes
Jack251970 Mar 18, 2025
1e01186
Merge branch 'dev' into search_delay
Jack251970 Mar 20, 2025
dc92f6a
Fix build issue
Jack251970 Mar 20, 2025
102636f
Improve code quality
Jack251970 Mar 20, 2025
8c387d0
Improve code quality & Add oneway bind mode
Jack251970 Mar 20, 2025
d785991
Improve search performance
Jack251970 Mar 21, 2025
2f4e140
Improve query async local function
Jack251970 Mar 21, 2025
2619851
Improve indention
Jack251970 Mar 21, 2025
bebe86d
Improve query async local function
Jack251970 Mar 21, 2025
529a219
Change serach delay method
Jack251970 Mar 21, 2025
5a88a1f
Merge branch 'dev' into search_delay
Jack251970 Mar 26, 2025
23470b6
Code quality
Jack251970 Mar 26, 2025
84bd5f5
Fix build issue
Jack251970 Mar 26, 2025
9cf34b2
Merge branch 'dev' into search_delay
Jack251970 Mar 26, 2025
7f5480d
Merge branch 'dev' into search_delay
Jack251970 Mar 30, 2025
1f6ab1c
Fix build issue
Jack251970 Mar 30, 2025
aec11ce
Code quality
Jack251970 Mar 30, 2025
cc7dc4a
Introduce unified settings panel design for search delay panel
Jack251970 Mar 30, 2025
72b0fbf
Add code comments
Jack251970 Mar 30, 2025
1ddfabc
Update code comments
Jack251970 Mar 30, 2025
63378e1
Code quality
Jack251970 Mar 30, 2025
1ede69c
Make Plugin dictionary private
Jack251970 Mar 30, 2025
d041fe8
Support default value
Jack251970 Mar 30, 2025
9ac6d58
Add default search delay for WebSearch plugins
Jack251970 Mar 30, 2025
2ea7ab2
Add todo
Jack251970 Mar 30, 2025
756ac0b
Remove debug codes
Jack251970 Mar 30, 2025
6604247
Fix typos & Improve code quality
Jack251970 Mar 30, 2025
2273d0c
Add debug codes
Jack251970 Mar 30, 2025
750cba2
Fix search delay runtime change issue
Jack251970 Mar 30, 2025
0dba9bc
Add search delay speed
Jack251970 Mar 30, 2025
659356a
Support plugin search delay
Jack251970 Mar 30, 2025
8a2ec4d
Update Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewM…
Jack251970 Mar 31, 2025
dd9514b
Support websearch plugin default search delay
Jack251970 Mar 31, 2025
12a3bbe
Code quality
Jack251970 Mar 31, 2025
a333041
Fix build issue
Jack251970 Mar 31, 2025
c925a79
Support changing plugin search delay
Jack251970 Mar 31, 2025
e9c1cff
Code quality
Jack251970 Mar 31, 2025
dec1e77
Improve code comments
Jack251970 Mar 31, 2025
c4cd51c
Change to search delay time
Jack251970 Mar 31, 2025
72a59ba
Change file names
Jack251970 Mar 31, 2025
5243d74
Improve code comments
Jack251970 Mar 31, 2025
b860bb2
Code quality
Jack251970 Mar 31, 2025
d42279b
Fix typo & Fix tip issue
Jack251970 Mar 31, 2025
39f41e4
Improve string resources
Jack251970 Mar 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,18 @@ public bool HideNotifyIcon
public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactivated { get; set; } = true;

bool _searchQueryResultsWithDelay { get; set; }
public bool SearchQueryResultsWithDelay
{
get => _searchQueryResultsWithDelay;
set
{
_searchQueryResultsWithDelay = value;
OnPropertyChanged();
}
}
public int SearchInputDelay { get; set; } = 120;

[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;

Expand Down
1 change: 1 addition & 0 deletions Flow.Launcher/Flow.Launcher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
<PackageReference Include="System.Reactive.Linq" Version="6.0.1" />
<PackageReference Include="TaskScheduler" Version="2.12.1" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.1" />
</ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions Flow.Launcher/Languages/en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
<system:String x:Key="hideOnStartupToolTip">Flow Launcher search window is hidden in the tray after starting up.</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</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">Search Delay Time</system:String>
<system:String x:Key="searchDelayTimeToolTip">Delay time after which search results appear when typing is stopped. Default is 120ms.</system:String>
<system:String x:Key="querySearchPrecision">Query Search Precision</system:String>
<system:String x:Key="querySearchPrecisionToolTip">Changes minimum match score required for results.</system:String>
<system:String x:Key="SearchPrecisionNone">None</system:String>
Expand Down
178 changes: 128 additions & 50 deletions Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Controls;
using System.Windows.Forms;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
Expand All @@ -27,6 +26,7 @@
using System.Windows.Media;
using System.Windows.Interop;
using Windows.Win32;
using System.Reactive.Linq;

namespace Flow.Launcher
{
Expand Down Expand Up @@ -195,6 +195,8 @@ private void OnLoaded(object sender, RoutedEventArgs _)
InitializePosition();
InitializePosition();
PreviewReset();
// Setup search text box reactiveness
SetupSearchTextBoxReactiveness(_settings.SearchQueryResultsWithDelay);
// since the default main window visibility is visible
// so we need set focus during startup
QueryTextBox.Focus();
Expand All @@ -204,63 +206,63 @@ private void OnLoaded(object sender, RoutedEventArgs _)
switch (e.PropertyName)
{
case nameof(MainViewModel.MainWindowVisibilityStatus):
{
Dispatcher.Invoke(() =>
{
if (_viewModel.MainWindowVisibilityStatus)
Dispatcher.Invoke(() =>
{
if (_settings.UseSound)
if (_viewModel.MainWindowVisibilityStatus)
{
SoundPlay();
if (_settings.UseSound)
{
SoundPlay();
}

UpdatePosition();
PreviewReset();
Activate();
QueryTextBox.Focus();
_settings.ActivateTimes++;
if (!_viewModel.LastQuerySelected)
{
QueryTextBox.SelectAll();
_viewModel.LastQuerySelected = true;
}

if (_viewModel.ProgressBarVisibility == Visibility.Visible &&
isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Begin(ProgressBar, true);
isProgressBarStoryboardPaused = false;
}

if (_settings.UseAnimation)
WindowAnimator();
}

UpdatePosition();
PreviewReset();
Activate();
QueryTextBox.Focus();
_settings.ActivateTimes++;
if (!_viewModel.LastQuerySelected)
else if (!isProgressBarStoryboardPaused)
{
QueryTextBox.SelectAll();
_viewModel.LastQuerySelected = true;
_progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}

if (_viewModel.ProgressBarVisibility == Visibility.Visible &&
isProgressBarStoryboardPaused)
});
break;
}
case nameof(MainViewModel.ProgressBarVisibility):
{
Dispatcher.Invoke(() =>
{
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}
else if (_viewModel.MainWindowVisibilityStatus &&
isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Begin(ProgressBar, true);
isProgressBarStoryboardPaused = false;
}

if (_settings.UseAnimation)
WindowAnimator();
}
else if (!isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}
});
break;
}
case nameof(MainViewModel.ProgressBarVisibility):
{
Dispatcher.Invoke(() =>
{
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}
else if (_viewModel.MainWindowVisibilityStatus &&
isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Begin(ProgressBar, true);
isProgressBarStoryboardPaused = false;
}
});
break;
}
});
break;
}
case nameof(MainViewModel.QueryTextCursorMovedToEnd):
if (_viewModel.QueryTextCursorMovedToEnd)
{
Expand Down Expand Up @@ -296,6 +298,9 @@ private void OnLoaded(object sender, RoutedEventArgs _)
case nameof(Settings.WindowTop):
Top = _settings.WindowTop;
break;
case nameof(Settings.SearchQueryResultsWithDelay):
SetupSearchTextBoxReactiveness(_settings.SearchQueryResultsWithDelay);
break;
}
};
}
Expand Down Expand Up @@ -415,10 +420,10 @@ private void InitializeNotifyIcon()
{
switch (e.Button)
{
case MouseButtons.Left:
case System.Windows.Forms.MouseButtons.Left:
_viewModel.ToggleFlowLauncher();
break;
case MouseButtons.Right:
case System.Windows.Forms.MouseButtons.Right:

contextMenu.IsOpen = true;
// Get context menu handle and bring it to the foreground
Expand Down Expand Up @@ -857,5 +862,78 @@ private void QueryTextBox_KeyUp(object sender, KeyEventArgs e)
be.UpdateSource();
}
}

#region Search Delay

// Edited from: https://github.com/microsoft/PowerToys

private IDisposable _reactiveSubscription;

private void SetupSearchTextBoxReactiveness(bool showResultsWithDelay)
{
if (_reactiveSubscription != null)
{
_reactiveSubscription.Dispose();
_reactiveSubscription = null;
}

QueryTextBox.TextChanged -= QueryTextBox_TextChanged;

if (showResultsWithDelay)
{
_reactiveSubscription = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
conversion => (sender, eventArg) => conversion(sender, eventArg),
add => QueryTextBox.TextChanged += add,
remove => QueryTextBox.TextChanged -= remove)
.Do(@event => ClearAutoCompleteText((TextBox)@event.Sender))
.Throttle(TimeSpan.FromMilliseconds(_settings.SearchInputDelay))
.Do(@event => Dispatcher.Invoke(() => PerformSearchQuery((TextBox)@event.Sender)))
.Subscribe();
}
else
{
QueryTextBox.TextChanged += QueryTextBox_TextChanged;
}
}

private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
ClearAutoCompleteText(textBox);
PerformSearchQuery(textBox);
}

private void ClearAutoCompleteText(TextBox textBox)
{
var text = textBox.Text;
var autoCompleteText = QueryTextSuggestionBox.Text;

if (ShouldAutoCompleteTextBeEmpty(text, autoCompleteText))
{
QueryTextSuggestionBox.Text = string.Empty;
}
}

private static bool ShouldAutoCompleteTextBeEmpty(string queryText, string autoCompleteText)
{
if (string.IsNullOrEmpty(autoCompleteText))
{
return false;
}
else
{
// Using Ordinal this is internal
return string.IsNullOrEmpty(queryText) || !autoCompleteText.StartsWith(queryText, StringComparison.Ordinal);
}
}

private void PerformSearchQuery(TextBox textBox)
{
var text = textBox.Text;
_viewModel.QueryText = text;
_viewModel.Query();
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ public bool PortableMode
}
}

public IEnumerable<int> SearchInputDelayRange => new List<int>()
{
30, 60, 90, 120, 150, 180, 210, 240, 270, 300
};

public List<LastQueryModeData> LastQueryModes { get; } =
DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery");

Expand Down
22 changes: 22 additions & 0 deletions Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,28 @@
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
Width="100"
ItemsSource="{Binding SearchInputDelayRange}"
SelectedItem="{Binding Settings.SearchInputDelay}" />
</cc:Card>
</cc:CardGroup>

<cc:CardGroup Margin="0 14 0 0">
<cc:Card Title="{DynamicResource querySearchPrecision}" Sub="{DynamicResource querySearchPrecisionToolTip}">
<ComboBox
Expand Down
3 changes: 2 additions & 1 deletion Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,6 @@ public string QueryText
{
_queryText = value;
OnPropertyChanged();
Query();
}
}

Expand Down Expand Up @@ -631,6 +630,7 @@ public void ChangeQueryText(string queryText, bool isReQuery = false)
{
// re-query is done in QueryText's setter method
QueryText = queryText;
Query();
Copy link
Member

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

From original codes, when QueryText is changed, it will call Query().

Copy link
Member Author

Choose a reason for hiding this comment

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

And I remove Query() in QueryText setter, so we need to manually call it.

Copy link
Member

Choose a reason for hiding this comment

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

Why do we do that?

Copy link
Member Author

@Jack251970 Jack251970 Mar 16, 2025

Choose a reason for hiding this comment

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

QueryText is a property used by others functions in MainViewModel like auto complete text, backspace event, change query text event, etc. So we need to update it once users input.

Query is a function to load results and we can delay them.

// set to false so the subsequent set true triggers
// PropertyChanged and MoveQueryTextToEnd is called
QueryTextCursorMovedToEnd = false;
Expand Down Expand Up @@ -695,6 +695,7 @@ private ResultsViewModel SelectedResults
else
{
QueryText = string.Empty;
Query();
}
}

Expand Down
Loading