From d788de8b9038942df95db6f024255ad61628d4d2 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Thu, 18 Jan 2024 16:45:49 -0600 Subject: [PATCH] first port to avalonia --- .../Resource/Internationalization.cs | 3 + Flow.Launcher.Core/Resource/Theme.cs | 5 + .../UserSettings/Settings.cs | 4 +- Flow.Launcher/App.axaml | 11 + Flow.Launcher/App.axaml.cs | 213 ++++++++ Flow.Launcher/App.xaml | 32 -- Flow.Launcher/App.xaml.cs | 194 ------- Flow.Launcher/Flow.Launcher.csproj | 13 +- Flow.Launcher/Helper/SingleInstance.cs | 40 +- Flow.Launcher/MainWindow.axaml | 454 ++++++++++++++++ ...MainWindow.xaml.cs => MainWindow.axaml.cs} | 467 ++++++++-------- Flow.Launcher/MainWindow.xaml | 501 ------------------ Flow.Launcher/PublicAPIInstance.cs | 7 +- Flow.Launcher/ResultListBox.axaml | 191 +++++++ Flow.Launcher/ResultListBox.axaml.cs | 162 ++++++ Flow.Launcher/ResultListBox.xaml | 255 --------- Flow.Launcher/ResultListBox.xaml.cs | 167 ------ Flow.Launcher/SettingWindow.xaml | 8 +- Flow.Launcher/ViewModel/MainViewModel.cs | 112 ++-- Flow.Launcher/ViewModel/ResultViewModel.cs | 53 +- Flow.Launcher/ViewModel/ResultsViewModel.cs | 59 ++- 21 files changed, 1434 insertions(+), 1517 deletions(-) create mode 100644 Flow.Launcher/App.axaml create mode 100644 Flow.Launcher/App.axaml.cs delete mode 100644 Flow.Launcher/App.xaml delete mode 100644 Flow.Launcher/App.xaml.cs create mode 100644 Flow.Launcher/MainWindow.axaml rename Flow.Launcher/{MainWindow.xaml.cs => MainWindow.axaml.cs} (59%) delete mode 100644 Flow.Launcher/MainWindow.xaml create mode 100644 Flow.Launcher/ResultListBox.axaml create mode 100644 Flow.Launcher/ResultListBox.axaml.cs delete mode 100644 Flow.Launcher/ResultListBox.xaml delete mode 100644 Flow.Launcher/ResultListBox.xaml.cs diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index acc693ed5..4bafa95e1 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -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) { diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 96338cf6a..ac22cd410 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -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); diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index ca1674315..e0a57826d 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -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; } /// /// Custom left position on selected monitor diff --git a/Flow.Launcher/App.axaml b/Flow.Launcher/App.axaml new file mode 100644 index 000000000..d54b69230 --- /dev/null +++ b/Flow.Launcher/App.axaml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/App.axaml.cs b/Flow.Launcher/App.axaml.cs new file mode 100644 index 000000000..534a2563d --- /dev/null +++ b/Flow.Launcher/App.axaml.cs @@ -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.InitializeAsFirstInstance(Unique)) + { + return; + } + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .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(); + } + + /// + /// let exception throw as normal is better for Debug + /// + [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(); + } + } +} diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml deleted file mode 100644 index b8e2a1cfe..000000000 --- a/Flow.Launcher/App.xaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs deleted file mode 100644 index f4a17761f..000000000 --- a/Flow.Launcher/App.xaml.cs +++ /dev/null @@ -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.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(); - } - - /// - /// let exception throw as normal is better for Debug - /// - [Conditional("RELEASE")] - private void RegisterDispatcherUnhandledException() - { - DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException; - } - - /// - /// let exception throw as normal is better for Debug - /// - [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(); - } - } -} diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 15729d9c5..055123b1a 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -4,6 +4,7 @@ WinExe net8.0-windows10.0.19041.0 true + true true Flow.Launcher.App Resources\app.ico @@ -12,6 +13,7 @@ false false en + true @@ -23,7 +25,6 @@ DEBUG;TRACE prompt 4 - true false @@ -42,10 +43,6 @@ - - - - @@ -83,7 +80,13 @@ + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs index 739fed378..cb457857c 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -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(); + } /// /// 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. /// - public static class SingleInstance - where TApplication: Application , ISingleInstanceApp - + public static class SingleInstance + where TApplication : Avalonia.Application, ISingleInstanceApp + { #region Private Fields @@ -230,7 +233,7 @@ namespace Flow.Launcher.Helper /// If not, activates the first instance. /// /// True if this is the first instance of the application. - 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. /// /// List of command line arg strings. - private static IList GetCommandLineArgs( string uniqueApplicationName ) + private static IList 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 diff --git a/Flow.Launcher/MainWindow.axaml b/Flow.Launcher/MainWindow.axaml new file mode 100644 index 000000000..a8af8bf90 --- /dev/null +++ b/Flow.Launcher/MainWindow.axaml @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.axaml.cs similarity index 59% rename from Flow.Launcher/MainWindow.xaml.cs rename to Flow.Launcher/MainWindow.axaml.cs index 7d1a68125..de7f2fe6c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.axaml.cs @@ -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; } /// @@ -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(); } } } diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml deleted file mode 100644 index b65fbc7bb..000000000 --- a/Flow.Launcher/MainWindow.xaml +++ /dev/null @@ -1,501 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 36309a22a..0809c74fd 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -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(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); diff --git a/Flow.Launcher/ResultListBox.axaml b/Flow.Launcher/ResultListBox.axaml new file mode 100644 index 000000000..e047f58dd --- /dev/null +++ b/Flow.Launcher/ResultListBox.axaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/ResultListBox.axaml.cs b/Flow.Launcher/ResultListBox.axaml.cs new file mode 100644 index 000000000..c07dba536 --- /dev/null +++ b/Flow.Launcher/ResultListBox.axaml.cs @@ -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); + // } + } +} diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml deleted file mode 100644 index ba4c9e9a4..000000000 --- a/Flow.Launcher/ResultListBox.xaml +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Flow.Launcher/ResultListBox.xaml.cs b/Flow.Launcher/ResultListBox.xaml.cs deleted file mode 100644 index 78720e86a..000000000 --- a/Flow.Launcher/ResultListBox.xaml.cs +++ /dev/null @@ -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); - } - } -} diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 1e886c022..3a2450431 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -1975,10 +1975,10 @@ - + + + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 61bf0c4dc..5c674a756 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -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 /// Force query even when Query Text doesn't change 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 results = await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); + IReadOnlyList 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 customShortcuts, IEnumerable builtInShortcuts) + private Query ConstructQuery(string queryText, IEnumerable customShortcuts, + IEnumerable 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 diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 3f204c16c..d86a1d990 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -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 LoadImageInternalAsync(string imagePath, Result.IconDelegate icon, bool loadFullImage) + private async Task 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 diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index bb07ce085..cdd781249 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -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); } + /// /// To avoid deadlock, this method should not called from main thread /// @@ -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 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 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, 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 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++; } }