mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3370 from Jack251970/history_preview1
Support Preview Panel for History Results
This commit is contained in:
commit
350276dbf5
7 changed files with 193 additions and 122 deletions
|
|
@ -32,6 +32,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png");
|
||||
public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png");
|
||||
public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png");
|
||||
public static readonly string HistoryIcon = Path.Combine(ImagesDirectory, "history.png");
|
||||
|
||||
public static string PythonPath;
|
||||
public static string NodePath;
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@
|
|||
|
||||
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Grid>
|
||||
<Grid x:Name="QueryBoxArea">
|
||||
<Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}">
|
||||
<Grid>
|
||||
<TextBox
|
||||
|
|
@ -339,7 +339,7 @@
|
|||
Y2="0" />
|
||||
</Grid>
|
||||
|
||||
<Grid ClipToBounds="True">
|
||||
<Grid x:Name="MiddleSeparatorArea" ClipToBounds="True">
|
||||
<ContentControl>
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
|
|
@ -379,7 +379,7 @@
|
|||
</ContentControl>
|
||||
</Grid>
|
||||
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
<Border x:Name="ResultPreviewAreaBoarder" Style="{DynamicResource WindowRadius}">
|
||||
<Border.Clip>
|
||||
<MultiBinding Converter="{StaticResource BorderClipConverter}">
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
|
||||
|
|
@ -387,12 +387,14 @@
|
|||
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
|
||||
</MultiBinding>
|
||||
</Border.Clip>
|
||||
<Grid>
|
||||
|
||||
<Grid x:Name="ResultPreviewArea">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="80" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="0.85*" MinWidth="244" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
x:Name="ResultArea"
|
||||
Grid.Column="0"
|
||||
|
|
@ -419,7 +421,9 @@
|
|||
RightClickResultCommand="{Binding RightClickResultCommand}" />
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
|
||||
<GridSplitter
|
||||
x:Name="PreviewMiddleSeparator"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
|
|
@ -433,6 +437,7 @@
|
|||
</ControlTemplate>
|
||||
</GridSplitter.Template>
|
||||
</GridSplitter>
|
||||
|
||||
<Grid
|
||||
x:Name="Preview"
|
||||
Grid.Column="2"
|
||||
|
|
@ -442,7 +447,7 @@
|
|||
<Border
|
||||
MinHeight="380"
|
||||
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
|
||||
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
|
||||
DataContext="{Binding PreviewSelectedItem, Mode=OneWay}"
|
||||
Visibility="{Binding ShowDefaultPreview}">
|
||||
<Grid
|
||||
Margin="0 0 10 5"
|
||||
|
|
@ -519,7 +524,7 @@
|
|||
MaxHeight="{Binding ElementName=ResultListBox, Path=ActualHeight}"
|
||||
Padding="0 0 10 10"
|
||||
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
|
||||
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
|
||||
DataContext="{Binding PreviewSelectedItem, Mode=OneWay}"
|
||||
Visibility="{Binding ShowCustomizedPreview}">
|
||||
<ContentControl Content="{Binding Result.PreviewPanel.Value}" />
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ namespace Flow.Launcher
|
|||
e.Handled = true;
|
||||
break;
|
||||
case Key.Right:
|
||||
if (_viewModel.SelectedIsFromQueryResults()
|
||||
if (_viewModel.QueryResultsSelected()
|
||||
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length
|
||||
&& !string.IsNullOrEmpty(QueryTextBox.Text))
|
||||
{
|
||||
|
|
@ -336,7 +336,7 @@ namespace Flow.Launcher
|
|||
|
||||
break;
|
||||
case Key.Left:
|
||||
if (!_viewModel.SelectedIsFromQueryResults() && QueryTextBox.CaretIndex == 0)
|
||||
if (!_viewModel.QueryResultsSelected() && QueryTextBox.CaretIndex == 0)
|
||||
{
|
||||
_viewModel.EscCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
|
|
@ -346,7 +346,7 @@ namespace Flow.Launcher
|
|||
case Key.Back:
|
||||
if (specialKeyState.CtrlPressed)
|
||||
{
|
||||
if (_viewModel.SelectedIsFromQueryResults()
|
||||
if (_viewModel.QueryResultsSelected()
|
||||
&& QueryTextBox.Text.Length > 0
|
||||
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@
|
|||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />
|
||||
<Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
|
|
@ -431,12 +432,12 @@
|
|||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="PreviewBorderStyle"
|
||||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="Gray" />
|
||||
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PreviewArea" TargetType="{x:Type Grid}">
|
||||
|
|
@ -446,8 +447,8 @@
|
|||
<MultiDataTrigger.Conditions>
|
||||
<!--
|
||||
<Condition Binding="{Binding ElementName=ResultListBox, Path=Visibility}" Value="Collapsed" />
|
||||
<Condition Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Collapsed" />
|
||||
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />-->
|
||||
<Condition Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Collapsed" />-->
|
||||
<Condition Binding="{Binding ElementName=History, Path=Visibility}" Value="Collapsed" />
|
||||
<Condition Binding="{Binding ElementName=ResultListBox, Path=Items.Count}" Value="0" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ namespace Flow.Launcher.ViewModel
|
|||
private ChannelWriter<ResultsForUpdate> _resultsUpdateChannelWriter;
|
||||
private Task _resultsViewUpdateTask;
|
||||
|
||||
private readonly IReadOnlyList<Result> _emptyResult = new List<Result>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
|
@ -162,7 +164,21 @@ namespace Flow.Launcher.ViewModel
|
|||
switch (args.PropertyName)
|
||||
{
|
||||
case nameof(Results.SelectedItem):
|
||||
UpdatePreview();
|
||||
_selectedItemFromQueryResults = true;
|
||||
PreviewSelectedItem = Results.SelectedItem;
|
||||
_ = UpdatePreviewAsync();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
History.PropertyChanged += (_, args) =>
|
||||
{
|
||||
switch (args.PropertyName)
|
||||
{
|
||||
case nameof(History.SelectedItem):
|
||||
_selectedItemFromQueryResults = false;
|
||||
PreviewSelectedItem = History.SelectedItem;
|
||||
_ = UpdatePreviewAsync();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -265,7 +281,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void LoadHistory()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = History;
|
||||
History.SelectedIndex = _history.Items.Count - 1;
|
||||
|
|
@ -279,7 +295,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
public void ReQuery()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
_ = QueryResultsAsync(isReQuery: true);
|
||||
}
|
||||
|
|
@ -320,7 +336,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void LoadContextMenu()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
||||
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
||||
|
|
@ -350,7 +366,7 @@ namespace Flow.Launcher.ViewModel
|
|||
private void AutocompleteQuery()
|
||||
{
|
||||
var result = SelectedResults.SelectedItem?.Result;
|
||||
if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty.
|
||||
if (result != null && QueryResultsSelected()) // SelectedItem returns null if selection is empty.
|
||||
{
|
||||
var autoCompleteText = result.Title;
|
||||
|
||||
|
|
@ -402,7 +418,7 @@ namespace Flow.Launcher.ViewModel
|
|||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
_userSelectedRecord.Add(result);
|
||||
// origin query is null when user select the context menu item directly of one item from query list
|
||||
|
|
@ -481,7 +497,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (_history.Items.Count > 0
|
||||
&& QueryText == string.Empty
|
||||
&& SelectedIsFromQueryResults())
|
||||
&& QueryResultsSelected())
|
||||
{
|
||||
lastHistoryIndex = 1;
|
||||
ReverseHistory();
|
||||
|
|
@ -501,7 +517,7 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void Esc()
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
if (!QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
|
|
@ -513,7 +529,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public void BackToQueryResults()
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
if (!QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
|
|
@ -644,13 +660,16 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private ResultsViewModel SelectedResults
|
||||
{
|
||||
get { return _selectedResults; }
|
||||
get => _selectedResults;
|
||||
set
|
||||
{
|
||||
var isReturningFromQueryResults = QueryResultsSelected();
|
||||
var isReturningFromContextMenu = ContextMenuSelected();
|
||||
var isReturningFromHistory = HistorySelected();
|
||||
_selectedResults = value;
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
Results.Visibility = Visibility.Visible;
|
||||
ContextMenu.Visibility = Visibility.Collapsed;
|
||||
History.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
|
@ -668,10 +687,27 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
ChangeQueryText(_queryTextBeforeLeaveResults);
|
||||
}
|
||||
|
||||
// If we are returning from history and we have not set select item yet,
|
||||
// we need to clear the preview selected item
|
||||
if (isReturningFromHistory && _selectedItemFromQueryResults.HasValue && (!_selectedItemFromQueryResults.Value))
|
||||
{
|
||||
PreviewSelectedItem = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
if (HistorySelected())
|
||||
{
|
||||
ContextMenu.Visibility = Visibility.Collapsed;
|
||||
History.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContextMenu.Visibility = Visibility.Visible;
|
||||
History.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
_queryTextBeforeLeaveResults = QueryText;
|
||||
|
||||
// Because of Fody's optimization
|
||||
|
|
@ -686,6 +722,16 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
QueryText = string.Empty;
|
||||
}
|
||||
|
||||
if (HistorySelected())
|
||||
{
|
||||
// If we are returning from query results and we have not set select item yet,
|
||||
// we need to clear the preview selected item
|
||||
if (isReturningFromQueryResults && _selectedItemFromQueryResults.HasValue && _selectedItemFromQueryResults.Value)
|
||||
{
|
||||
PreviewSelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_selectedResults.Visibility = Visibility.Visible;
|
||||
|
|
@ -785,6 +831,22 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region Preview
|
||||
|
||||
private static readonly int ResultAreaColumnPreviewShown = 1;
|
||||
private static readonly int ResultAreaColumnPreviewHidden = 3;
|
||||
|
||||
private bool? _selectedItemFromQueryResults;
|
||||
|
||||
private ResultViewModel _previewSelectedItem;
|
||||
public ResultViewModel PreviewSelectedItem
|
||||
{
|
||||
get => _previewSelectedItem;
|
||||
set
|
||||
{
|
||||
_previewSelectedItem = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool InternalPreviewVisible
|
||||
{
|
||||
get
|
||||
|
|
@ -803,18 +865,14 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly int ResultAreaColumnPreviewShown = 1;
|
||||
|
||||
private static readonly int ResultAreaColumnPreviewHidden = 3;
|
||||
|
||||
public int ResultAreaColumn { get; set; } = ResultAreaColumnPreviewShown;
|
||||
|
||||
// This is not a reliable indicator of whether external preview is visible due to the
|
||||
// ability of manually closing/exiting the external preview program which, does not inform flow that
|
||||
// preview is no longer available.
|
||||
public bool ExternalPreviewVisible { get; set; } = false;
|
||||
public bool ExternalPreviewVisible { get; private set; }
|
||||
|
||||
private void ShowPreview()
|
||||
private async Task ShowPreviewAsync()
|
||||
{
|
||||
var useExternalPreview = PluginManager.UseExternalPreview();
|
||||
|
||||
|
|
@ -825,13 +883,15 @@ namespace Flow.Launcher.ViewModel
|
|||
// Internal preview may still be on when user switches to external
|
||||
if (InternalPreviewVisible)
|
||||
HideInternalPreview();
|
||||
OpenExternalPreview(path);
|
||||
|
||||
_ = OpenExternalPreviewAsync(path);
|
||||
break;
|
||||
|
||||
case true
|
||||
when !CanExternalPreviewSelectedResult(out var _):
|
||||
if (ExternalPreviewVisible)
|
||||
CloseExternalPreview();
|
||||
await CloseExternalPreviewAsync();
|
||||
|
||||
ShowInternalPreview();
|
||||
break;
|
||||
|
||||
|
|
@ -844,7 +904,7 @@ namespace Flow.Launcher.ViewModel
|
|||
private void HidePreview()
|
||||
{
|
||||
if (PluginManager.UseExternalPreview())
|
||||
CloseExternalPreview();
|
||||
_ = CloseExternalPreviewAsync();
|
||||
|
||||
if (InternalPreviewVisible)
|
||||
HideInternalPreview();
|
||||
|
|
@ -859,31 +919,31 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
ShowPreview();
|
||||
_ = ShowPreviewAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenExternalPreview(string path, bool sendFailToast = true)
|
||||
private async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true)
|
||||
{
|
||||
_ = PluginManager.OpenExternalPreviewAsync(path, sendFailToast).ConfigureAwait(false);
|
||||
await PluginManager.OpenExternalPreviewAsync(path, sendFailToast).ConfigureAwait(false);
|
||||
ExternalPreviewVisible = true;
|
||||
}
|
||||
|
||||
private void CloseExternalPreview()
|
||||
private async Task CloseExternalPreviewAsync()
|
||||
{
|
||||
_ = PluginManager.CloseExternalPreviewAsync().ConfigureAwait(false);
|
||||
await PluginManager.CloseExternalPreviewAsync().ConfigureAwait(false);
|
||||
ExternalPreviewVisible = false;
|
||||
}
|
||||
|
||||
private static void SwitchExternalPreview(string path, bool sendFailToast = true)
|
||||
private static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true)
|
||||
{
|
||||
_ = PluginManager.SwitchExternalPreviewAsync(path,sendFailToast).ConfigureAwait(false);
|
||||
await PluginManager.SwitchExternalPreviewAsync(path, sendFailToast).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ShowInternalPreview()
|
||||
{
|
||||
ResultAreaColumn = ResultAreaColumnPreviewShown;
|
||||
Results.SelectedItem?.LoadPreviewImage();
|
||||
PreviewSelectedItem?.LoadPreviewImage();
|
||||
}
|
||||
|
||||
private void HideInternalPreview()
|
||||
|
|
@ -897,20 +957,18 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
case true
|
||||
when PluginManager.AllowAlwaysPreview() && CanExternalPreviewSelectedResult(out var path):
|
||||
OpenExternalPreview(path);
|
||||
_ = OpenExternalPreviewAsync(path);
|
||||
break;
|
||||
|
||||
case true:
|
||||
ShowInternalPreview();
|
||||
break;
|
||||
|
||||
case false:
|
||||
HidePreview();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePreview()
|
||||
private async Task UpdatePreviewAsync()
|
||||
{
|
||||
switch (PluginManager.UseExternalPreview())
|
||||
{
|
||||
|
|
@ -918,44 +976,48 @@ namespace Flow.Launcher.ViewModel
|
|||
when CanExternalPreviewSelectedResult(out var path):
|
||||
if (ExternalPreviewVisible)
|
||||
{
|
||||
SwitchExternalPreview(path, false);
|
||||
_ = SwitchExternalPreviewAsync(path, false);
|
||||
}
|
||||
else if (InternalPreviewVisible)
|
||||
{
|
||||
HideInternalPreview();
|
||||
OpenExternalPreview(path);
|
||||
_ = OpenExternalPreviewAsync(path);
|
||||
}
|
||||
break;
|
||||
|
||||
case true
|
||||
when !CanExternalPreviewSelectedResult(out var _):
|
||||
if (ExternalPreviewVisible)
|
||||
{
|
||||
CloseExternalPreview();
|
||||
await CloseExternalPreviewAsync();
|
||||
ShowInternalPreview();
|
||||
}
|
||||
break;
|
||||
|
||||
case false
|
||||
when InternalPreviewVisible:
|
||||
Results.SelectedItem?.LoadPreviewImage();
|
||||
PreviewSelectedItem?.LoadPreviewImage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanExternalPreviewSelectedResult(out string path)
|
||||
{
|
||||
path = Results.SelectedItem?.Result?.Preview.FilePath;
|
||||
path = QueryResultsPreviewed() ? Results.SelectedItem?.Result?.Preview.FilePath : string.Empty;
|
||||
return !string.IsNullOrEmpty(path);
|
||||
}
|
||||
|
||||
private bool QueryResultsPreviewed()
|
||||
{
|
||||
var previewed = PreviewSelectedItem == Results.SelectedItem;
|
||||
return previewed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query
|
||||
|
||||
public void Query(bool isReQuery = false)
|
||||
private void Query(bool isReQuery = false)
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
_ = QueryResultsAsync(isReQuery);
|
||||
}
|
||||
|
|
@ -1027,6 +1089,11 @@ namespace Flow.Launcher.ViewModel
|
|||
Title = string.Format(title, h.Query),
|
||||
SubTitle = string.Format(time, h.ExecutedDateTime),
|
||||
IcoPath = "Images\\history.png",
|
||||
Preview = new Result.PreviewInfo
|
||||
{
|
||||
PreviewImagePath = Constant.HistoryIcon,
|
||||
Description = string.Format(time, h.ExecutedDateTime)
|
||||
},
|
||||
OriginQuery = new Query { RawQuery = h.Query },
|
||||
Action = _ =>
|
||||
{
|
||||
|
|
@ -1053,8 +1120,6 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private readonly IReadOnlyList<Result> _emptyResult = new List<Result>();
|
||||
|
||||
private async Task QueryResultsAsync(bool isReQuery = false, bool reSelect = true)
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
|
|
@ -1318,7 +1383,7 @@ namespace Flow.Launcher.ViewModel
|
|||
return menu;
|
||||
}
|
||||
|
||||
internal bool SelectedIsFromQueryResults()
|
||||
internal bool QueryResultsSelected()
|
||||
{
|
||||
var selected = SelectedResults == Results;
|
||||
return selected;
|
||||
|
|
@ -1352,6 +1417,18 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Flow Launcher should ignore any hotkeys
|
||||
/// </summary>
|
||||
public bool ShouldIgnoreHotkeys()
|
||||
{
|
||||
return Settings.IgnoreHotkeysOnFullscreen && Win32Helper.IsForegroundWindowFullscreen() || GameModeStatus;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Show()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
|
|
@ -1388,10 +1465,10 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
if (ExternalPreviewVisible)
|
||||
{
|
||||
CloseExternalPreview();
|
||||
await CloseExternalPreviewAsync();
|
||||
}
|
||||
|
||||
if (!SelectedIsFromQueryResults())
|
||||
if (!QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
|
|
@ -1401,10 +1478,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
await Task.Delay(1); // Wait for one frame to ensure UI reflects changes
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Application.Current.MainWindow.UpdateLayout(); // Force UI update
|
||||
});
|
||||
Application.Current.Dispatcher.Invoke(Application.Current.MainWindow.UpdateLayout); // Force UI update
|
||||
}
|
||||
|
||||
switch (Settings.LastQueryMode)
|
||||
|
|
@ -1463,17 +1537,8 @@ namespace Flow.Launcher.ViewModel
|
|||
#pragma warning restore VSTHRD100 // Avoid async void methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Flow Launcher should ignore any hotkeys
|
||||
/// Save history, user selected records and top most records
|
||||
/// </summary>
|
||||
public bool ShouldIgnoreHotkeys()
|
||||
{
|
||||
return Settings.IgnoreHotkeysOnFullscreen && Win32Helper.IsForegroundWindowFullscreen() || GameModeStatus;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_historyItemsStorage.Save();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Text;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
|
@ -6,25 +9,20 @@ using Flow.Launcher.Infrastructure.Image;
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.IO;
|
||||
using System.Drawing.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public class ResultViewModel : BaseModel
|
||||
{
|
||||
private static PrivateFontCollection fontCollection = new();
|
||||
private static Dictionary<string, string> fonts = new();
|
||||
private static readonly PrivateFontCollection FontCollection = new();
|
||||
private static readonly Dictionary<string, string> Fonts = new();
|
||||
|
||||
public ResultViewModel(Result result, Settings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (result == null) return;
|
||||
|
||||
Result = result;
|
||||
|
||||
if (Result.Glyph is { FontFamily: not null } glyph)
|
||||
|
|
@ -39,20 +37,20 @@ namespace Flow.Launcher.ViewModel
|
|||
fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath);
|
||||
}
|
||||
|
||||
if (fonts.ContainsKey(fontFamilyPath))
|
||||
if (Fonts.TryGetValue(fontFamilyPath, out var value))
|
||||
{
|
||||
Glyph = glyph with
|
||||
{
|
||||
FontFamily = fonts[fontFamilyPath]
|
||||
FontFamily = value
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
fontCollection.AddFontFile(fontFamilyPath);
|
||||
fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}";
|
||||
FontCollection.AddFontFile(fontFamilyPath);
|
||||
Fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{FontCollection.Families[^1].Name}";
|
||||
Glyph = glyph with
|
||||
{
|
||||
FontFamily = fonts[fontFamilyPath]
|
||||
FontFamily = Fonts[fontFamilyPath]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +59,6 @@ namespace Flow.Launcher.ViewModel
|
|||
Glyph = glyph;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Settings Settings { get; }
|
||||
|
|
@ -95,14 +92,10 @@ namespace Flow.Launcher.ViewModel
|
|||
get
|
||||
{
|
||||
if (PreviewImageAvailable)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to icon
|
||||
return ShowIcon;
|
||||
}
|
||||
|
||||
// Fall back to icon
|
||||
return ShowIcon;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,9 +104,8 @@ namespace Flow.Launcher.ViewModel
|
|||
get
|
||||
{
|
||||
if (Result.RoundedIcon)
|
||||
{
|
||||
return IconXY / 2;
|
||||
}
|
||||
|
||||
return IconXY;
|
||||
}
|
||||
|
||||
|
|
@ -148,31 +140,40 @@ namespace Flow.Launcher.ViewModel
|
|||
? Result.SubTitle
|
||||
: Result.SubTitleToolTip;
|
||||
|
||||
private volatile bool ImageLoaded;
|
||||
private volatile bool PreviewImageLoaded;
|
||||
private volatile bool _imageLoaded;
|
||||
private volatile bool _previewImageLoaded;
|
||||
|
||||
private ImageSource image = ImageLoader.LoadingImage;
|
||||
private ImageSource previewImage = ImageLoader.LoadingImage;
|
||||
private ImageSource _image = ImageLoader.LoadingImage;
|
||||
private ImageSource _previewImage = ImageLoader.LoadingImage;
|
||||
|
||||
public ImageSource Image
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ImageLoaded)
|
||||
if (!_imageLoaded)
|
||||
{
|
||||
ImageLoaded = true;
|
||||
_imageLoaded = true;
|
||||
_ = LoadImageAsync();
|
||||
}
|
||||
|
||||
return image;
|
||||
return _image;
|
||||
}
|
||||
private set => image = value;
|
||||
private set => _image = value;
|
||||
}
|
||||
|
||||
public ImageSource PreviewImage
|
||||
{
|
||||
get => previewImage;
|
||||
private set => previewImage = value;
|
||||
get
|
||||
{
|
||||
if (!_previewImageLoaded)
|
||||
{
|
||||
_previewImageLoaded = true;
|
||||
_ = LoadPreviewImageAsync();
|
||||
}
|
||||
|
||||
return _previewImage;
|
||||
}
|
||||
private set => _previewImage = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -188,8 +189,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
try
|
||||
{
|
||||
var image = icon();
|
||||
return image;
|
||||
return icon();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -208,7 +208,7 @@ namespace Flow.Launcher.ViewModel
|
|||
var iconDelegate = Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, false, out ImageSource img))
|
||||
{
|
||||
image = img;
|
||||
_image = img;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -223,7 +223,7 @@ namespace Flow.Launcher.ViewModel
|
|||
var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, true, out ImageSource img))
|
||||
{
|
||||
previewImage = img;
|
||||
_previewImage = img;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -234,13 +234,10 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public void LoadPreviewImage()
|
||||
{
|
||||
if (ShowDefaultPreview == Visibility.Visible)
|
||||
if (ShowDefaultPreview == Visibility.Visible && !_previewImageLoaded && ShowPreviewImage == Visibility.Visible)
|
||||
{
|
||||
if (!PreviewImageLoaded && ShowPreviewImage == Visibility.Visible)
|
||||
{
|
||||
PreviewImageLoaded = true;
|
||||
_ = LoadPreviewImageAsync();
|
||||
}
|
||||
_previewImageLoaded = true;
|
||||
_ = LoadPreviewImageAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
|
@ -10,6 +8,8 @@ using System.Windows.Controls;
|
|||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -28,6 +28,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Results = new ResultCollection();
|
||||
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
|
||||
}
|
||||
|
||||
public ResultsViewModel(Settings settings) : this()
|
||||
{
|
||||
_settings = settings;
|
||||
|
|
@ -219,7 +220,6 @@ namespace Flow.Launcher.ViewModel
|
|||
if (newRawResults.Count == 0)
|
||||
return Results;
|
||||
|
||||
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
|
||||
|
||||
return Results.Where(r => r.Result.PluginID != resultId)
|
||||
|
|
@ -241,6 +241,7 @@ namespace Flow.Launcher.ViewModel
|
|||
#endregion
|
||||
|
||||
#region FormattedText Dependency Property
|
||||
|
||||
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
|
||||
"FormattedText",
|
||||
typeof(Inline),
|
||||
|
|
@ -259,8 +260,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var textBlock = d as TextBlock;
|
||||
if (textBlock == null) return;
|
||||
if (d is not TextBlock textBlock) return;
|
||||
|
||||
var inline = (Inline)e.NewValue;
|
||||
|
||||
|
|
@ -269,6 +269,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
textBlock.Inlines.Add(inline);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
|
||||
|
|
@ -279,7 +280,6 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
|
|
@ -297,6 +297,7 @@ namespace Flow.Launcher.ViewModel
|
|||
// wpf use DirectX / double buffered already, so just reset all won't cause ui flickering
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
private void AddAll(List<ResultViewModel> Items)
|
||||
{
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
|
|
@ -308,6 +309,7 @@ namespace Flow.Launcher.ViewModel
|
|||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAll(int Capacity = 512)
|
||||
{
|
||||
Clear();
|
||||
|
|
|
|||
Loading…
Reference in a new issue