Skip to content
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

Input Smooth & Search delay #3350

Draft
wants to merge 23 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Contributor 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
Contributor 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
Contributor 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