first port to avalonia

This commit is contained in:
Hongtao Zhang 2024-01-18 16:45:49 -06:00
parent 05a0455d91
commit d788de8b90
21 changed files with 1434 additions and 1517 deletions

View file

@ -134,6 +134,7 @@ namespace Flow.Launcher.Core.Resource
private void RemoveOldLanguageFiles()
{
return;
var dicts = Application.Current.Resources.MergedDictionaries;
foreach (var r in _oldResources)
{
@ -143,6 +144,7 @@ namespace Flow.Launcher.Core.Resource
private void LoadLanguage(Language language)
{
return;
var dicts = Application.Current.Resources.MergedDictionaries;
var filename = $"{language.LanguageCode}{Extension}";
var files = _languageDirectories
@ -171,6 +173,7 @@ namespace Flow.Launcher.Core.Resource
public string GetTranslation(string key)
{
return "";
var translation = Application.Current.TryFindResource(key);
if (translation is string)
{

View file

@ -38,6 +38,8 @@ namespace Flow.Launcher.Core.Resource
_themeDirectories.Add(UserDirectoryPath);
MakeSureThemeDirectoriesExist();
return;
var dicts = Application.Current.Resources.MergedDictionaries;
_oldResource = dicts.First(d =>
{
@ -72,6 +74,7 @@ namespace Flow.Launcher.Core.Resource
public bool ChangeTheme(string theme)
{
return false;
const string defaultTheme = Constant.DefaultTheme;
string path = GetThemePath(theme);
@ -125,6 +128,8 @@ namespace Flow.Launcher.Core.Resource
private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate)
{
return;
var dicts = Application.Current.Resources.MergedDictionaries;
dicts.Remove(_oldResource);

View file

@ -193,8 +193,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool AutoUpdates { get; set; } = false;
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
public int WindowLeft { get; set; }
public int WindowTop { get; set; }
/// <summary>
/// Custom left position on selected monitor

11
Flow.Launcher/App.axaml Normal file
View file

@ -0,0 +1,11 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Flow.Launcher.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

213
Flow.Launcher/App.axaml.cs Normal file
View file

@ -0,0 +1,213 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.ExternalPlugins.Environments;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.ViewModel;
using Microsoft.VisualStudio.Threading;
using PropertyChanged;
using Application = Avalonia.Application;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher
{
[DoNotNotify]
public partial class App : Application, IDisposable, ISingleInstanceApp
{
public static PublicAPIInstance API { get; private set; }
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
private static bool _disposed;
private Settings _settings;
private MainViewModel _mainVM;
private SettingWindowViewModel _settingsVM;
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo);
private readonly Portable _portable = new Portable();
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet();
private StringMatcher _stringMatcher;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
if (!SingleInstance<App>.InitializeAsFirstInstance(Unique))
{
return;
}
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
new JoinableTaskFactory(new JoinableTaskContext()).Run(() => Stopwatch.NormalAsync(
"|App.OnStartup|Startup cost",
async () =>
{
_portable.PreStartCleanUpAfterPortabilityUpdate();
Log.Info(
"|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
var imageLoadertask = ImageLoader.InitializeAsync();
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
Http.API = API;
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(API);
await imageLoadertask;
var window = new MainWindow(_settings, _mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
throw new NotSupportedException("desktop is not IClassicDesktopStyleApplicationLifetime");
desktop.MainWindow = window;
desktop.MainWindow.Title = Constant.FlowLauncher;
// desktop.Exit += (s, e) => Dispose();
HotKeyMapper.Initialize(_mainVM);
// todo temp fix for instance code logic
// load plugin before change language, because plugin language also needs be changed
InternationalizationManager.Instance.Settings = _settings;
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
// main windows needs initialized before theme change because of blur settings
ThemeManager.Instance.Settings = _settings;
ThemeManager.Instance.ChangeTheme(_settings.Theme);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
RegisterExitEvents();
AutoStartup();
AutoUpdates();
API.SaveAppAllSettings();
Log.Info(
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
}));
base.OnFrameworkInitializationCompleted();
}
private void AutoStartup()
{
// we try to enable auto-startup on first launch, or reenable if it was removed
// but the user still has the setting set
if (_settings.StartFlowLauncherOnSystemStartup && !Helper.AutoStartup.IsEnabled)
{
try
{
Helper.AutoStartup.Enable();
}
catch (Exception e)
{
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false;
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
e.Message);
}
}
}
//[Conditional("RELEASE")]
private void AutoUpdates()
{
_ = Task.Run(async () =>
{
if (_settings.AutoUpdates)
{
// check update every 5 hours
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await _updater.UpdateAppAsync(API);
while (await timer.WaitForNextTickAsync())
// check updates on startup
await _updater.UpdateAppAsync(API);
}
});
}
private void RegisterExitEvents()
{
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private static void RegisterAppDomainExceptions()
{
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
}
public void Dispose()
{
// if sessionending is called, exit proverbially be called when log off / shutdown
// but if sessionending is not called, exit won't be called when log off / shutdown
if (!_disposed)
{
API.SaveAppAllSettings();
_disposed = true;
}
}
public void OnSecondAppStarted()
{
_mainVM.Show();
}
}
}

View file

@ -1,32 +0,0 @@
<Application
x:Class="Flow.Launcher.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
ShutdownMode="OnMainWindowClose"
Startup="OnStartupAsync">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources>
<ui:ThemeResources.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Light.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Dark.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ui:ThemeResources.ThemeDictionaries>
</ui:ThemeResources>
<ui:XamlControlsResources />
<ResourceDictionary Source="pack://application:,,,/Resources/CustomControlTemplate.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/Win11System.xaml" />
<ResourceDictionary Source="pack://application:,,,/Languages/en.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View file

@ -1,194 +0,0 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.ExternalPlugins.Environments;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.ViewModel;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher
{
public partial class App : IDisposable, ISingleInstanceApp
{
public static PublicAPIInstance API { get; private set; }
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
private static bool _disposed;
private Settings _settings;
private MainViewModel _mainVM;
private SettingWindowViewModel _settingsVM;
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo);
private readonly Portable _portable = new Portable();
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet();
private StringMatcher _stringMatcher;
[STAThread]
public static void Main()
{
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
{
using (var application = new App())
{
application.InitializeComponent();
application.Run();
}
}
}
private async void OnStartupAsync(object sender, StartupEventArgs e)
{
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
{
_portable.PreStartCleanUpAfterPortabilityUpdate();
Log.Info(
"|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
var imageLoadertask = ImageLoader.InitializeAsync();
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
Http.API = API;
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(API);
await imageLoadertask;
var window = new MainWindow(_settings, _mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
Current.MainWindow = window;
Current.MainWindow.Title = Constant.FlowLauncher;
HotKeyMapper.Initialize(_mainVM);
// todo temp fix for instance code logic
// load plugin before change language, because plugin language also needs be changed
InternationalizationManager.Instance.Settings = _settings;
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
// main windows needs initialized before theme change because of blur settings
ThemeManager.Instance.Settings = _settings;
ThemeManager.Instance.ChangeTheme(_settings.Theme);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
RegisterExitEvents();
AutoStartup();
AutoUpdates();
API.SaveAppAllSettings();
Log.Info(
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
});
}
private void AutoStartup()
{
// we try to enable auto-startup on first launch, or reenable if it was removed
// but the user still has the setting set
if (_settings.StartFlowLauncherOnSystemStartup && !Helper.AutoStartup.IsEnabled)
{
try
{
Helper.AutoStartup.Enable();
}
catch (Exception e)
{
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false;
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
e.Message);
}
}
}
//[Conditional("RELEASE")]
private void AutoUpdates()
{
_ = Task.Run(async () =>
{
if (_settings.AutoUpdates)
{
// check update every 5 hours
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await _updater.UpdateAppAsync(API);
while (await timer.WaitForNextTickAsync())
// check updates on startup
await _updater.UpdateAppAsync(API);
}
});
}
private void RegisterExitEvents()
{
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
Current.Exit += (s, e) => Dispose();
Current.SessionEnding += (s, e) => Dispose();
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private void RegisterDispatcherUnhandledException()
{
DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException;
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private static void RegisterAppDomainExceptions()
{
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
}
public void Dispose()
{
// if sessionending is called, exit proverbially be called when log off / shutdown
// but if sessionending is not called, exit won't be called when log off / shutdown
if (!_disposed)
{
API.SaveAppAllSettings();
_disposed = true;
}
}
public void OnSecondAppStarted()
{
_mainVM.Show();
}
}
}

View file

@ -4,6 +4,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWPF>true</UseWPF>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<UseWindowsForms>true</UseWindowsForms>
<StartupObject>Flow.Launcher.App</StartupObject>
<ApplicationIcon>Resources\app.ico</ApplicationIcon>
@ -12,6 +13,7 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -23,7 +25,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>true</UseVSHostingProcess>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
@ -42,10 +43,6 @@
<ApplicationDefinition Remove="App.xaml" />
</ItemGroup>
<ItemGroup>
<Page Include="App.xaml" />
</ItemGroup>
<ItemGroup>
<Page Remove="Themes\ThemeBuilder\Template.xaml" />
</ItemGroup>
@ -83,7 +80,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.7" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.7" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.7" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.7" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.7" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.7"/>
<PackageReference Include="Fody" Version="6.5.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -8,7 +8,8 @@ using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Avalonia;
using Avalonia.Threading;
// http://blogs.microsoft.co.il/arik/2010/05/28/wpf-single-instance-application/
// modified to allow single instace restart
@ -119,8 +120,10 @@ namespace Flow.Launcher.Helper
DWMWINDOWMAXIMIZEDCHANGE = 0x0321,
#region Windows 7
DWMSENDICONICTHUMBNAIL = 0x0323,
DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326,
#endregion
USER = 0x0400,
@ -140,7 +143,8 @@ namespace Flow.Launcher.Helper
public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled);
[DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine,
out int numArgs);
[DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
@ -159,6 +163,7 @@ namespace Flow.Launcher.Helper
{
throw new Win32Exception();
}
var result = new string[numArgs];
for (int i = 0; i < numArgs; i++)
@ -171,19 +176,17 @@ namespace Flow.Launcher.Helper
}
finally
{
IntPtr p = _LocalFree(argv);
// Otherwise LocalFree failed.
// Assert.AreEqual(IntPtr.Zero, p);
}
}
}
}
public interface ISingleInstanceApp
{
void OnSecondAppStarted();
}
public interface ISingleInstanceApp
{
void OnSecondAppStarted();
}
/// <summary>
/// This class checks to make sure that only one instance of
@ -196,9 +199,9 @@ namespace Flow.Launcher.Helper
/// running as Administrator, can activate it with command line arguments.
/// For most apps, this will not be much of an issue.
/// </remarks>
public static class SingleInstance<TApplication>
where TApplication: Application , ISingleInstanceApp
public static class SingleInstance<TApplication>
where TApplication : Avalonia.Application, ISingleInstanceApp
{
#region Private Fields
@ -230,7 +233,7 @@ namespace Flow.Launcher.Helper
/// If not, activates the first instance.
/// </summary>
/// <returns>True if this is the first instance of the application.</returns>
public static bool InitializeAsFirstInstance( string uniqueName )
public static bool InitializeAsFirstInstance(string uniqueName)
{
// Build unique application Id and the IPC channel name.
string applicationIdentifier = uniqueName + Environment.UserName;
@ -268,7 +271,7 @@ namespace Flow.Launcher.Helper
/// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
/// </summary>
/// <returns>List of command line arg strings.</returns>
private static IList<string> GetCommandLineArgs( string uniqueApplicationName )
private static IList<string> GetCommandLineArgs(string uniqueApplicationName)
{
string[] args = null;
@ -279,7 +282,6 @@ namespace Flow.Launcher.Helper
}
catch (NotSupportedException)
{
// The application was clickonce deployed
// Clickonce deployed apps cannot recieve traditional commandline arguments
// As a workaround commandline arguments can be written to a shared location before
@ -323,15 +325,17 @@ namespace Flow.Launcher.Helper
{
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In))
{
while(true)
while (true)
{
// Wait for connection to the pipe
await pipeServer.WaitForConnectionAsync();
if (Application.Current != null)
{
// Do an asynchronous call to ActivateFirstInstance function
Application.Current.Dispatcher.Invoke(ActivateFirstInstance);
Dispatcher.UIThread.Invoke(ActivateFirstInstance);
}
// Disconect client
pipeServer.Disconnect();
}
@ -378,7 +382,7 @@ namespace Flow.Launcher.Helper
return;
}
((TApplication)Application.Current).OnSecondAppStarted();
((TApplication)Application.Current)!.OnSecondAppStarted();
}
#endregion

View file

@ -0,0 +1,454 @@
<Window
x:Class="Flow.Launcher.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Name="FlowMainWindow"
Title="Flow Launcher"
x:DataType="vm:MainViewModel"
MinWidth="{Binding MainWindowWidth, Mode=OneWay}"
MaxWidth="{Binding MainWindowWidth, Mode=OneWay}"
Background="Transparent"
Deactivated="OnDeactivated"
Initialized="OnInitialized"
Loaded="OnLoaded"
Opacity="{Binding MainWindowOpacity, Mode=OneWay}"
ShowInTaskbar="False"
SizeToContent="Height"
Topmost="True"
IsVisible="{Binding MainWindowVisibility, Mode=TwoWay}"
WindowStartupLocation="CenterScreen"
ExtendClientAreaChromeHints="NoChrome">
<Window.Resources>
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
<converters:BorderClipConverter x:Key="BorderClipConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:SplitterConverter x:Key="SplitterConverter" />
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter" />
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter" />
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter" />
</Window.Resources>
<!-- <Window.InputBindings> -->
<!-- <KeyBinding Key="Escape" Command="{Binding EscCommand}" /> -->
<!-- <KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}" /> -->
<!-- <KeyBinding Key="Tab" Command="{Binding AutocompleteQueryCommand}" /> -->
<!-- <KeyBinding -->
<!-- Key="Tab" -->
<!-- Command="{Binding AutocompleteQueryCommand}" -->
<!-- Modifiers="Shift" /> -->
<!-- <KeyBinding -->
<!-- Key="I" -->
<!-- Command="{Binding OpenSettingCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="N" -->
<!-- Command="{Binding SelectNextItemCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="J" -->
<!-- Command="{Binding SelectNextItemCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="D" -->
<!-- Command="{Binding SelectNextPageCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="P" -->
<!-- Command="{Binding SelectPrevItemCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="K" -->
<!-- Command="{Binding SelectPrevItemCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="U" -->
<!-- Command="{Binding SelectPrevPageCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="Home" -->
<!-- Command="{Binding SelectFirstResultCommand}" -->
<!-- Modifiers="Alt" /> -->
<!-- <KeyBinding -->
<!-- Key="O" -->
<!-- Command="{Binding LoadContextMenuCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="R" -->
<!-- Command="{Binding ReQueryCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="H" -->
<!-- Command="{Binding LoadHistoryCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="OemCloseBrackets" -->
<!-- Command="{Binding IncreaseWidthCommand}" -->
<!-- Modifiers="Control" /> -->
<!-- <KeyBinding -->
<!-- Key="OemOpenBrackets" -->
<!-- Command="{Binding DecreaseWidthCommand}" -->
<!-- Modifiers="Control" /> -->
<!-- <KeyBinding -->
<!-- Key="OemPlus" -->
<!-- Command="{Binding IncreaseMaxResultCommand}" -->
<!-- Modifiers="Control" /> -->
<!-- <KeyBinding -->
<!-- Key="OemMinus" -->
<!-- Command="{Binding DecreaseMaxResultCommand}" -->
<!-- Modifiers="Control" /> -->
<!-- <KeyBinding -->
<!-- Key="H" -->
<!-- Command="{Binding LoadHistoryCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="Enter" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- Modifiers="Ctrl+Shift" /> -->
<!-- <KeyBinding -->
<!-- Key="Enter" -->
<!-- Command="{Binding LoadContextMenuCommand}" -->
<!-- Modifiers="Shift" /> -->
<!-- <KeyBinding Key="Enter" Command="{Binding OpenResultCommand}" /> -->
<!-- <KeyBinding -->
<!-- Key="Enter" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="Enter" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- Modifiers="Alt" /> -->
<!-- <KeyBinding -->
<!-- Key="D1" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="0" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D2" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="1" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D3" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="2" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D4" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="3" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D5" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="4" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D6" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="5" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D7" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="6" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D8" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="7" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D9" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="8" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="D0" -->
<!-- Command="{Binding OpenResultCommand}" -->
<!-- CommandParameter="9" -->
<!-- Modifiers="{Binding OpenResultCommandModifiers}" /> -->
<!-- <KeyBinding -->
<!-- Key="F12" -->
<!-- Command="{Binding ToggleGameModeCommand}" -->
<!-- Modifiers="Ctrl" /> -->
<!-- <KeyBinding -->
<!-- Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" -->
<!-- Command="{Binding TogglePreviewCommand}" -->
<!-- Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> -->
<!-- </Window.InputBindings> -->
<Grid>
<Border PointerPressed="OnMouseDown">
<StackPanel Orientation="Vertical">
<Grid>
<Border>
<Grid>
<TextBox
x:Name="QueryTextSuggestionBox"
IsEnabled="False">
<TextBox.Text>
<!-- <MultiBinding Converter="{StaticResource QuerySuggestionBoxConverter}"> -->
<!-- <Binding ElementName="QueryTextBox" Mode="OneTime" /> -->
<!-- <Binding ElementName="QueryTextBox" Path="Text" /> -->
<!-- </MultiBinding> -->
</TextBox.Text>
</TextBox>
<TextBox
x:Name="QueryTextBox"
Height="40"
Text="{Binding QueryText, Mode=TwoWay}">
<!-- <TextBox.CommandBindings> -->
<!-- <CommandBinding Command="ApplicationCommands.Copy" Executed="OnCopy" /> -->
<!-- <CommandBinding Command="ApplicationCommands.Paste" Executed="OnPaste" /> -->
<!-- </TextBox.CommandBindings> -->
<TextBox.ContextMenu>
<ContextMenu MinWidth="160">
<!-- <MenuItem Command="ApplicationCommands.Cut" Header="{DynamicResource cut}"> -->
<!-- <MenuItem.Icon> -->
<!-- <ui:FontIcon Glyph="&#xe8c6;" /> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<!-- <MenuItem Command="ApplicationCommands.Copy" Header="{DynamicResource copy}"> -->
<!-- <MenuItem.Icon> -->
<!-- <ui:FontIcon Glyph="&#xe8c8;" /> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<!-- <MenuItem Command="ApplicationCommands.Paste" Header="{DynamicResource paste}"> -->
<!-- <MenuItem.Icon> -->
<!-- <ui:FontIcon Glyph="&#xe77f;" /> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<Separator
Margin="0"
Padding="0,4,0,4"
Background="{DynamicResource ContextSeparator}" />
<MenuItem Click="OnContextMenusForSettingsClick"
Header="{DynamicResource flowlauncher_settings}">
<MenuItem.Icon>
<!-- <FontIcon Glyph="&#xe713;" /> -->
</MenuItem.Icon>
</MenuItem>
<!-- <MenuItem Command="{Binding EscCommand}" Header="{DynamicResource closeWindow}"> -->
<!-- <MenuItem.Icon> -->
<!-- <ui:FontIcon Glyph="&#xe711;" /> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Border>
<StackPanel
x:Name="ClockPanel"
IsHitTestVisible="False"
HorizontalAlignment="Right">
<TextBlock
x:Name="ClockBox"
Text="{Binding ClockText}"
IsVisible="{Binding Settings.UseClock}" />
<TextBlock
x:Name="DateBox"
Text="{Binding DateText}"
IsVisible="{Binding Settings.UseDate}" />
</StackPanel>
<Border>
<Grid>
<Image
x:Name="PluginActivationIcon"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Source="{Binding PluginIconPath}"
Stretch="Uniform" />
<Canvas>
<Path
Name="SearchIcon"
Margin="0"
Data="{DynamicResource SearchIconImg}"
Stretch="Fill"
IsVisible="{Binding SearchIconVisibility}" />
</Canvas>
</Grid>
</Border>
<!-- <Line -->
<!-- x:Name="ProgressBar" -->
<!-- Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}, Path=ActualWidth}" -->
<!-- Height="2" -->
<!-- Margin="12,0,12,0" -->
<!-- HorizontalAlignment="Center" -->
<!-- VerticalAlignment="Bottom" -->
<!-- StrokeThickness="2" -->
<!-- IsVisible="{Binding ProgressBarVisibility, Mode=TwoWay}" -->
<!-- X1="-100" -->
<!-- X2="0" -->
<!-- Y1="0" -->
<!-- Y2="0" /> -->
</Grid>
<Grid ClipToBounds="True">
<ContentControl>
<!-- <ContentControl.Style> -->
<!-- <Style TargetType="ContentControl"> -->
<!-- <Setter Property="Visibility" Value="Collapsed" /> -->
<!-- <Style.Triggers> -->
<!-- <DataTrigger Binding="{Binding ElementName=ResultListBox, Path=Visibility}" -->
<!-- Value="Visible"> -->
<!-- <Setter Property="Visibility" Value="Visible" /> -->
<!-- </DataTrigger> -->
<!-- <DataTrigger Binding="{Binding ElementName=ContextMenu, Path=Visibility}" -->
<!-- Value="Visible"> -->
<!-- <Setter Property="Visibility" Value="Visible" /> -->
<!-- </DataTrigger> -->
<!-- <DataTrigger Binding="{Binding ElementName=History, Path=Visibility}" -->
<!-- Value="Visible"> -->
<!-- <Setter Property="Visibility" Value="Visible" /> -->
<!-- </DataTrigger> -->
<!-- </Style.Triggers> -->
<!-- </Style> -->
<!-- </ContentControl.Style> -->
<Rectangle
x:Name="MiddleSeparator"
HorizontalAlignment="Stretch" />
</ContentControl>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="0.85*" MinWidth="244" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="ResultArea"
Grid.Column="0"
Grid.ColumnSpan="{Binding ResultAreaColumn}">
<Border DataContext="{Binding ResultsVM}">
<!-- <Border.Clip> -->
<!-- <MultiBinding Converter="{StaticResource BorderClipConverter}"> -->
<!-- <Binding Path="Width" RelativeSource="{RelativeSource Self}" /> -->
<!-- <Binding Path="Height" RelativeSource="{RelativeSource Self}" /> -->
<!-- <Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" /> -->
<!-- </MultiBinding> -->
<!-- </Border.Clip> -->
<flowlauncher:ResultListBox x:Name="ResultListBox" />
</Border>
<!-- <Border DataContext="{Binding ContextMenu}"> -->
<!-- <Border.Clip> -->
<!-- ~1~ <MultiBinding Converter="{StaticResource BorderClipConverter}"> @1@ -->
<!-- ~1~ <Binding Path="Width" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ <Binding Path="Height" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ <Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ </MultiBinding> @1@ -->
<!-- </Border.Clip> -->
<!-- <flowlauncher:ResultListBox -->
<!-- x:Name="ContextMenu" /> -->
<!-- </Border> -->
<!-- <Border DataContext="{Binding History}"> -->
<!-- <Border.Clip> -->
<!-- ~1~ <MultiBinding Converter="{StaticResource BorderClipConverter}"> @1@ -->
<!-- ~1~ <Binding Path="Width" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ <Binding Path="Height" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ <Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" /> @1@ -->
<!-- ~1~ </MultiBinding> @1@ -->
<!-- </Border.Clip> -->
<!-- <flowlauncher:ResultListBox -->
<!-- x:Name="History" /> -->
<!-- </Border> -->
</StackPanel>
<!-- <GridSplitter -->
<!-- Grid.Column="1" -->
<!-- HorizontalAlignment="Center" -->
<!-- VerticalAlignment="Stretch" -->
<!-- Background="Transparent" -->
<!-- ShowsPreview="True" /> -->
<!-- <Grid -->
<!-- x:Name="Preview" -->
<!-- Grid.Column="2" -->
<!-- VerticalAlignment="Stretch" -->
<!-- IsVisible="{Binding PreviewVisible}"> -->
<!-- <Border -->
<!-- x:DataType="vm:ResultViewModel" -->
<!-- IsVisible="{Binding ShowDefaultPreview}"> -->
<!-- <Grid -->
<!-- Margin="20,0,10,0" -->
<!-- VerticalAlignment="Stretch" -->
<!-- Background="Transparent"> -->
<!-- <Grid.RowDefinitions> -->
<!-- <RowDefinition Height="*" /> -->
<!-- <RowDefinition Height="Auto" /> -->
<!-- </Grid.RowDefinitions> -->
<!-- <Grid Grid.Row="0" VerticalAlignment="Center"> -->
<!-- <Grid.RowDefinitions> -->
<!-- <RowDefinition Height="Auto" /> -->
<!-- <RowDefinition Height="Auto" /> -->
<!-- </Grid.RowDefinitions> -->
<!-- <TextBlock -->
<!-- x:Name="PreviewGlyphIcon" -->
<!-- Grid.Row="0" -->
<!-- Margin="0,16,0,0" -->
<!-- FontFamily="{Binding Glyph.FontFamily}" -->
<!-- Text="{Binding Glyph.Glyph}" -->
<!-- IsVisible="{Binding ShowGlyph}" /> -->
<!-- <Image -->
<!-- x:Name="PreviewImageIcon" -->
<!-- Grid.Row="0" -->
<!-- MaxHeight="320" -->
<!-- Margin="0,16,0,0" -->
<!-- HorizontalAlignment="Center" -->
<!-- Source="{Binding PreviewImage}" -->
<!-- StretchDirection="DownOnly" -->
<!-- IsVisible="{Binding ShowPreviewImage}"> -->
<!-- ~1~ <Image.Style> @1@ -->
<!-- ~1~ <Style TargetType="{x:Type Image}"> @1@ -->
<!-- ~1~ <Setter Property="MaxWidth" Value="96" /> @1@ -->
<!-- ~1~ <Style.Triggers> @1@ -->
<!-- ~1~ <DataTrigger Binding="{Binding UseBigThumbnail}" Value="True"> @1@ -->
<!-- ~1~ <Setter Property="MaxWidth" @1@ -->
<!-- ~1~ Value="{Binding ElementName=Preview, Path=ActualWidth}" /> @1@ -->
<!-- ~1~ </DataTrigger> @1@ -->
<!-- ~1~ </Style.Triggers> @1@ -->
<!-- ~1~ </Style> @1@ -->
<!-- ~1~ </Image.Style> @1@ -->
<!-- </Image> -->
<!-- <TextBlock -->
<!-- x:Name="PreviewTitle" -->
<!-- Grid.Row="1" -->
<!-- Margin="0,6,0,16" -->
<!-- HorizontalAlignment="Stretch" -->
<!-- Text="{Binding Result.Title}" -->
<!-- TextAlignment="Center" -->
<!-- TextWrapping="Wrap" /> -->
<!-- </Grid> -->
<!-- <StackPanel Grid.Row="1"> -->
<!-- ~1~ <StackPanel.Style> @1@ -->
<!-- ~1~ <Style TargetType="{x:Type StackPanel}"> @1@ -->
<!-- ~1~ <Style.Triggers> @1@ -->
<!-- ~1~ <DataTrigger @1@ -->
<!-- ~1~ Binding="{Binding ElementName=PreviewSubTitle, UpdateSourceTrigger=PropertyChanged, Path=Text.Length}" @1@ -->
<!-- ~1~ Value="0"> @1@ -->
<!-- ~1~ <Setter Property="Visibility" Value="Collapsed" /> @1@ -->
<!-- ~1~ </DataTrigger> @1@ -->
<!-- ~1~ </Style.Triggers> @1@ -->
<!-- ~1~ </Style> @1@ -->
<!-- ~1~ </StackPanel.Style> @1@ -->
<!-- <Separator /> -->
<!-- <TextBlock -->
<!-- x:Name="PreviewSubTitle" -->
<!-- Text="{Binding Result.SubTitle}" /> -->
<!-- </StackPanel> -->
<!-- </Grid> -->
<!-- </Border> -->
<!-- <Border -->
<!-- x:DataType="vm:ResultViewModel" -->
<!-- IsVisible="{Binding ShowCustomizedPreview}"> -->
<!-- <ContentControl Content="{Binding Result.PreviewPanel.Value}" /> -->
<!-- </Border> -->
<!-- </Grid> -->
</Grid>
</StackPanel>
</Border>
</Grid>
</Window>

View file

@ -1,10 +1,6 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
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;
@ -19,20 +15,30 @@ using NotifyIcon = System.Windows.Forms.NotifyIcon;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin.SharedCommands;
using System.Windows.Threading;
using System.Windows.Data;
using ModernWpf.Controls;
using Key = System.Windows.Input.Key;
using System.Media;
using System.Windows;
using Avalonia;
using Avalonia.Input;
using Avalonia.Threading;
using PropertyChanged;
using static Flow.Launcher.ViewModel.SettingWindowViewModel;
using ContextMenu = System.Windows.Controls.ContextMenu;
using MenuItem = System.Windows.Controls.MenuItem;
using MouseButton = System.Windows.Input.MouseButton;
using RoutedEventArgs = Avalonia.Interactivity.RoutedEventArgs;
using Thickness = System.Windows.Thickness;
using Window = Avalonia.Controls.Window;
namespace Flow.Launcher
{
public partial class MainWindow
[DoNotNotify]
public partial class MainWindow : Window
{
#region Private Fields
private readonly Storyboard _progressBarStoryboard = new Storyboard();
// private readonly Storyboard _progressBarStoryboard = new Storyboard();
private bool isProgressBarStoryboardPaused;
private Settings _settings;
private NotifyIcon _notifyIcon;
@ -50,7 +56,7 @@ namespace Flow.Launcher
_settings = settings;
InitializeComponent();
InitializePosition();
// InitializePosition();
}
public MainWindow()
@ -58,30 +64,31 @@ namespace Flow.Launcher
InitializeComponent();
}
private void OnCopy(object sender, ExecutedRoutedEventArgs e)
{
var result = _viewModel.Results.SelectedItem?.Result;
if (QueryTextBox.SelectionLength == 0 && result != null)
{
string copyText = result.CopyText;
App.API.CopyToClipboard(copyText, directCopy: true);
}
else if (!string.IsNullOrEmpty(QueryTextBox.Text))
{
App.API.CopyToClipboard(QueryTextBox.SelectedText, showDefaultNotification: false);
}
}
// private void OnCopy(object sender, ExecutedRoutedEventArgs e)
// {
// var result = _viewModel.Results.SelectedItem?.Result;
// if (QueryTextBox.SelectionLength == 0 && result != null)
// {
// string copyText = result.CopyText;
// App.API.CopyToClipboard(copyText, directCopy: true);
// }
// else if (!string.IsNullOrEmpty(QueryTextBox.Text))
// {
// App.API.CopyToClipboard(QueryTextBox.SelectedText, showDefaultNotification: false);
// }
// }
//
// private void OnPaste(object sender, ExecutedRoutedEventArgs e)
// {
// if (System.Windows.Clipboard.ContainsText())
// {
// _viewModel.ChangeQueryText(System.Windows.Clipboard.GetText().Replace("\n", String.Empty)
// .Replace("\r", String.Empty));
// e.Handled = true;
// }
// }
private void OnPaste(object sender, ExecutedRoutedEventArgs e)
{
if (System.Windows.Clipboard.ContainsText())
{
_viewModel.ChangeQueryText(System.Windows.Clipboard.GetText().Replace("\n", String.Empty).Replace("\r", String.Empty));
e.Handled = true;
}
}
private async void OnClosing(object sender, CancelEventArgs e)
public async void OnClosing(object sender, CancelEventArgs e)
{
_notifyIcon.Visible = false;
App.API.SaveAppAllSettings();
@ -91,20 +98,20 @@ namespace Flow.Launcher
Environment.Exit(0);
}
private void OnInitialized(object sender, EventArgs e)
public void OnInitialized(object sender, EventArgs e)
{
}
private void OnLoaded(object sender, RoutedEventArgs _)
public void OnLoaded(object sender, RoutedEventArgs _)
{
CheckFirstLaunch();
HideStartup();
// show notify icon when flowlauncher is hidden
InitializeNotifyIcon();
InitializeColorScheme();
WindowsInteropHelper.DisableControlBox(this);
// WindowsInteropHelper.DisableControlBox(this);
InitProgressbarAnimation();
InitializePosition();
// InitializePosition();
PreviewReset();
// since the default main window visibility is visible
// so we need set focus during startup
@ -116,7 +123,7 @@ namespace Flow.Launcher
{
case nameof(MainViewModel.MainWindowVisibilityStatus):
{
Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
if (_viewModel.MainWindowVisibilityStatus)
{
@ -124,6 +131,7 @@ namespace Flow.Launcher
{
animationSound.Play();
}
UpdatePosition();
PreviewReset();
Activate();
@ -135,18 +143,18 @@ namespace Flow.Launcher
_viewModel.LastQuerySelected = true;
}
if (_viewModel.ProgressBarVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
if (_viewModel.ProgressBarVisibility && isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Begin(ProgressBar, true);
// _progressBarStoryboard.Begin(ProgressBar, true);
isProgressBarStoryboardPaused = false;
}
if (_settings.UseAnimation)
WindowAnimator();
// if (_settings.UseAnimation)
// WindowAnimator();
}
else if (!isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Stop(ProgressBar);
// _progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}
});
@ -154,17 +162,17 @@ namespace Flow.Launcher
}
case nameof(MainViewModel.ProgressBarVisibility):
{
Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
if (!_viewModel.ProgressBarVisibility && !isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Stop(ProgressBar);
// _progressBarStoryboard.Stop(ProgressBar);
isProgressBarStoryboardPaused = true;
}
else if (_viewModel.MainWindowVisibilityStatus &&
isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Begin(ProgressBar, true);
// _progressBarStoryboard.Begin(ProgressBar, true);
isProgressBarStoryboardPaused = false;
}
});
@ -176,9 +184,12 @@ namespace Flow.Launcher
MoveQueryTextToEnd();
_viewModel.QueryTextCursorMovedToEnd = false;
}
break;
case nameof(MainViewModel.GameModeStatus):
_notifyIcon.Icon = _viewModel.GameModeStatus ? Properties.Resources.gamemode : Properties.Resources.app;
_notifyIcon.Icon = _viewModel.GameModeStatus
? Properties.Resources.gamemode
: Properties.Resources.app;
break;
}
};
@ -197,10 +208,10 @@ namespace Flow.Launcher
UpdateNotifyIconText();
break;
case nameof(Settings.WindowLeft):
Left = _settings.WindowLeft;
Position = new PixelPoint(_settings.WindowLeft, _settings.WindowTop);
break;
case nameof(Settings.WindowTop):
Top = _settings.WindowTop;
Position = new PixelPoint(_settings.WindowLeft, _settings.WindowTop);
break;
}
};
@ -210,8 +221,7 @@ namespace Flow.Launcher
{
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
{
Top = _settings.WindowTop;
Left = _settings.WindowLeft;
Position = new PixelPoint(_settings.WindowLeft, _settings.WindowTop);
}
else
{
@ -219,39 +229,36 @@ namespace Flow.Launcher
switch (_settings.SearchWindowAlign)
{
case SearchWindowAligns.Center:
Left = HorizonCenter(screen);
Top = VerticalCenter(screen);
Position = new PixelPoint(HorizonCenter(screen), VerticalCenter(screen));
break;
case SearchWindowAligns.CenterTop:
Left = HorizonCenter(screen);
Top = 10;
Position = new PixelPoint(HorizonCenter(screen), 10);
break;
case SearchWindowAligns.LeftTop:
Left = HorizonLeft(screen);
Top = 10;
Position = new PixelPoint(HorizonLeft(screen), 10);
break;
case SearchWindowAligns.RightTop:
Left = HorizonRight(screen);
Top = 10;
Position = new PixelPoint(HorizonRight(screen), 10);
break;
case SearchWindowAligns.Custom:
Left = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X + _settings.CustomWindowLeft, 0).X;
Top = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y + _settings.CustomWindowTop).Y;
// Left = WindowsInteropHelper
// .TransformPixelsToDIP(this, screen.WorkingArea.X + _settings.CustomWindowLeft, 0).X;
// Top = WindowsInteropHelper
// .TransformPixelsToDIP(this, 0, screen.WorkingArea.Y + _settings.CustomWindowTop).Y;
break;
}
}
}
private void UpdateNotifyIconText()
{
var menu = contextMenu;
((MenuItem)menu.Items[0]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")";
((MenuItem)menu.Items[0]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") +
" (" + _settings.Hotkey + ")";
((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("GameMode");
((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset");
((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings");
((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit");
}
private void InitializeNotifyIcon()
@ -265,42 +272,31 @@ namespace Flow.Launcher
contextMenu = new ContextMenu();
var openIcon = new FontIcon
{
Glyph = "\ue71e"
};
var openIcon = new FontIcon { Glyph = "\ue71e" };
var open = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", Icon = openIcon
};
var gamemodeIcon = new FontIcon
{
Glyph = "\ue7fc"
Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" +
_settings.Hotkey + ")",
Icon = openIcon
};
var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" };
var gamemode = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("GameMode"), Icon = gamemodeIcon
};
var positionresetIcon = new FontIcon
{
Glyph = "\ue73f"
};
var positionresetIcon = new FontIcon { Glyph = "\ue73f" };
var positionreset = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"), Icon = positionresetIcon
};
var settingsIcon = new FontIcon
{
Glyph = "\ue713"
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"),
Icon = positionresetIcon
};
var settingsIcon = new FontIcon { Glyph = "\ue713" };
var settings = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"), Icon = settingsIcon
};
var exitIcon = new FontIcon
{
Glyph = "\ue7e8"
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"),
Icon = settingsIcon
};
var exitIcon = new FontIcon { Glyph = "\ue7e8" };
var exit = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"), Icon = exitIcon
@ -321,7 +317,8 @@ namespace Flow.Launcher
contextMenu.Items.Add(settings);
contextMenu.Items.Add(exit);
_notifyIcon.ContextMenuStrip = new ContextMenuStrip(); // it need for close the context menu. if not, context menu can't close.
_notifyIcon.ContextMenuStrip =
new ContextMenuStrip(); // it need for close the context menu. if not, context menu can't close.
_notifyIcon.MouseClick += (o, e) =>
{
switch (e.Button)
@ -358,128 +355,130 @@ namespace Flow.Launcher
_viewModel.Show();
await Task.Delay(300); // If don't give a time, Positioning will be weird.
var screen = SelectedScreen();
Left = HorizonCenter(screen);
Top = VerticalCenter(screen);
Position = new PixelPoint(HorizonCenter(screen), VerticalCenter(screen));
}
private void InitProgressbarAnimation()
{
var da = new DoubleAnimation(ProgressBar.X2, ActualWidth + 100, new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
var da1 = new DoubleAnimation(ProgressBar.X1, ActualWidth + 0, new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
Storyboard.SetTargetProperty(da, new PropertyPath("(Line.X2)"));
Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X1)"));
_progressBarStoryboard.Children.Add(da);
_progressBarStoryboard.Children.Add(da1);
_progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
_viewModel.ProgressBarVisibility = Visibility.Hidden;
isProgressBarStoryboardPaused = true;
// var da = new DoubleAnimation(ProgressBar.X2, ActualWidth + 100,
// new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
// var da1 = new DoubleAnimation(ProgressBar.X1, ActualWidth + 0,
// new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
// Storyboard.SetTargetProperty(da, new PropertyPath("(Line.X2)"));
// Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X1)"));
// _progressBarStoryboard.Children.Add(da);
// _progressBarStoryboard.Children.Add(da1);
// _progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
// _viewModel.ProgressBarVisibility = Visibility.Hidden;
// isProgressBarStoryboardPaused = true;
}
public void WindowAnimator()
// public void WindowAnimator()
// {
// if (_animating)
// return;
//
// _animating = true;
// UpdatePosition();
//
// Storyboard windowsb = new Storyboard();
// Storyboard clocksb = new Storyboard();
// Storyboard iconsb = new Storyboard();
// CircleEase easing = new CircleEase();
// easing.EasingMode = EasingMode.EaseInOut;
//
// var animationLength = _settings.AnimationSpeed switch
// {
// AnimationSpeeds.Slow => 560,
// AnimationSpeeds.Medium => 360,
// AnimationSpeeds.Fast => 160,
// _ => _settings.CustomAnimationLength
// };
//
// var WindowOpacity = new DoubleAnimation
// {
// From = 0,
// To = 1,
// Duration = TimeSpan.FromMilliseconds(animationLength * 2 / 3),
// FillBehavior = FillBehavior.Stop
// };
//
// var WindowMotion = new DoubleAnimation
// {
// From = Top + 10,
// To = Top,
// Duration = TimeSpan.FromMilliseconds(animationLength * 2 / 3),
// FillBehavior = FillBehavior.Stop
// };
// var IconMotion = new DoubleAnimation
// {
// From = 12,
// To = 0,
// EasingFunction = easing,
// Duration = TimeSpan.FromMilliseconds(animationLength),
// FillBehavior = FillBehavior.Stop
// };
//
// var ClockOpacity = new DoubleAnimation
// {
// From = 0,
// To = 1,
// EasingFunction = easing,
// Duration = TimeSpan.FromMilliseconds(animationLength),
// FillBehavior = FillBehavior.Stop
// };
// double TargetIconOpacity = SearchIcon.Opacity; // Animation Target Opacity from Style
// var IconOpacity = new DoubleAnimation
// {
// From = 0,
// To = TargetIconOpacity,
// EasingFunction = easing,
// Duration = TimeSpan.FromMilliseconds(animationLength),
// FillBehavior = FillBehavior.Stop
// };
//
// double right = ClockPanel.Margin.Right;
// var thicknessAnimation = new ThicknessAnimation
// {
// From = new Thickness(0, 12, right, 0),
// To = new Thickness(0, 0, right, 0),
// EasingFunction = easing,
// Duration = TimeSpan.FromMilliseconds(animationLength),
// FillBehavior = FillBehavior.Stop
// };
//
// Storyboard.SetTargetProperty(ClockOpacity, new PropertyPath(OpacityProperty));
// Storyboard.SetTargetName(thicknessAnimation, "ClockPanel");
// Storyboard.SetTargetProperty(thicknessAnimation, new PropertyPath(MarginProperty));
// Storyboard.SetTarget(WindowOpacity, this);
// Storyboard.SetTargetProperty(WindowOpacity, new PropertyPath(Window.OpacityProperty));
// Storyboard.SetTargetProperty(WindowMotion, new PropertyPath(Window.TopProperty));
// Storyboard.SetTargetProperty(IconMotion, new PropertyPath(TopProperty));
// Storyboard.SetTargetProperty(IconOpacity, new PropertyPath(OpacityProperty));
//
// clocksb.Children.Add(thicknessAnimation);
// clocksb.Children.Add(ClockOpacity);
// windowsb.Children.Add(WindowOpacity);
// windowsb.Children.Add(WindowMotion);
// iconsb.Children.Add(IconMotion);
// iconsb.Children.Add(IconOpacity);
//
// windowsb.Completed += (_, _) => _animating = false;
// _settings.WindowLeft = Left;
// _settings.WindowTop = Top;
//
// if (QueryTextBox.Text.Length == 0)
// {
// clocksb.Begin(ClockPanel);
// }
//
// iconsb.Begin(SearchIcon);
// windowsb.Begin(FlowMainWindow);
// }
private void OnMouseDown(object sender, PointerPressedEventArgs e)
{
if (_animating)
return;
_animating = true;
UpdatePosition();
Storyboard windowsb = new Storyboard();
Storyboard clocksb = new Storyboard();
Storyboard iconsb = new Storyboard();
CircleEase easing = new CircleEase();
easing.EasingMode = EasingMode.EaseInOut;
var animationLength = _settings.AnimationSpeed switch
{
AnimationSpeeds.Slow => 560,
AnimationSpeeds.Medium => 360,
AnimationSpeeds.Fast => 160,
_ => _settings.CustomAnimationLength
};
var WindowOpacity = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromMilliseconds(animationLength * 2 / 3),
FillBehavior = FillBehavior.Stop
};
var WindowMotion = new DoubleAnimation
{
From = Top + 10,
To = Top,
Duration = TimeSpan.FromMilliseconds(animationLength * 2 / 3),
FillBehavior = FillBehavior.Stop
};
var IconMotion = new DoubleAnimation
{
From = 12,
To = 0,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(animationLength),
FillBehavior = FillBehavior.Stop
};
var ClockOpacity = new DoubleAnimation
{
From = 0,
To = 1,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(animationLength),
FillBehavior = FillBehavior.Stop
};
double TargetIconOpacity = SearchIcon.Opacity; // Animation Target Opacity from Style
var IconOpacity = new DoubleAnimation
{
From = 0,
To = TargetIconOpacity,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(animationLength),
FillBehavior = FillBehavior.Stop
};
double right = ClockPanel.Margin.Right;
var thicknessAnimation = new ThicknessAnimation
{
From = new Thickness(0, 12, right, 0),
To = new Thickness(0, 0, right, 0),
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(animationLength),
FillBehavior = FillBehavior.Stop
};
Storyboard.SetTargetProperty(ClockOpacity, new PropertyPath(OpacityProperty));
Storyboard.SetTargetName(thicknessAnimation, "ClockPanel");
Storyboard.SetTargetProperty(thicknessAnimation, new PropertyPath(MarginProperty));
Storyboard.SetTarget(WindowOpacity, this);
Storyboard.SetTargetProperty(WindowOpacity, new PropertyPath(Window.OpacityProperty));
Storyboard.SetTargetProperty(WindowMotion, new PropertyPath(Window.TopProperty));
Storyboard.SetTargetProperty(IconMotion, new PropertyPath(TopProperty));
Storyboard.SetTargetProperty(IconOpacity, new PropertyPath(OpacityProperty));
clocksb.Children.Add(thicknessAnimation);
clocksb.Children.Add(ClockOpacity);
windowsb.Children.Add(WindowOpacity);
windowsb.Children.Add(WindowMotion);
iconsb.Children.Add(IconMotion);
iconsb.Children.Add(IconOpacity);
windowsb.Completed += (_, _) => _animating = false;
_settings.WindowLeft = Left;
_settings.WindowTop = Top;
if (QueryTextBox.Text.Length == 0)
{
clocksb.Begin(ClockPanel);
}
iconsb.Begin(SearchIcon);
windowsb.Begin(FlowMainWindow);
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) DragMove();
// if (e.Pointer.IsPrimary) DragMove();
}
private void OnPreviewDragOver(object sender, DragEventArgs e)
@ -499,8 +498,9 @@ namespace Flow.Launcher
private async void OnDeactivated(object sender, EventArgs e)
{
_settings.WindowLeft = Left;
_settings.WindowTop = Top;
return;
_settings.WindowLeft = Position.X;
_settings.WindowTop = Position.Y;
//This condition stops extra hide call when animator is on,
// which causes the toggling to occasional hide instead of show.
if (_viewModel.MainWindowVisibilityStatus)
@ -522,7 +522,7 @@ namespace Flow.Launcher
{
if (_animating)
return;
InitializePosition();
// InitializePosition();
}
private void OnLocationChanged(object sender, EventArgs e)
@ -531,8 +531,8 @@ namespace Flow.Launcher
return;
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
{
_settings.WindowLeft = Left;
_settings.WindowTop = Top;
_settings.WindowLeft = Position.X;
_settings.WindowTop = Position.Y;
}
}
@ -552,7 +552,7 @@ namespace Flow.Launcher
public Screen SelectedScreen()
{
Screen screen = null;
switch(_settings.SearchWindowScreen)
switch (_settings.SearchWindowScreen)
{
case SearchWindowScreens.Cursor:
screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
@ -574,38 +574,39 @@ namespace Flow.Launcher
screen = Screen.AllScreens[0];
break;
}
return screen ?? Screen.AllScreens[0];
}
public double HorizonCenter(Screen screen)
public int HorizonCenter(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - ActualWidth) / 2 + dip1.X;
return left;
var dip1 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - Width) / 2 + dip1.X;
return (int) left;
}
public double VerticalCenter(Screen screen)
public int VerticalCenter(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - QueryTextBox.ActualHeight) / 4 + dip1.Y;
return top;
var dip1 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - QueryTextBox.Height) / 4 + dip1.Y;
return (int)top;
}
public double HorizonRight(Screen screen)
public int HorizonRight(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip1.X + dip2.X - ActualWidth) - 10;
return left;
var dip1 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip1.X + dip2.X - Width) - 10;
return (int) left;
}
public double HorizonLeft(Screen screen)
public int HorizonLeft(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip1 = new PixelPoint(0,0); // WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var left = dip1.X + 10;
return left;
return (int) left;
}
/// <summary>
@ -641,6 +642,7 @@ namespace Flow.Launcher
_viewModel.LoadContextMenuCommand.Execute(null);
e.Handled = true;
}
break;
case Key.Left:
if (!_viewModel.SelectedIsFromQueryResults() && QueryTextBox.CaretIndex == 0)
@ -648,6 +650,7 @@ namespace Flow.Launcher
_viewModel.EscCommand.Execute(null);
e.Handled = true;
}
break;
case Key.Back:
if (specialKeyState.CtrlPressed)
@ -666,10 +669,10 @@ namespace Flow.Launcher
}
}
}
break;
default:
break;
}
}
@ -682,7 +685,7 @@ namespace Flow.Launcher
{
// QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle.
// To ensure QueryTextBox is up to date with QueryText from the View, we need to Dispatch with such a priority
Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
Dispatcher.UIThread.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length);
}
public void InitializeColorScheme()
@ -701,8 +704,8 @@ namespace Flow.Launcher
{
if (_viewModel.QueryText != QueryTextBox.Text)
{
BindingExpression be = QueryTextBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);
be.UpdateSource();
// BindingExpression be = QueryTextBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);
// be.UpdateSource();
}
}
}

View file

@ -1,501 +0,0 @@
<Window
x:Class="Flow.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Name="FlowMainWindow"
Title="Flow Launcher"
MinWidth="{Binding MainWindowWidth, Mode=OneWay}"
MaxWidth="{Binding MainWindowWidth, Mode=OneWay}"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
AllowDrop="True"
AllowsTransparency="True"
Background="Transparent"
Closing="OnClosing"
Deactivated="OnDeactivated"
Icon="Images/app.png"
Initialized="OnInitialized"
Loaded="OnLoaded"
LocationChanged="OnLocationChanged"
Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
PreviewKeyDown="OnKeyDown"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="Height"
Style="{DynamicResource WindowStyle}"
Topmost="True"
Visibility="{Binding MainWindowVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
WindowStartupLocation="Manual"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
<converters:BorderClipConverter x:Key="BorderClipConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:SplitterConverter x:Key="SplitterConverter" />
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter" />
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter" />
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter" />
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
<KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}" />
<KeyBinding Key="Tab" Command="{Binding AutocompleteQueryCommand}" />
<KeyBinding
Key="Tab"
Command="{Binding AutocompleteQueryCommand}"
Modifiers="Shift" />
<KeyBinding
Key="I"
Command="{Binding OpenSettingCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="N"
Command="{Binding SelectNextItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="J"
Command="{Binding SelectNextItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="D"
Command="{Binding SelectNextPageCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="P"
Command="{Binding SelectPrevItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="K"
Command="{Binding SelectPrevItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="U"
Command="{Binding SelectPrevPageCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="Home"
Command="{Binding SelectFirstResultCommand}"
Modifiers="Alt" />
<KeyBinding
Key="O"
Command="{Binding LoadContextMenuCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="R"
Command="{Binding ReQueryCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="H"
Command="{Binding LoadHistoryCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="OemCloseBrackets"
Command="{Binding IncreaseWidthCommand}"
Modifiers="Control" />
<KeyBinding
Key="OemOpenBrackets"
Command="{Binding DecreaseWidthCommand}"
Modifiers="Control" />
<KeyBinding
Key="OemPlus"
Command="{Binding IncreaseMaxResultCommand}"
Modifiers="Control" />
<KeyBinding
Key="OemMinus"
Command="{Binding DecreaseMaxResultCommand}"
Modifiers="Control" />
<KeyBinding
Key="H"
Command="{Binding LoadHistoryCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="Enter"
Command="{Binding OpenResultCommand}"
Modifiers="Ctrl+Shift" />
<KeyBinding
Key="Enter"
Command="{Binding LoadContextMenuCommand}"
Modifiers="Shift" />
<KeyBinding Key="Enter" Command="{Binding OpenResultCommand}" />
<KeyBinding
Key="Enter"
Command="{Binding OpenResultCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="Enter"
Command="{Binding OpenResultCommand}"
Modifiers="Alt" />
<KeyBinding
Key="D1"
Command="{Binding OpenResultCommand}"
CommandParameter="0"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D2"
Command="{Binding OpenResultCommand}"
CommandParameter="1"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D3"
Command="{Binding OpenResultCommand}"
CommandParameter="2"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D4"
Command="{Binding OpenResultCommand}"
CommandParameter="3"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D5"
Command="{Binding OpenResultCommand}"
CommandParameter="4"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D6"
Command="{Binding OpenResultCommand}"
CommandParameter="5"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D7"
Command="{Binding OpenResultCommand}"
CommandParameter="6"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D8"
Command="{Binding OpenResultCommand}"
CommandParameter="7"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D9"
Command="{Binding OpenResultCommand}"
CommandParameter="8"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="D0"
Command="{Binding OpenResultCommand}"
CommandParameter="9"
Modifiers="{Binding OpenResultCommandModifiers}" />
<KeyBinding
Key="F12"
Command="{Binding ToggleGameModeCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding TogglePreviewCommand}"
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings>
<Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
<StackPanel Orientation="Vertical">
<Grid>
<Border Style="{DynamicResource QueryBoxBgStyle}">
<Grid>
<TextBox
x:Name="QueryTextSuggestionBox"
IsEnabled="False"
Style="{DynamicResource QuerySuggestionBoxStyle}">
<TextBox.Text>
<MultiBinding Converter="{StaticResource QuerySuggestionBoxConverter}">
<Binding ElementName="QueryTextBox" Mode="OneTime" />
<Binding ElementName="ResultListBox" Path="SelectedItem" />
<Binding ElementName="QueryTextBox" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox
x:Name="QueryTextBox"
AllowDrop="True"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
PreviewDragOver="OnPreviewDragOver"
PreviewKeyUp="QueryTextBox_KeyUp"
Style="{DynamicResource QueryBoxStyle}"
Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="Visible">
<TextBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy" Executed="OnCopy" />
<CommandBinding Command="ApplicationCommands.Paste" Executed="OnPaste" />
</TextBox.CommandBindings>
<TextBox.ContextMenu>
<ContextMenu MinWidth="160">
<MenuItem Command="ApplicationCommands.Cut" Header="{DynamicResource cut}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe8c6;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="ApplicationCommands.Copy" Header="{DynamicResource copy}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe8c8;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="ApplicationCommands.Paste" Header="{DynamicResource paste}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe77f;" />
</MenuItem.Icon>
</MenuItem>
<Separator
Margin="0"
Padding="0,4,0,4"
Background="{DynamicResource ContextSeparator}" />
<MenuItem Click="OnContextMenusForSettingsClick" Header="{DynamicResource flowlauncher_settings}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe713;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding EscCommand}" Header="{DynamicResource closeWindow}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe711;" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Border>
<StackPanel
x:Name="ClockPanel"
IsHitTestVisible="False"
Style="{DynamicResource ClockPanel}">
<TextBlock
x:Name="ClockBox"
Style="{DynamicResource ClockBox}"
Text="{Binding ClockText}"
Visibility="{Binding Settings.UseClock, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="DateBox"
Style="{DynamicResource DateBox}"
Text="{Binding DateText}"
Visibility="{Binding Settings.UseDate, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<Border>
<Grid>
<Image
x:Name="PluginActivationIcon"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Panel.ZIndex="2"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{Binding PluginIconPath}"
Stretch="Uniform"
Style="{DynamicResource PluginActivationIcon}" />
<Canvas Style="{DynamicResource SearchIconPosition}">
<Path
Name="SearchIcon"
Margin="0"
Data="{DynamicResource SearchIconImg}"
Stretch="Fill"
Style="{DynamicResource SearchIconStyle}"
Visibility="{Binding SearchIconVisibility}" />
</Canvas>
</Grid>
</Border>
<Line
x:Name="ProgressBar"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}, Path=ActualWidth}"
Height="2"
Margin="12,0,12,0"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
StrokeThickness="2"
Style="{DynamicResource PendingLineStyle}"
Visibility="{Binding ProgressBarVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
X1="-100"
X2="0"
Y1="0"
Y2="0" />
</Grid>
<Grid ClipToBounds="True">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ResultListBox, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ContextMenu, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=History, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<Rectangle
Name="MiddleSeparator"
Width="Auto"
HorizontalAlignment="Stretch"
Style="{DynamicResource SeparatorStyle}" />
</ContentControl>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="0.85*" MinWidth="244" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="ResultArea"
Grid.Column="0"
Grid.ColumnSpan="{Binding ResultAreaColumn}">
<Border Style="{DynamicResource WindowRadius}">
<Border.Clip>
<MultiBinding Converter="{StaticResource BorderClipConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Border.Clip>
<ContentControl>
<flowlauncher:ResultListBox
x:Name="ResultListBox"
DataContext="{Binding Results}"
LeftClickResultCommand="{Binding LeftClickResultCommand}"
RightClickResultCommand="{Binding RightClickResultCommand}" />
</ContentControl>
</Border>
<Border Style="{DynamicResource WindowRadius}">
<Border.Clip>
<MultiBinding Converter="{StaticResource BorderClipConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Border.Clip>
<ContentControl>
<flowlauncher:ResultListBox
x:Name="ContextMenu"
DataContext="{Binding ContextMenu}"
LeftClickResultCommand="{Binding LeftClickResultCommand}"
RightClickResultCommand="{Binding RightClickResultCommand}" />
</ContentControl>
</Border>
<Border Style="{DynamicResource WindowRadius}">
<Border.Clip>
<MultiBinding Converter="{StaticResource BorderClipConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Border.Clip>
<ContentControl>
<flowlauncher:ResultListBox
x:Name="History"
DataContext="{Binding History}"
LeftClickResultCommand="{Binding LeftClickResultCommand}"
RightClickResultCommand="{Binding RightClickResultCommand}" />
</ContentControl>
</Border>
</StackPanel>
<GridSplitter
Grid.Column="1"
Width="{Binding PreviewVisible, Converter={StaticResource SplitterConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Background="Transparent"
ShowsPreview="True" />
<Grid
x:Name="Preview"
Grid.Column="2"
VerticalAlignment="Stretch"
Style="{DynamicResource PreviewArea}"
Visibility="{Binding PreviewVisible, Converter={StaticResource BoolToVisibilityConverter}}">
<Border
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
Style="{DynamicResource PreviewBorderStyle}"
Visibility="{Binding ShowDefaultPreview}">
<Grid
Margin="20,0,10,0"
VerticalAlignment="Stretch"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
x:Name="PreviewGlyphIcon"
Grid.Row="0"
Height="Auto"
Margin="0,16,0,0"
FontFamily="{Binding Glyph.FontFamily}"
Style="{DynamicResource PreviewGlyph}"
Text="{Binding Glyph.Glyph}"
Visibility="{Binding ShowGlyph}" />
<Image
x:Name="PreviewImageIcon"
Grid.Row="0"
MaxHeight="320"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Source="{Binding PreviewImage}"
StretchDirection="DownOnly"
Visibility="{Binding ShowPreviewImage}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="MaxWidth" Value="96" />
<Style.Triggers>
<DataTrigger Binding="{Binding UseBigThumbnail}" Value="True">
<Setter Property="MaxWidth" Value="{Binding ElementName=Preview, Path=ActualWidth}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock
x:Name="PreviewTitle"
Grid.Row="1"
Margin="0,6,0,16"
HorizontalAlignment="Stretch"
Style="{DynamicResource PreviewItemTitleStyle}"
Text="{Binding Result.Title}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Grid>
<StackPanel Grid.Row="1">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PreviewSubTitle, UpdateSourceTrigger=PropertyChanged, Path=Text.Length}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Separator Style="{DynamicResource PreviewSep}" />
<TextBlock
x:Name="PreviewSubTitle"
Style="{DynamicResource PreviewItemSubTitleStyle}"
Text="{Binding Result.SubTitle}" />
</StackPanel>
</Grid>
</Border>
<Border
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
Style="{DynamicResource PreviewBorderStyle}"
Visibility="{Binding ShowCustomizedPreview}">
<ContentControl Content="{Binding Result.PreviewPanel.Value}" />
</Border>
</Grid>
</Grid>
</StackPanel>
</Border>
</Grid>
</Window>

View file

@ -25,6 +25,7 @@ using Flow.Launcher.Infrastructure.Storage;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Collections.Specialized;
using Avalonia.Threading;
namespace Flow.Launcher
{
@ -102,7 +103,7 @@ namespace Flow.Launcher
public void OpenSettingDialog()
{
Application.Current.Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>(this, _settingsVM);
});
@ -147,9 +148,9 @@ namespace Flow.Launcher
}
}
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = true;
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = false;
public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key);

View file

@ -0,0 +1,191 @@
<UserControl
x:Class="Flow.Launcher.ResultListBox"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="using:Flow.Launcher.Converters"
xmlns:vm="using:Flow.Launcher.ViewModel"
x:DataType="vm:ResultsViewModel">
<ListBox
HorizontalAlignment="Stretch"
Focusable="False"
ItemsSource="{Binding Results}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionMode="AlwaysSelected"
AutoScrollToSelectedItem="True"
IsVisible="{Binding Visibility}"
MaxHeight="{Binding MaxHeight}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch" Height="52">
<Button.Template>
<ControlTemplate>
<ContentPresenter Content="{TemplateBinding Button.Content}" />
</ControlTemplate>
</Button.Template>
<Button.Content>
<Grid
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="Hand"
UseLayoutRounding="False">
<Grid.Resources>
<converter:HighlightTextConverter x:Key="HighlightTextConverter" />
<converter:OrdinalConverter x:Key="OrdinalConverter" />
<converter:OpenResultHotkeyVisibilityConverter
x:Key="OpenResultHotkeyVisibilityConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="9*" />
<ColumnDefinition Width="Auto" MinWidth="8" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="HotkeyArea"
Grid.Column="2"
Margin="0,0,10,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowOpenResultHotkey}">
<TextBlock
x:Name="Hotkey"
Margin="12,0,12,0"
Padding="0,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<TextBlock.Text>
<!-- <MultiBinding StringFormat="{}{0}+{1}"> -->
<!-- <Binding Path="OpenResultModifiers" /> -->
<!-- <Binding Converter="{StaticResource ResourceKey=OrdinalConverter}" -->
<!-- RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" /> -->
<!-- </MultiBinding> -->
</TextBlock.Text>
</TextBlock>
</StackPanel>
<Grid Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border
x:Name="Bullet"
Grid.Column="0" />
<Border
Grid.Column="1"
Margin="9,0,0,0"
BorderBrush="Transparent"
BorderThickness="1">
<Image
x:Name="ImageIcon"
Margin="0,0,0,0"
HorizontalAlignment="Center"
IsHitTestVisible="False"
Source="{Binding Image, TargetNullValue={x:Null}}"
Stretch="Uniform"
IsVisible="{Binding ShowIcon}">
</Image>
</Border>
<Border
Grid.Column="1"
Margin="9,0,0,0"
BorderBrush="Transparent"
BorderThickness="0">
<TextBlock
x:Name="GlyphIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{Binding Glyph.FontFamily}"
Text="{Binding Glyph.Glyph}"
IsVisible="{Binding ShowGlyph}" />
</Border>
</Grid>
<Grid
Grid.Column="1"
Margin="6,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ProgressBar
x:Name="progressbarResult"
Grid.Row="0"
Foreground="{Binding Result.ProgressBarColor}"
Value="{Binding ResultProgress, Mode=OneWay}">
</ProgressBar>
<TextBlock
x:Name="Title"
Grid.Row="0"
VerticalAlignment="Center"
DockPanel.Dock="Left"
IsEnabled="False"
Text="{Binding Result.Title}"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding ShowTitleToolTip}">
<!-- <vm:ResultsViewModel.FormattedText> -->
<!-- <MultiBinding Converter="{StaticResource HighlightTextConverter}"> -->
<!-- <Binding Path="Result.Title" /> -->
<!-- <Binding Path="Result.TitleHighlightData" /> -->
<!-- </MultiBinding> -->
<!-- </vm:ResultsViewModel.FormattedText> -->
</TextBlock>
<TextBlock
x:Name="SubTitle"
Grid.Row="1"
IsEnabled="False"
Text="{Binding Result.SubTitle}"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding ShowSubTitleToolTip}" />
</Grid>
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- http://stackoverflow.com/questions/16819577/setting-background-color-or-wpf-4-0-listbox-windows-8/#16820062 -->
<!-- <ListBox.ItemContainerStyle> -->
<!-- <Style TargetType="{x:Type ListBoxItem}"> -->
<!-- <EventSetter Event="MouseEnter" Handler="OnMouseEnter" /> -->
<!-- <EventSetter Event="MouseMove" Handler="OnMouseMove" /> -->
<!-- <Setter Property="Height" Value="{DynamicResource ResultItemHeight}" /> -->
<!-- <Setter Property="Margin" Value="0" /> -->
<!-- <Setter Property="Padding" Value="0" /> -->
<!-- <Setter Property="BorderThickness" Value="0" /> -->
<!-- <Setter Property="Template"> -->
<!-- <Setter.Value> -->
<!-- <ControlTemplate TargetType="{x:Type ListBoxItem}"> -->
<!-- <Border -->
<!-- x:Name="Bd" -->
<!-- Margin="{DynamicResource ItemMargin}" -->
<!-- Background="{TemplateBinding Background}" -->
<!-- BorderBrush="{TemplateBinding BorderBrush}" -->
<!-- CornerRadius="{DynamicResource ItemRadius}" -->
<!-- SnapsToDevicePixels="True"> -->
<!-- <ContentPresenter -->
<!-- HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" -->
<!-- VerticalAlignment="{TemplateBinding VerticalContentAlignment}" -->
<!-- Content="{TemplateBinding Content}" -->
<!-- ContentStringFormat="{TemplateBinding ContentStringFormat}" -->
<!-- ContentTemplate="{TemplateBinding ContentTemplate}" -->
<!-- SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> -->
<!-- </Border> -->
<!-- <ControlTemplate.Triggers> -->
<!-- <Trigger Property="IsSelected" Value="True"> -->
<!-- <Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemSelectedBackgroundColor}" /> -->
<!-- <Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource ItemSelectedBackgroundColor}" /> -->
<!-- </Trigger> -->
<!-- </ControlTemplate.Triggers> -->
<!-- </ControlTemplate> -->
<!-- </Setter.Value> -->
<!-- </Setter> -->
<!-- </Style> -->
<!-- </ListBox.ItemContainerStyle> -->
</ListBox>
</UserControl>

View file

@ -0,0 +1,162 @@
using System;
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Flow.Launcher.ViewModel;
using PropertyChanged;
namespace Flow.Launcher
{
[DoNotNotify]
public partial class ResultListBox : UserControl
{
protected object _lock = new object();
private PixelPoint _lastpos;
private ListBoxItem curItem = null;
public ResultListBox()
{
InitializeComponent();
}
//
// public static readonly DependencyProperty RightClickResultCommandProperty =
// DependencyProperty.Register("RightClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
//
// public ICommand RightClickResultCommand
// {
// get
// {
// return (ICommand)GetValue(RightClickResultCommandProperty);
// }
// set
// {
// SetValue(RightClickResultCommandProperty, value);
// }
// }
//
// public static readonly DependencyProperty LeftClickResultCommandProperty =
// DependencyProperty.Register("LeftClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
//
// public ICommand LeftClickResultCommand
// {
// get
// {
// return (ICommand)GetValue(LeftClickResultCommandProperty);
// }
// set
// {
// SetValue(LeftClickResultCommandProperty, value);
// }
// }
//
// private void OnMouseEnter(object sender, MouseEventArgs e)
// {
// lock (_lock)
// {
// curItem = (ListBoxItem)sender;
// var p = e.GetPosition((IInputElement)sender);
// _lastpos = p;
// }
// }
//
// private void OnMouseMove(object sender, MouseEventArgs e)
// {
// lock (_lock)
// {
// var p = e.GetPosition((IInputElement)sender);
// if (_lastpos != p)
// {
// ((ListBoxItem)sender).IsSelected = true;
// }
// }
// }
private void ListBox_PreviewMouseDown(object sender, PointerPressedEventArgs e)
{
lock (_lock)
{
if (curItem != null)
{
curItem.IsSelected = true;
}
}
}
private Point start;
private string path;
private string query;
// this method is called by the UI thread, which is single threaded, so we can be sloppy with locking
private bool isDragging;
//
// private void ResultList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
// {
// if (Mouse.DirectlyOver is not FrameworkElement
// {
// DataContext: ResultViewModel
// {
// Result:
// {
// CopyText: { } copyText,
// OriginQuery.RawQuery: { } rawQuery
// }
// }
// }) return;
//
// path = copyText;
// query = rawQuery;
// start = e.GetPosition(null);
// isDragging = true;
// }
// private void ResultList_MouseMove(object sender, MouseEventArgs e)
// {
// if (e.LeftButton != MouseButtonState.Pressed || !isDragging)
// {
// start = default;
// path = string.Empty;
// query = string.Empty;
// isDragging = false;
// return;
// }
//
// if (!File.Exists(path) && !Directory.Exists(path))
// return;
//
// Point mousePosition = e.GetPosition(null);
// Vector diff = this.start - mousePosition;
//
// if (Math.Abs(diff.X) < SystemParameters.MinimumHorizontalDragDistance
// || Math.Abs(diff.Y) < SystemParameters.MinimumVerticalDragDistance)
// return;
//
// isDragging = false;
//
// var data = new DataObject(DataFormats.FileDrop, new[]
// {
// path
// });
//
// // Reassigning query to a new variable because for some reason
// // after DragDrop.DoDragDrop call, 'query' loses its content, i.e. becomes empty string
// var rawQuery = query;
// var effect = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move | DragDropEffects.Copy);
// if (effect == DragDropEffects.Move)
// App.API.ChangeQuery(rawQuery, true);
// }
// private void ResultListBox_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
// {
// if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
// return;
//
// RightClickResultCommand?.Execute(result.Result);
// }
// private void ResultListBox_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
// {
// if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
// return;
//
// LeftClickResultCommand?.Execute(null);
// }
}
}

View file

@ -1,255 +0,0 @@
<ListBox
x:Class="Flow.Launcher.ResultListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="clr-namespace:Flow.Launcher.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
MaxHeight="{Binding MaxHeight}"
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance vm:ResultsViewModel}"
d:DesignHeight="100"
d:DesignWidth="100"
Focusable="False"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Results}"
KeyboardNavigation.DirectionalNavigation="Cycle"
PreviewMouseDown="ListBox_PreviewMouseDown"
PreviewMouseLeftButtonDown="ResultList_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="ResultListBox_OnPreviewMouseUp"
PreviewMouseMove="ResultList_MouseMove"
PreviewMouseRightButtonDown="ResultListBox_OnPreviewMouseRightButtonDown"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionChanged="OnSelectionChanged"
SelectionMode="Single"
Style="{DynamicResource BaseListboxStyle}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
Visibility="{Binding Visibility}"
mc:Ignorable="d">
<!-- IsSynchronizedWithCurrentItem: http://stackoverflow.com/a/7833798/2833083 -->
<ListBox.Resources>
<converter:IconRadiusConverter x:Key="IconRadiusConverter" />
<converter:DiameterToCenterPointConverter x:Key="DiameterToCenterPointConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch">
<Button.Template>
<ControlTemplate>
<ContentPresenter Content="{TemplateBinding Button.Content}" />
</ControlTemplate>
</Button.Template>
<Button.Content>
<Grid
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="Hand"
UseLayoutRounding="False">
<Grid.Resources>
<converter:HighlightTextConverter x:Key="HighlightTextConverter" />
<converter:OrdinalConverter x:Key="OrdinalConverter" />
<converter:OpenResultHotkeyVisibilityConverter x:Key="OpenResultHotkeyVisibilityConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Style="{DynamicResource ImageAreaWidth}" />
<ColumnDefinition Width="9*" />
<ColumnDefinition Width="Auto" MinWidth="8" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="HotkeyArea"
Grid.Column="2"
Margin="0,0,10,0"
VerticalAlignment="Center"
Visibility="{Binding ShowOpenResultHotkey}">
<TextBlock
x:Name="Hotkey"
Margin="12,0,12,0"
Padding="0,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{DynamicResource ItemHotkeyStyle}">
<TextBlock.Visibility>
<Binding Converter="{StaticResource ResourceKey=OpenResultHotkeyVisibilityConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" />
</TextBlock.Visibility>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}+{1}">
<Binding Path="OpenResultModifiers" />
<Binding Converter="{StaticResource ResourceKey=OrdinalConverter}" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<Grid Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border
x:Name="Bullet"
Grid.Column="0"
Style="{DynamicResource BulletStyle}" />
<Border
Grid.Column="1"
Margin="9,0,0,0"
BorderBrush="Transparent"
BorderThickness="1">
<Image
x:Name="ImageIcon"
Margin="0,0,0,0"
HorizontalAlignment="Center"
IsHitTestVisible="False"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Image, TargetNullValue={x:Null}}"
Stretch="Uniform"
Style="{DynamicResource ImageIconStyle}"
Visibility="{Binding ShowIcon}">
<Image.Clip>
<EllipseGeometry Center="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource DiameterToCenterPointConverter}}">
<EllipseGeometry.RadiusX>
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
<Binding ElementName="ImageIcon" Path="ActualWidth" />
<Binding Path="Result.RoundedIcon" />
</MultiBinding>
</EllipseGeometry.RadiusX>
<EllipseGeometry.RadiusY>
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
<Binding ElementName="ImageIcon" Path="ActualWidth" />
<Binding Path="Result.RoundedIcon" />
</MultiBinding>
</EllipseGeometry.RadiusY>
</EllipseGeometry>
</Image.Clip>
</Image>
</Border>
<Border
Grid.Column="1"
Margin="9,0,0,0"
BorderBrush="Transparent"
BorderThickness="0">
<TextBlock
x:Name="GlyphIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{Binding Glyph.FontFamily}"
Style="{DynamicResource ItemGlyph}"
Text="{Binding Glyph.Glyph}"
Visibility="{Binding ShowGlyph}" />
</Border>
</Grid>
<Grid
Grid.Column="1"
Margin="6,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition x:Name="SubTitleRowDefinition" Height="Auto" />
</Grid.RowDefinitions>
<ProgressBar
x:Name="progressbarResult"
Grid.Row="0"
Foreground="{Binding Result.ProgressBarColor}"
Value="{Binding ResultProgress, Mode=OneWay}">
<ProgressBar.Style>
<Style BasedOn="{StaticResource ProgressBarResult}" TargetType="ProgressBar">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Result.ProgressBar}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<TextBlock
x:Name="Title"
Grid.Row="0"
VerticalAlignment="Center"
DockPanel.Dock="Left"
IsEnabled="False"
Style="{DynamicResource ItemTitleStyle}"
Text="{Binding Result.Title}"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding ShowTitleToolTip}"
ToolTipService.ShowOnDisabled="True">
<vm:ResultsViewModel.FormattedText>
<MultiBinding Converter="{StaticResource HighlightTextConverter}">
<Binding Path="Result.Title" />
<Binding Path="Result.TitleHighlightData" />
</MultiBinding>
</vm:ResultsViewModel.FormattedText>
</TextBlock>
<TextBlock
x:Name="SubTitle"
Grid.Row="1"
IsEnabled="False"
Style="{DynamicResource ItemSubTitleStyle}"
Text="{Binding Result.SubTitle}"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding ShowSubTitleToolTip}"
ToolTipService.ShowOnDisabled="True" />
</Grid>
</Grid>
</Button.Content>
</Button>
<!-- a result item height is 52 including margin -->
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
<Setter TargetName="Bullet" Property="Style" Value="{DynamicResource ItemBulletSelectedStyle}" />
<Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
<Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
<Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" />
<Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- http://stackoverflow.com/questions/16819577/setting-background-color-or-wpf-4-0-listbox-windows-8/#16820062 -->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseEnter" Handler="OnMouseEnter" />
<EventSetter Event="MouseMove" Handler="OnMouseMove" />
<Setter Property="Height" Value="{DynamicResource ResultItemHeight}" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border
x:Name="Bd"
Margin="{DynamicResource ItemMargin}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{DynamicResource ItemRadius}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemSelectedBackgroundColor}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource ItemSelectedBackgroundColor}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

View file

@ -1,167 +0,0 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
{
public partial class ResultListBox
{
protected object _lock = new object();
private Point _lastpos;
private ListBoxItem curItem = null;
public ResultListBox()
{
InitializeComponent();
}
public static readonly DependencyProperty RightClickResultCommandProperty =
DependencyProperty.Register("RightClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
public ICommand RightClickResultCommand
{
get
{
return (ICommand)GetValue(RightClickResultCommandProperty);
}
set
{
SetValue(RightClickResultCommandProperty, value);
}
}
public static readonly DependencyProperty LeftClickResultCommandProperty =
DependencyProperty.Register("LeftClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
public ICommand LeftClickResultCommand
{
get
{
return (ICommand)GetValue(LeftClickResultCommandProperty);
}
set
{
SetValue(LeftClickResultCommandProperty, value);
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
{
ScrollIntoView(e.AddedItems[0]);
}
}
private void OnMouseEnter(object sender, MouseEventArgs e)
{
lock (_lock)
{
curItem = (ListBoxItem)sender;
var p = e.GetPosition((IInputElement)sender);
_lastpos = p;
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
lock (_lock)
{
var p = e.GetPosition((IInputElement)sender);
if (_lastpos != p)
{
((ListBoxItem)sender).IsSelected = true;
}
}
}
private void ListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
lock (_lock)
{
if (curItem != null)
{
curItem.IsSelected = true;
}
}
}
private Point start;
private string path;
private string query;
// this method is called by the UI thread, which is single threaded, so we can be sloppy with locking
private bool isDragging;
private void ResultList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver is not FrameworkElement
{
DataContext: ResultViewModel
{
Result:
{
CopyText: { } copyText,
OriginQuery.RawQuery: { } rawQuery
}
}
}) return;
path = copyText;
query = rawQuery;
start = e.GetPosition(null);
isDragging = true;
}
private void ResultList_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !isDragging)
{
start = default;
path = string.Empty;
query = string.Empty;
isDragging = false;
return;
}
if (!File.Exists(path) && !Directory.Exists(path))
return;
Point mousePosition = e.GetPosition(null);
Vector diff = this.start - mousePosition;
if (Math.Abs(diff.X) < SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(diff.Y) < SystemParameters.MinimumVerticalDragDistance)
return;
isDragging = false;
var data = new DataObject(DataFormats.FileDrop, new[]
{
path
});
// Reassigning query to a new variable because for some reason
// after DragDrop.DoDragDrop call, 'query' loses its content, i.e. becomes empty string
var rawQuery = query;
var effect = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move | DragDropEffects.Copy);
if (effect == DragDropEffects.Move)
App.API.ChangeQuery(rawQuery, true);
}
private void ResultListBox_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
return;
RightClickResultCommand?.Execute(result.Result);
}
private void ResultListBox_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
return;
LeftClickResultCommand?.Execute(null);
}
}
}

View file

@ -1975,10 +1975,10 @@
</Border>
<ContentControl Grid.Row="2">
<flowlauncher:ResultListBox
DataContext="{Binding PreviewResults, Mode=OneTime}"
IsHitTestVisible="False"
Visibility="Visible" />
<!-- <flowlauncher:ResultListBox -->
<!-- DataContext="{Binding PreviewResults, Mode=OneTime}" -->
<!-- IsHitTestVisible="False" -->
<!-- IsVisible="True" /> -->
</ContentControl>
</Grid>

View file

@ -25,6 +25,7 @@ using CommunityToolkit.Mvvm.Input;
using System.Globalization;
using System.Windows.Input;
using System.ComponentModel;
using Avalonia.Threading;
namespace Flow.Launcher.ViewModel
{
@ -93,7 +94,7 @@ namespace Flow.Launcher.ViewModel
{
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
};
Results = new ResultsViewModel(Settings)
ResultsVM = new ResultsViewModel(Settings)
{
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
};
@ -101,13 +102,13 @@ namespace Flow.Launcher.ViewModel
{
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
};
_selectedResults = Results;
_selectedResults = ResultsVM;
Results.PropertyChanged += (_, args) =>
ResultsVM.PropertyChanged += (_, args) =>
{
switch (args.PropertyName)
{
case nameof(Results.SelectedItem):
case nameof(ResultsVM.SelectedItem):
UpdatePreview();
break;
}
@ -174,7 +175,8 @@ namespace Flow.Launcher.ViewModel
var token = e.Token == default ? _updateToken : e.Token;
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, token)))
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query,
token)))
{
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
}
@ -188,7 +190,8 @@ namespace Flow.Launcher.ViewModel
Hide();
await PluginManager.ReloadDataAsync().ConfigureAwait(false);
Notification.Show(InternationalizationManager.Instance.GetTranslation("success"), InternationalizationManager.Instance.GetTranslation("completedSuccessfully"));
Notification.Show(InternationalizationManager.Instance.GetTranslation("success"),
InternationalizationManager.Instance.GetTranslation("completedSuccessfully"));
}
[RelayCommand]
@ -201,7 +204,7 @@ namespace Flow.Launcher.ViewModel
}
else
{
SelectedResults = Results;
SelectedResults = ResultsVM;
}
}
@ -226,7 +229,7 @@ namespace Flow.Launcher.ViewModel
}
else
{
SelectedResults = Results;
SelectedResults = ResultsVM;
}
}
@ -278,11 +281,13 @@ namespace Flow.Launcher.ViewModel
{
results.SelectedIndex = int.Parse(index);
}
var result = results.SelectedItem?.Result;
if (result == null)
{
return;
}
var hideWindow = await result.ExecuteAsync(new ActionContext
{
// not null means pressing modifier key + number, should ignore the modifier key
@ -350,7 +355,7 @@ namespace Flow.Launcher.ViewModel
{
if (!SelectedIsFromQueryResults())
{
SelectedResults = Results;
SelectedResults = ResultsVM;
}
else
{
@ -386,7 +391,7 @@ namespace Flow.Launcher.ViewModel
}
}
public ResultsViewModel Results { get; private set; }
public ResultsViewModel ResultsVM { get; private set; }
public ResultsViewModel ContextMenu { get; private set; }
@ -395,6 +400,7 @@ namespace Flow.Launcher.ViewModel
public bool GameModeStatus { get; set; } = false;
private string _queryText;
public string QueryText
{
get => _queryText;
@ -418,6 +424,7 @@ namespace Flow.Launcher.ViewModel
Settings.WindowSize += 100;
Settings.WindowLeft -= 50;
}
OnPropertyChanged();
}
@ -433,6 +440,7 @@ namespace Flow.Launcher.ViewModel
Settings.WindowLeft += 50;
Settings.WindowSize -= 100;
}
OnPropertyChanged();
}
@ -471,7 +479,7 @@ namespace Flow.Launcher.ViewModel
{
ResultAreaColumn = 1;
PreviewVisible = true;
Results.SelectedItem?.LoadPreviewImage();
ResultsVM.SelectedItem?.LoadPreviewImage();
}
private void HidePreview()
@ -496,7 +504,7 @@ namespace Flow.Launcher.ViewModel
{
if (PreviewVisible)
{
Results.SelectedItem?.LoadPreviewImage();
ResultsVM.SelectedItem?.LoadPreviewImage();
}
}
@ -508,22 +516,21 @@ namespace Flow.Launcher.ViewModel
/// <param name="isReQuery">Force query even when Query Text doesn't change</param>
public void ChangeQueryText(string queryText, bool isReQuery = false)
{
Application.Current.Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
if (QueryText != queryText)
{
// re-query is done in QueryText's setter method
QueryText = queryText;
// set to false so the subsequent set true triggers
// PropertyChanged and MoveQueryTextToEnd is called
QueryTextCursorMovedToEnd = false;
}
else if (isReQuery)
{
Query(isReQuery: true);
}
QueryTextCursorMovedToEnd = true;
});
}
@ -543,13 +550,13 @@ namespace Flow.Launcher.ViewModel
_selectedResults = value;
if (SelectedIsFromQueryResults())
{
ContextMenu.Visibility = Visibility.Collapsed;
History.Visibility = Visibility.Collapsed;
ContextMenu.Visibility = false;
History.Visibility = false;
ChangeQueryText(_queryTextBeforeLeaveResults);
}
else
{
Results.Visibility = Visibility.Collapsed;
ResultsVM.Visibility = false;
_queryTextBeforeLeaveResults = QueryText;
@ -567,12 +574,12 @@ namespace Flow.Launcher.ViewModel
}
}
_selectedResults.Visibility = Visibility.Visible;
_selectedResults.Visibility = true;
}
}
public Visibility ProgressBarVisibility { get; set; }
public Visibility MainWindowVisibility { get; set; }
public bool ProgressBarVisibility { get; set; }
public bool MainWindowVisibility { get; set; }
public double MainWindowOpacity { get; set; } = 1;
// This is to be used for determining the visibility status of the mainwindow instead of MainWindowVisibility
@ -581,7 +588,7 @@ namespace Flow.Launcher.ViewModel
public event VisibilityChangedEventHandler VisibilityChanged;
public Visibility SearchIconVisibility { get; set; }
public bool SearchIconVisibility { get; set; }
public double MainWindowWidth
{
@ -593,8 +600,8 @@ namespace Flow.Launcher.ViewModel
public string OpenResultCommandModifiers => Settings.OpenResultModifiers;
public string PreviewHotkey
{
public string PreviewHotkey
{
get
{
// TODO try to patch issue #1755
@ -608,6 +615,7 @@ namespace Flow.Launcher.ViewModel
{
Settings.PreviewHotkey = "F1";
}
return Settings.PreviewHotkey;
}
}
@ -646,7 +654,7 @@ namespace Flow.Launcher.ViewModel
var query = QueryText.ToLower().Trim();
ContextMenu.Clear();
var selected = Results.SelectedItem?.Result;
var selected = ResultsVM.SelectedItem?.Result;
if (selected != null) // SelectedItem returns null if selection is empty.
{
@ -670,7 +678,6 @@ namespace Flow.Launcher.ViewModel
r.Score = match.Score;
return true;
}).ToList();
ContextMenu.AddResults(filtered, id);
}
@ -697,13 +704,10 @@ namespace Flow.Launcher.ViewModel
Title = string.Format(title, h.Query),
SubTitle = string.Format(time, h.ExecutedDateTime),
IcoPath = "Images\\history.png",
OriginQuery = new Query
{
RawQuery = h.Query
},
OriginQuery = new Query { RawQuery = h.Query },
Action = _ =>
{
SelectedResults = Results;
SelectedResults = ResultsVM;
ChangeQueryText(h.Query);
return false;
}
@ -736,10 +740,10 @@ namespace Flow.Launcher.ViewModel
if (query == null) // shortcut expanded
{
Results.Clear();
Results.Visibility = Visibility.Collapsed;
ResultsVM.Clear();
ResultsVM.Visibility = false;
PluginIconPath = null;
SearchIconVisibility = Visibility.Visible;
SearchIconVisibility = true;
return;
}
@ -750,7 +754,7 @@ namespace Flow.Launcher.ViewModel
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;
ProgressBarVisibility = Visibility.Hidden;
ProgressBarVisibility = false;
_isQueryRunning = true;
// Switch to ThreadPool thread
@ -772,12 +776,12 @@ namespace Flow.Launcher.ViewModel
if (plugins.Count == 1)
{
PluginIconPath = plugins.Single().Metadata.IcoPath;
SearchIconVisibility = Visibility.Hidden;
SearchIconVisibility = false;
}
else
{
PluginIconPath = null;
SearchIconVisibility = Visibility.Visible;
SearchIconVisibility = true;
}
@ -795,7 +799,7 @@ namespace Flow.Launcher.ViewModel
// 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning)
{
ProgressBarVisibility = Visibility.Visible;
ProgressBarVisibility = true;
}
}, currentCancellationToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
@ -827,7 +831,7 @@ namespace Flow.Launcher.ViewModel
if (!currentCancellationToken.IsCancellationRequested)
{
// update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
ProgressBarVisibility = false;
}
// Local function
@ -837,20 +841,23 @@ namespace Flow.Launcher.ViewModel
// Task.Yield will force it to run in ThreadPool
await Task.Yield();
IReadOnlyList<Result> results = await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken);
IReadOnlyList<Result> results =
await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken);
currentCancellationToken.ThrowIfCancellationRequested();
results ??= _emptyResult;
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken)))
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query,
currentCancellationToken)))
{
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
}
}
}
private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts, IEnumerable<BuiltinShortcutModel> builtInShortcuts)
private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts,
IEnumerable<BuiltinShortcutModel> builtInShortcuts)
{
if (string.IsNullOrWhiteSpace(queryText))
{
@ -872,7 +879,7 @@ namespace Flow.Launcher.ViewModel
string customExpanded = queryBuilder.ToString();
Application.Current.Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
foreach (var shortcut in builtInShortcuts)
{
@ -887,7 +894,9 @@ namespace Flow.Launcher.ViewModel
}
catch (Exception e)
{
Log.Exception($"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}", e);
Log.Exception(
$"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}",
e);
}
}
});
@ -904,7 +913,7 @@ namespace Flow.Launcher.ViewModel
{
if (_lastQuery?.ActionKeyword != query?.ActionKeyword)
{
Results.Clear();
ResultsVM.Clear();
}
}
@ -976,7 +985,7 @@ namespace Flow.Launcher.ViewModel
internal bool SelectedIsFromQueryResults()
{
var selected = SelectedResults == Results;
var selected = SelectedResults == ResultsVM;
return selected;
}
@ -1010,9 +1019,9 @@ namespace Flow.Launcher.ViewModel
public void Show()
{
Application.Current.Dispatcher.Invoke(() =>
Dispatcher.UIThread.Invoke(() =>
{
MainWindowVisibility = Visibility.Visible;
MainWindowVisibility = true;
MainWindowOpacity = 1;
@ -1028,8 +1037,9 @@ namespace Flow.Launcher.ViewModel
if (!SelectedIsFromQueryResults())
{
SelectedResults = Results;
SelectedResults = ResultsVM;
}
switch (Settings.LastQueryMode)
{
case LastQueryMode.Empty:
@ -1051,7 +1061,7 @@ namespace Flow.Launcher.ViewModel
}
MainWindowVisibilityStatus = false;
MainWindowVisibility = Visibility.Collapsed;
MainWindowVisibility = false;
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false });
}
@ -1116,7 +1126,7 @@ namespace Flow.Launcher.ViewModel
}
}
Results.AddResults(resultsForUpdates, token);
ResultsVM.AddResults(resultsForUpdates, token);
}
#endregion

View file

@ -25,6 +25,7 @@ namespace Flow.Launcher.ViewModel
{
return;
}
Result = result;
if (Result.Glyph is { FontFamily: not null } glyph)
@ -41,19 +42,14 @@ namespace Flow.Launcher.ViewModel
if (fonts.ContainsKey(fontFamilyPath))
{
Glyph = glyph with
{
FontFamily = fonts[fontFamilyPath]
};
Glyph = glyph with { FontFamily = fonts[fontFamilyPath] };
}
else
{
fontCollection.AddFontFile(fontFamilyPath);
fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}";
Glyph = glyph with
{
FontFamily = fonts[fontFamilyPath]
};
fonts[fontFamilyPath] =
$"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}";
Glyph = glyph with { FontFamily = fonts[fontFamilyPath] };
}
}
else
@ -61,42 +57,40 @@ namespace Flow.Launcher.ViewModel
Glyph = glyph;
}
}
}
private Settings Settings { get; }
public Visibility ShowOpenResultHotkey =>
Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Collapsed;
public bool ShowOpenResultHotkey => Settings.ShowOpenResultHotkey;
public Visibility ShowDefaultPreview => Result.PreviewPanel == null ? Visibility.Visible : Visibility.Collapsed;
public bool ShowDefaultPreview => Result.PreviewPanel != null;
public Visibility ShowCustomizedPreview => Result.PreviewPanel == null ? Visibility.Collapsed : Visibility.Visible;
public bool ShowCustomizedPreview => Result.PreviewPanel != null;
public Visibility ShowIcon
public bool ShowIcon
{
get
{
// If both glyph and image icons are not available, it will then be the default icon
if (!ImgIconAvailable && !GlyphAvailable)
return Visibility.Visible;
return true;
// Although user can choose to use glyph icons, plugins may choose to supply only image icons.
// In this case we ignore the setting because otherwise icons will not display as intended
if (Settings.UseGlyphIcons && !GlyphAvailable && ImgIconAvailable)
return Visibility.Visible;
return true;
return !Settings.UseGlyphIcons && ImgIconAvailable ? Visibility.Visible : Visibility.Hidden;
return !Settings.UseGlyphIcons && ImgIconAvailable;
}
}
public Visibility ShowPreviewImage
public bool ShowPreviewImage
{
get
{
if (PreviewImageAvailable)
{
return Visibility.Visible;
return true;
}
else
{
@ -114,21 +108,21 @@ namespace Flow.Launcher.ViewModel
{
return IconXY / 2;
}
return IconXY;
}
}
public Visibility ShowGlyph
public bool ShowGlyph
{
get
{
// Although user can choose to not use glyph icons, plugins may choose to supply only glyph icons.
// In this case we ignore the setting because otherwise icons will not display as intended
if (!Settings.UseGlyphIcons && !ImgIconAvailable && GlyphAvailable)
return Visibility.Visible;
return true;
return Settings.UseGlyphIcons && GlyphAvailable ? Visibility.Visible : Visibility.Collapsed;
return Settings.UseGlyphIcons && GlyphAvailable;
}
}
@ -136,7 +130,8 @@ namespace Flow.Launcher.ViewModel
private bool ImgIconAvailable => !string.IsNullOrEmpty(Result.IcoPath) || Result.Icon is not null;
private bool PreviewImageAvailable => !string.IsNullOrEmpty(Result.Preview.PreviewImagePath) || Result.Preview.PreviewDelegate != null;
private bool PreviewImageAvailable => !string.IsNullOrEmpty(Result.Preview.PreviewImagePath) ||
Result.Preview.PreviewDelegate != null;
public string OpenResultModifiers => Settings.OpenResultModifiers;
@ -182,7 +177,8 @@ namespace Flow.Launcher.ViewModel
public GlyphInfo Glyph { get; set; }
private async Task<ImageSource> LoadImageInternalAsync(string imagePath, Result.IconDelegate icon, bool loadFullImage)
private async Task<ImageSource> LoadImageInternalAsync(string imagePath, Result.IconDelegate icon,
bool loadFullImage)
{
if (string.IsNullOrEmpty(imagePath) && icon != null)
{
@ -234,9 +230,9 @@ namespace Flow.Launcher.ViewModel
public void LoadPreviewImage()
{
if (ShowDefaultPreview == Visibility.Visible)
if (ShowDefaultPreview)
{
if (!PreviewImageLoaded && ShowPreviewImage == Visibility.Visible)
if (!PreviewImageLoaded && ShowPreviewImage)
{
PreviewImageLoaded = true;
_ = LoadPreviewImageAsync();
@ -245,6 +241,7 @@ namespace Flow.Launcher.ViewModel
}
public Result Result { get; }
public int ResultProgress
{
get

View file

@ -27,6 +27,7 @@ namespace Flow.Launcher.ViewModel
Results = new ResultCollection();
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
}
public ResultsViewModel(Settings settings) : this()
{
_settings = settings;
@ -43,14 +44,14 @@ namespace Flow.Launcher.ViewModel
#region Properties
public double MaxHeight => MaxResults * (double)Application.Current.FindResource("ResultItemHeight")!;
public double MaxHeight => MaxResults * 52;
public int SelectedIndex { get; set; }
public ResultViewModel SelectedItem { get; set; }
public Thickness Margin { get; set; }
public Visibility Visibility { get; set; } = Visibility.Collapsed;
public bool Visibility { get; set; } = true;
public ICommand RightClickResultCommand { get; init; }
public ICommand LeftClickResultCommand { get; init; }
@ -69,6 +70,7 @@ namespace Flow.Launcher.ViewModel
break;
}
}
return index;
}
@ -87,7 +89,6 @@ namespace Flow.Launcher.ViewModel
}
}
#endregion
#region Public Methods
@ -144,6 +145,7 @@ namespace Flow.Launcher.ViewModel
UpdateResults(newResults);
}
/// <summary>
/// To avoid deadlock, this method should not called from main thread
/// </summary>
@ -169,12 +171,12 @@ namespace Flow.Launcher.ViewModel
switch (Visibility)
{
case Visibility.Collapsed when Results.Count > 0:
case false when Results.Count > 0:
SelectedIndex = 0;
Visibility = Visibility.Visible;
Visibility = true;
break;
case Visibility.Visible when Results.Count == 0:
Visibility = Visibility.Collapsed;
case true when Results.Count == 0:
Visibility = false;
break;
}
}
@ -199,28 +201,30 @@ namespace Flow.Launcher.ViewModel
return Results;
return Results.Where(r => r != null && !resultsForUpdates.Any(u => u.ID == r.Result.PluginID))
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
.OrderByDescending(rv => rv.Result.Score)
.ToList();
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
.OrderByDescending(rv => rv.Result.Score)
.ToList();
}
#endregion
#region FormattedText Dependency Property
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(Inline),
typeof(ResultsViewModel),
new PropertyMetadata(null, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
// public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
// "FormattedText",
// typeof(Inline),
// typeof(ResultsViewModel),
// new PropertyMetadata(null, FormattedTextPropertyChanged));
//
// public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
// {
// textBlock.SetValue(FormattedTextProperty, value);
// }
public static Inline GetFormattedText(DependencyObject textBlock)
{
return (Inline)textBlock.GetValue(FormattedTextProperty);
}
// public static Inline GetFormattedText(DependencyObject textBlock)
// {
// return (Inline)textBlock.GetValue(FormattedTextProperty);
// }
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
@ -234,6 +238,7 @@ namespace Flow.Launcher.ViewModel
textBlock.Inlines.Add(inline);
}
#endregion
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
@ -262,6 +267,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++)
@ -270,9 +276,11 @@ namespace Flow.Launcher.ViewModel
if (_token.IsCancellationRequested)
return;
Add(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
}
}
public void RemoveAll(int Capacity = 512)
{
Clear();
@ -307,6 +315,7 @@ namespace Flow.Launcher.ViewModel
{
Capacity = newItems.Count;
}
editTime++;
}
}