From d363cf8137d5af58448c1e4df5bd3cec0dbf8c90 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 10:42:19 +0800 Subject: [PATCH 01/15] Add property change for settings class & Add localize support for enum --- .../Flow.Launcher.Plugin.Shell/Settings.cs | 123 ++++++++++++++++-- 1 file changed, 114 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 4616a18ec..92db4771e 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -1,24 +1,121 @@ using System.Collections.Generic; +using Flow.Launcher.Localization.Attributes; namespace Flow.Launcher.Plugin.Shell { - public class Settings + public class Settings : BaseModel { - public Shell Shell { get; set; } = Shell.Cmd; + private Shell _shell = Shell.Cmd; + public Shell Shell + { + get => _shell; + set + { + if (_shell != value) + { + _shell = value; + OnPropertyChanged(); + } + } + } - public bool ReplaceWinR { get; set; } = false; + private bool _replaceWinR = false; + public bool ReplaceWinR + { + get => _replaceWinR; + set + { + if (_replaceWinR != value) + { + _replaceWinR = value; + OnPropertyChanged(); + } + } + } - public bool CloseShellAfterPress { get; set; } = false; + private bool _closeShellAfterPress = false; + public bool CloseShellAfterPress + { + get => _closeShellAfterPress; + set + { + if (_closeShellAfterPress != value) + { + _closeShellAfterPress = value; + OnPropertyChanged(); + } + } + } - public bool LeaveShellOpen { get; set; } + private bool _leaveShellOpen; + public bool LeaveShellOpen + { + get => _leaveShellOpen; + set + { + if (_leaveShellOpen != value) + { + _leaveShellOpen = value; + OnPropertyChanged(); + } + } + } - public bool RunAsAdministrator { get; set; } = true; + private bool _runAsAdministrator = true; + public bool RunAsAdministrator + { + get => _runAsAdministrator; + set + { + if (_runAsAdministrator != value) + { + _runAsAdministrator = value; + OnPropertyChanged(); + } + } + } - public bool UseWindowsTerminal { get; set; } = false; + private bool _useWindowsTerminal = false; + public bool UseWindowsTerminal + { + get => _useWindowsTerminal; + set + { + if (_useWindowsTerminal != value) + { + _useWindowsTerminal = value; + OnPropertyChanged(); + } + } + } - public bool ShowOnlyMostUsedCMDs { get; set; } + private bool _showOnlyMostUsedCMDs; + public bool ShowOnlyMostUsedCMDs + { + get => _showOnlyMostUsedCMDs; + set + { + if (_showOnlyMostUsedCMDs != value) + { + _showOnlyMostUsedCMDs = value; + OnPropertyChanged(); + } + } + } - public int ShowOnlyMostUsedCMDsNumber { get; set; } + private int _showOnlyMostUsedCMDsNumber; + public int ShowOnlyMostUsedCMDsNumber + { + get => _showOnlyMostUsedCMDsNumber; + set + { + if (_showOnlyMostUsedCMDsNumber != value) + { + _showOnlyMostUsedCMDsNumber = value; + OnPropertyChanged(); + } + } + } public Dictionary CommandHistory { get; set; } = []; @@ -31,11 +128,19 @@ namespace Flow.Launcher.Plugin.Shell } } + [EnumLocalize] public enum Shell { + [EnumLocalizeValue("CMD")] Cmd = 0, + + [EnumLocalizeValue("PowerShell")] Powershell = 1, + + [EnumLocalizeValue("RunCommand")] RunCommand = 2, + + [EnumLocalizeValue("Pwsh")] Pwsh = 3, } } From 175571a1309ecfcf4e6d10f8d813e137206641e5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 23:00:32 +0800 Subject: [PATCH 02/15] Refactor ShellSettings with Binding logic --- .../CloseShellAfterPressEnabledConverter.cs | 22 +++ .../LeaveShellOpenEnabledConverter.cs | 25 +++ Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 1 + .../ShellSetting.xaml.cs | 142 ------------------ .../ViewModels/ShellSettingViewModel.cs | 78 ++++++++++ .../{ => Views}/ShellSetting.xaml | 53 +++++-- .../Views/ShellSetting.xaml.cs | 16 ++ 7 files changed, 180 insertions(+), 157 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs rename Plugins/Flow.Launcher.Plugin.Shell/{ => Views}/ShellSetting.xaml (55%) create mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs new file mode 100644 index 000000000..a47b58e1e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Flow.Launcher.Plugin.Shell.Converters; + +public class CloseShellAfterPressEnabledConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not bool) + return Binding.DoNothing; + + var leaveShellOpen = (bool)value; + return !leaveShellOpen; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs new file mode 100644 index 000000000..ca938ae7e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Flow.Launcher.Plugin.Shell.Converters; + +public class LeaveShellOpenEnabledConverter : IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if ( + values.Length != 2 || + values[0] is not bool closeShellAfterPress || + values[1] is not Shell shell + ) + return Binding.DoNothing; + + return (!closeShellAfterPress) && shell != Shell.RunCommand; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 2440facd0..6433179f0 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin.Shell.Views; using WindowsInput; using WindowsInput.Native; using Control = System.Windows.Controls.Control; diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs deleted file mode 100644 index 0abc823e0..000000000 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; - -namespace Flow.Launcher.Plugin.Shell -{ - public partial class CMDSetting : UserControl - { - private readonly Settings _settings; - - public CMDSetting(Settings settings) - { - InitializeComponent(); - _settings = settings; - } - - private void CMDSetting_OnLoaded(object sender, RoutedEventArgs re) - { - ReplaceWinR.IsChecked = _settings.ReplaceWinR; - - CloseShellAfterPress.IsChecked = _settings.CloseShellAfterPress; - - LeaveShellOpen.IsChecked = _settings.LeaveShellOpen; - - AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator; - - UseWindowsTerminal.IsChecked = _settings.UseWindowsTerminal; - - LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - - ShowOnlyMostUsedCMDs.IsChecked = _settings.ShowOnlyMostUsedCMDs; - - if (ShowOnlyMostUsedCMDs.IsChecked != true) - ShowOnlyMostUsedCMDsNumber.IsEnabled = false; - - ShowOnlyMostUsedCMDsNumber.ItemsSource = new List() { 5, 10, 20 }; - - if (_settings.ShowOnlyMostUsedCMDsNumber == 0) - { - ShowOnlyMostUsedCMDsNumber.SelectedIndex = 0; - - _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; - } - - CloseShellAfterPress.Checked += (o, e) => - { - _settings.CloseShellAfterPress = true; - LeaveShellOpen.IsChecked = false; - LeaveShellOpen.IsEnabled = false; - }; - - CloseShellAfterPress.Unchecked += (o, e) => - { - _settings.CloseShellAfterPress = false; - LeaveShellOpen.IsEnabled = true; - }; - - LeaveShellOpen.Checked += (o, e) => - { - _settings.LeaveShellOpen = true; - CloseShellAfterPress.IsChecked = false; - CloseShellAfterPress.IsEnabled = false; - }; - - LeaveShellOpen.Unchecked += (o, e) => - { - _settings.LeaveShellOpen = false; - CloseShellAfterPress.IsEnabled = true; - }; - - AlwaysRunAsAdministrator.Checked += (o, e) => - { - _settings.RunAsAdministrator = true; - }; - - AlwaysRunAsAdministrator.Unchecked += (o, e) => - { - _settings.RunAsAdministrator = false; - }; - - UseWindowsTerminal.Checked += (o, e) => - { - _settings.UseWindowsTerminal = true; - }; - - UseWindowsTerminal.Unchecked += (o, e) => - { - _settings.UseWindowsTerminal = false; - }; - - ReplaceWinR.Checked += (o, e) => - { - _settings.ReplaceWinR = true; - }; - - ReplaceWinR.Unchecked += (o, e) => - { - _settings.ReplaceWinR = false; - }; - - ShellComboBox.SelectedIndex = _settings.Shell switch - { - Shell.Cmd => 0, - Shell.Powershell => 1, - Shell.Pwsh => 2, - _ => ShellComboBox.Items.Count - 1 - }; - - ShellComboBox.SelectionChanged += (o, e) => - { - _settings.Shell = ShellComboBox.SelectedIndex switch - { - 0 => Shell.Cmd, - 1 => Shell.Powershell, - 2 => Shell.Pwsh, - _ => Shell.RunCommand - }; - LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; - }; - - ShowOnlyMostUsedCMDs.Checked += (o, e) => - { - _settings.ShowOnlyMostUsedCMDs = true; - - ShowOnlyMostUsedCMDsNumber.IsEnabled = true; - }; - - ShowOnlyMostUsedCMDs.Unchecked += (o, e) => - { - _settings.ShowOnlyMostUsedCMDs = false; - - ShowOnlyMostUsedCMDsNumber.IsEnabled = false; - }; - - ShowOnlyMostUsedCMDsNumber.SelectedItem = _settings.ShowOnlyMostUsedCMDsNumber; - ShowOnlyMostUsedCMDsNumber.SelectionChanged += (o, e) => - { - _settings.ShowOnlyMostUsedCMDsNumber = (int)ShowOnlyMostUsedCMDsNumber.SelectedItem; - }; - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs b/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs new file mode 100644 index 000000000..341fc3868 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/ViewModels/ShellSettingViewModel.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.Shell.ViewModels; + +public class ShellSettingViewModel : BaseModel +{ + public Settings Settings { get; } + + public List AllShells { get; } = ShellLocalized.GetValues(); + + public Shell SelectedShell + { + get => Settings.Shell; + set + { + if (Settings.Shell != value) + { + Settings.Shell = value; + OnPropertyChanged(); + } + } + } + + public List OnlyMostUsedCMDsNumbers { get; } = [5, 10, 20]; + public int SelectedOnlyMostUsedCMDsNumber + { + get => Settings.ShowOnlyMostUsedCMDsNumber; + set + { + if (Settings.ShowOnlyMostUsedCMDsNumber != value) + { + Settings.ShowOnlyMostUsedCMDsNumber = value; + OnPropertyChanged(); + } + } + } + + public bool CloseShellAfterPress + { + get => Settings.CloseShellAfterPress; + set + { + if (Settings.CloseShellAfterPress != value) + { + Settings.CloseShellAfterPress = value; + OnPropertyChanged(); + // Only allow CloseShellAfterPress to be true when LeaveShellOpen is false + if (value) + { + LeaveShellOpen = false; + } + } + } + } + + public bool LeaveShellOpen + { + get => Settings.LeaveShellOpen; + set + { + if (Settings.LeaveShellOpen != value) + { + Settings.LeaveShellOpen = value; + OnPropertyChanged(); + // Only allow LeaveShellOpen to be true when CloseShellAfterPress is false + if (value) + { + CloseShellAfterPress = false; + } + } + } + } + + public ShellSettingViewModel(Settings settings) + { + Settings = settings; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml similarity index 55% rename from Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml rename to Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml index 32f2ad69c..ce52d6c7a 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml @@ -1,13 +1,20 @@  + + + + + @@ -23,50 +30,66 @@ Grid.Row="0" Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" HorizontalAlignment="Left" - Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_relace_winr}" + IsChecked="{Binding Settings.ReplaceWinR, Mode=TwoWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}" + IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}" + IsEnabled="{Binding LeaveShellOpen, Converter={StaticResource CloseShellAfterPressEnabledConverter}, Mode=OneWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_leave_cmd_open}" + IsChecked="{Binding LeaveShellOpen, Mode=TwoWay}"> + + + + + + + + Content="{DynamicResource flowlauncher_plugin_cmd_always_run_as_administrator}" + IsChecked="{Binding Settings.RunAsAdministrator, Mode=TwoWay}" /> + Content="{DynamicResource flowlauncher_plugin_cmd_use_windows_terminal}" + IsChecked="{Binding Settings.UseWindowsTerminal, Mode=TwoWay}" /> - CMD - PowerShell - Pwsh - RunCommand - + HorizontalAlignment="Left" + DisplayMemberPath="Display" + ItemsSource="{Binding AllShells, Mode=OneTime}" + SelectedValue="{Binding SelectedShell, Mode=TwoWay}" + SelectedValuePath="Value" /> + Content="{DynamicResource flowlauncher_plugin_cmd_history}" + IsChecked="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=TwoWay}" /> + HorizontalAlignment="Left" + IsEnabled="{Binding Settings.ShowOnlyMostUsedCMDs, Mode=OneWay}" + ItemsSource="{Binding OnlyMostUsedCMDsNumbers, Mode=OneTime}" + SelectedItem="{Binding SelectedOnlyMostUsedCMDsNumber, Mode=TwoWay}" /> diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs new file mode 100644 index 000000000..1ec9018df --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs @@ -0,0 +1,16 @@ +using System.Windows.Controls; +using Flow.Launcher.Plugin.Shell.ViewModels; + +namespace Flow.Launcher.Plugin.Shell.Views +{ + public partial class CMDSetting : UserControl + { + public CMDSetting(Settings settings) + { + var viewModel = new ShellSettingViewModel(settings); + DataContext = viewModel; + InitializeComponent(); + DataContext = viewModel; + } + } +} From d106c5144b7a5397408d566ca5acf2faf1606ae0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 29 Sep 2025 23:12:02 +0800 Subject: [PATCH 03/15] Fix IsEnabled logic --- .../CloseShellAfterPressEnabledConverter.cs | 22 ------------------- ...OrCloseShellAfterPressEnabledConverter.cs} | 6 ++--- .../Views/ShellSetting.xaml | 15 ++++++++----- 3 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs rename Plugins/Flow.Launcher.Plugin.Shell/Converters/{LeaveShellOpenEnabledConverter.cs => LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs} (68%) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs deleted file mode 100644 index a47b58e1e..000000000 --- a/Plugins/Flow.Launcher.Plugin.Shell/Converters/CloseShellAfterPressEnabledConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace Flow.Launcher.Plugin.Shell.Converters; - -public class CloseShellAfterPressEnabledConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not bool) - return Binding.DoNothing; - - var leaveShellOpen = (bool)value; - return !leaveShellOpen; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs similarity index 68% rename from Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs rename to Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs index ca938ae7e..5649353e5 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenEnabledConverter.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Converters/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs @@ -4,18 +4,18 @@ using System.Windows.Data; namespace Flow.Launcher.Plugin.Shell.Converters; -public class LeaveShellOpenEnabledConverter : IMultiValueConverter +public class LeaveShellOpenOrCloseShellAfterPressEnabledConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if ( values.Length != 2 || - values[0] is not bool closeShellAfterPress || + values[0] is not bool closeShellAfterPressOrLeaveShellOpen || values[1] is not Shell shell ) return Binding.DoNothing; - return (!closeShellAfterPress) && shell != Shell.RunCommand; + return (!closeShellAfterPressOrLeaveShellOpen) && shell != Shell.RunCommand; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml index ce52d6c7a..5f66884ea 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml @@ -11,8 +11,7 @@ d:DesignWidth="300" mc:Ignorable="d"> - - + @@ -38,8 +37,14 @@ Margin="{StaticResource SettingPanelItemRightTopBottomMargin}" HorizontalAlignment="Left" Content="{DynamicResource flowlauncher_plugin_cmd_close_cmd_after_press}" - IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}" - IsEnabled="{Binding LeaveShellOpen, Converter={StaticResource CloseShellAfterPressEnabledConverter}, Mode=OneWay}" /> + IsChecked="{Binding CloseShellAfterPress, Mode=TwoWay}"> + + + + + + + - + From 89505fce307c1ab7b5ad8dacfe69f97656a7f5e4 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Tue, 30 Sep 2025 09:37:48 +0800 Subject: [PATCH 04/15] Remove unnecessary DataContext Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs index 1ec9018df..c656ea070 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Views/ShellSetting.xaml.cs @@ -10,7 +10,6 @@ namespace Flow.Launcher.Plugin.Shell.Views var viewModel = new ShellSettingViewModel(settings); DataContext = viewModel; InitializeComponent(); - DataContext = viewModel; } } } From aab213a6b975fb3d223a482729f99541e3e5b8f1 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Tue, 30 Sep 2025 09:38:18 +0800 Subject: [PATCH 05/15] Add default value for ShowOnlyMostUsedCMDsNumber Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Shell/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 92db4771e..79a906534 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -103,7 +103,7 @@ namespace Flow.Launcher.Plugin.Shell } } - private int _showOnlyMostUsedCMDsNumber; + private int _showOnlyMostUsedCMDsNumber = 5; public int ShowOnlyMostUsedCMDsNumber { get => _showOnlyMostUsedCMDsNumber; From 9be546d6c72d152ecba61cbd4f9ee542abfdf0cd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 09:50:29 +0800 Subject: [PATCH 06/15] Fix ShowOnlyMostUsedCMDsNumber default value --- Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 6433179f0..e89ec376c 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -384,9 +384,15 @@ namespace Flow.Launcher.Plugin.Shell Context = context; _settings = context.API.LoadSettingJsonStorage(); context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); + // Since the old Settings class set default value of ShowOnlyMostUsedCMDsNumber to 0 which is a wrong value, + // we need to fix it here to make sure the default value is 5 + if (_settings.ShowOnlyMostUsedCMDsNumber == 0) + { + _settings.ShowOnlyMostUsedCMDsNumber = 5; + } } - bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) + private bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) { if (!Context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR) { From d56d85b702a35eafb29b72dfcf2e91f6c0d3045e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 19:41:27 +0800 Subject: [PATCH 07/15] Add lock for sound & Rename variable --- Flow.Launcher/MainWindow.xaml.cs | 52 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index c4ed73a0d..a9e03bc8c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Media; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -61,8 +62,9 @@ namespace Flow.Launcher private bool _isArrowKeyPressed = false; // Window Sound Effects - private MediaPlayer animationSoundWMP; - private SoundPlayer animationSoundWPF; + private MediaPlayer _animationSoundWMP; + private SoundPlayer _animationSoundWPF; + private readonly Lock _soundLock = new(); // Window WndProc private HwndSource _hwndSource; @@ -687,31 +689,37 @@ namespace Flow.Launcher private void InitSoundEffects() { - if (_settings.WMPInstalled) + lock (_soundLock) { - animationSoundWMP?.Close(); - animationSoundWMP = new MediaPlayer(); - animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); - } - else - { - animationSoundWPF?.Dispose(); - animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); - animationSoundWPF.Load(); + if (_settings.WMPInstalled) + { + _animationSoundWMP?.Close(); + _animationSoundWMP = new MediaPlayer(); + _animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); + } + else + { + _animationSoundWPF?.Dispose(); + _animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); + _animationSoundWPF.Load(); + } } } private void SoundPlay() { - if (_settings.WMPInstalled) + lock (_soundLock) { - animationSoundWMP.Position = TimeSpan.Zero; - animationSoundWMP.Volume = _settings.SoundVolume / 100.0; - animationSoundWMP.Play(); - } - else - { - animationSoundWPF.Play(); + if (_settings.WMPInstalled) + { + _animationSoundWMP.Position = TimeSpan.Zero; + _animationSoundWMP.Volume = _settings.SoundVolume / 100.0; + _animationSoundWMP.Play(); + } + else + { + _animationSoundWPF.Play(); + } } } @@ -1436,8 +1444,8 @@ namespace Flow.Launcher { _hwndSource?.Dispose(); _notifyIcon?.Dispose(); - animationSoundWMP?.Close(); - animationSoundWPF?.Dispose(); + _animationSoundWMP?.Close(); + _animationSoundWPF?.Dispose(); _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; } From 652ec40d82da67d4db7a2da0c7502f7df1d05fae Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 30 Sep 2025 22:28:41 +1000 Subject: [PATCH 08/15] add removal todo comment --- Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index e89ec376c..25303e9d5 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -386,6 +386,7 @@ namespace Flow.Launcher.Plugin.Shell context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); // Since the old Settings class set default value of ShowOnlyMostUsedCMDsNumber to 0 which is a wrong value, // we need to fix it here to make sure the default value is 5 + // todo: remove this code block after release v2.2.0 if (_settings.ShowOnlyMostUsedCMDsNumber == 0) { _settings.ShowOnlyMostUsedCMDsNumber = 5; From f239866c6811792f60da0fd243ffd003a1e609c4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 21:11:03 +0800 Subject: [PATCH 09/15] Use sleep mode listener to fix modern standby sleep mode issue --- .../NativeMethods.txt | 7 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 104 +++++++++++++++++- Flow.Launcher/MainWindow.xaml.cs | 53 +++++++-- 3 files changed, 150 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index eb844dd7c..cd072f635 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -85,5 +85,10 @@ QueryFullProcessImageName EVENT_OBJECT_HIDE EVENT_SYSTEM_DIALOGEND +DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS WM_POWERBROADCAST -PBT_APMRESUMEAUTOMATIC \ No newline at end of file +PBT_APMRESUMEAUTOMATIC +PBT_APMRESUMESUSPEND +PowerRegisterSuspendResumeNotification +PowerUnregisterSuspendResumeNotification +DeviceNotifyCallbackRoutine \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 5d30b740d..c94008b03 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -19,6 +19,7 @@ using Microsoft.Win32.SafeHandles; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; +using Windows.Win32.System.Power; using Windows.Win32.System.Threading; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.Shell.Common; @@ -338,9 +339,6 @@ namespace Flow.Launcher.Infrastructure public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE; public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE; - public const int WM_POWERBROADCAST = (int)PInvoke.WM_POWERBROADCAST; - public const int PBT_APMRESUMEAUTOMATIC = (int)PInvoke.PBT_APMRESUMEAUTOMATIC; - #endregion #region Window Handle @@ -918,5 +916,105 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region Sleep Mode Listener + + private static Action _func; + private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null; + private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient; + private static SafeHandle _recipientHandle; + private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null; + + /// + /// Registers a listener for sleep mode events. + /// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit + /// https://blog.csdn.net/mochounv/article/details/114668594 + /// + /// + /// + public static unsafe void RegisterSleepModeListener(Action func) + { + if (_callback != null) + { + // Only register if not already registered + return; + } + + _func = func; + _callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback); + _recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS() + { + Callback = _callback, + Context = null + }; + + _recipientHandle = new StructSafeHandle(_recipient); + _handle = PInvoke.PowerRegisterSuspendResumeNotification( + REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK, + _recipientHandle, + out var handle) == WIN32_ERROR.ERROR_SUCCESS ? + new HPOWERNOTIFY(new IntPtr(handle)) : + HPOWERNOTIFY.Null; + if (_handle.IsNull) + { + throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error()); + } + } + + /// + /// Unregisters the sleep mode listener. + /// + public static void UnregisterSleepModeListener() + { + if (!_handle.IsNull) + { + PInvoke.PowerUnregisterSuspendResumeNotification(_handle); + _handle = HPOWERNOTIFY.Null; + _func = null; + _callback = null; + _recipientHandle = null; + } + } + + private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting) + { + switch (type) + { + case PInvoke.PBT_APMRESUMEAUTOMATIC: + // Operation is resuming automatically from a low-power state.This message is sent every time the system resumes + _func(); + break; + + case PInvoke.PBT_APMRESUMESUSPEND: + // Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key + _func(); + break; + } + + return 0; + } + + private sealed class StructSafeHandle : SafeHandle where T : struct + { + private readonly nint _ptr = nint.Zero; + + public StructSafeHandle(T recipient) : base(nint.Zero, true) + { + var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(recipient, pRecipient, false); + SetHandle(pRecipient); + _ptr = pRecipient; + } + + public override bool IsInvalid => handle == nint.Zero; + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(_ptr); + return true; + } + } + + #endregion } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index a9e03bc8c..01a7dc9bd 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -95,6 +95,7 @@ namespace Flow.Launcher UpdatePosition(); InitSoundEffects(); + RegisterSoundEffectsEvent(); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); _viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged; } @@ -668,16 +669,6 @@ namespace Flow.Launcher handled = true; } break; - case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages - // https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast - if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC) - { - // Fix for sound not playing after sleep / hibernate - // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps - InitSoundEffects(); - } - handled = true; - break; } return IntPtr.Zero; @@ -723,6 +714,47 @@ namespace Flow.Launcher } } + private void RegisterSoundEffectsEvent() + { + // Fix for sound not playing after sleep / hibernate for both modern standby and legacy standby + // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps + try + { + Win32Helper.RegisterSleepModeListener(() => + { + if (Application.Current == null) + { + return; + } + + // We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.Invoke(InitSoundEffects); + return; + } + + InitSoundEffects(); + }); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to register sound effect event", e); + } + } + + private static void UnregisterSoundEffectsEvent() + { + try + { + Win32Helper.UnregisterSleepModeListener(); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to unregister sound effect event", e); + } + } + #endregion #region Window Notify Icon @@ -1447,6 +1479,7 @@ namespace Flow.Launcher _animationSoundWMP?.Close(); _animationSoundWPF?.Dispose(); _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; + UnregisterSoundEffectsEvent(); } _disposed = true; From c27817eaf0f7c58bf8aba58afb1856a6e7d34a28 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 21:20:00 +0800 Subject: [PATCH 10/15] Fix possible null exception --- Flow.Launcher.Infrastructure/Win32Helper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index c94008b03..8a41e12b4 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -982,12 +982,12 @@ namespace Flow.Launcher.Infrastructure { case PInvoke.PBT_APMRESUMEAUTOMATIC: // Operation is resuming automatically from a low-power state.This message is sent every time the system resumes - _func(); + _func?.Invoke(); break; case PInvoke.PBT_APMRESUMESUSPEND: // Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key - _func(); + _func?.Invoke(); break; } From e376da44820f3ac69602cce1f5e66c5c920af1b2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 19:29:46 +0800 Subject: [PATCH 11/15] Save settings before shutdown/restart to prevent data loss Added a call to `Context.API.SaveAppAllSettings()` before executing system shutdown, restart, or advanced restart operations. This ensures that any unsaved settings are persisted, reducing the risk of data loss during these actions. --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 89067d44c..d0eb339fe 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -211,6 +211,9 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_shutdown_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); + // Save settings before shutdown to avoid data loss + Context.API.SaveAppAllSettings(); + if (result == MessageBoxResult.Yes) if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_SHUTDOWN | EXIT_WINDOWS_FLAGS.EWX_POWEROFF, REASON); @@ -232,6 +235,9 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); + // Save settings before restart to avoid data loss + Context.API.SaveAppAllSettings(); + if (result == MessageBoxResult.Yes) if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT, REASON); @@ -253,6 +259,9 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); + // Save settings before restart to avoid data loss + Context.API.SaveAppAllSettings(); + if (result == MessageBoxResult.Yes) if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT | EXIT_WINDOWS_FLAGS.EWX_BOOTOPTIONS, REASON); From 167570559f82c8c7fa5bf51a5b33a0ca78c7c26d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 19:42:24 +0800 Subject: [PATCH 12/15] Move settings save to post-confirmation for actions Previously, `Context.API.SaveAppAllSettings()` was called unconditionally before user confirmation for shutdown, restart, and advanced restart actions. This change ensures settings are only saved if the user confirms the action by clicking "Yes" in the confirmation dialog. For all three functionalities: - Moved the settings save call inside the `if (result == MessageBoxResult.Yes)` block. - Retained the existing logic for executing the respective system commands, with checks for `EnableShutdownPrivilege()` to determine whether to use `PInvoke.ExitWindowsEx` or the `shutdown` command. This change prevents unnecessary settings saves when the user cancels the action. --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index d0eb339fe..b53c0261b 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -210,16 +210,16 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_dlgtext_shutdown_computer(), Localize.flowlauncher_plugin_sys_shutdown_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); - - // Save settings before shutdown to avoid data loss - Context.API.SaveAppAllSettings(); - if (result == MessageBoxResult.Yes) + { + // Save settings before shutdown to avoid data loss + Context.API.SaveAppAllSettings(); + if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_SHUTDOWN | EXIT_WINDOWS_FLAGS.EWX_POWEROFF, REASON); else Process.Start("shutdown", "/s /t 0"); - + } return true; } }, @@ -234,16 +234,16 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_dlgtext_restart_computer(), Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); - - // Save settings before restart to avoid data loss - Context.API.SaveAppAllSettings(); - if (result == MessageBoxResult.Yes) + { + // Save settings before restart to avoid data loss + Context.API.SaveAppAllSettings(); + if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT, REASON); else Process.Start("shutdown", "/r /t 0"); - + } return true; } }, @@ -258,16 +258,16 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_dlgtext_restart_computer_advanced(), Localize.flowlauncher_plugin_sys_restart_computer(), MessageBoxButton.YesNo, MessageBoxImage.Warning); - - // Save settings before restart to avoid data loss - Context.API.SaveAppAllSettings(); - if (result == MessageBoxResult.Yes) + { + // Save settings before restart to avoid data loss + Context.API.SaveAppAllSettings(); + if (EnableShutdownPrivilege()) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT | EXIT_WINDOWS_FLAGS.EWX_BOOTOPTIONS, REASON); else Process.Start("shutdown", "/r /o /t 0"); - + } return true; } }, From d08ee30a7a17c23f481302dad350ff7c042eb42e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 19:43:31 +0800 Subject: [PATCH 13/15] Refactor plugin actions to simplify logic Removed logoff operation logic and associated return statement. Eliminated return statement after recycle bin error handling. Removed async plugin data reload and success message logic. Simplified theme selector query handling by removing `return false`. These changes streamline the code and improve maintainability. --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index b53c0261b..cb3acf77f 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -282,10 +282,8 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_dlgtext_logoff_computer(), Localize.flowlauncher_plugin_sys_log_off(), MessageBoxButton.YesNo, MessageBoxImage.Warning); - if (result == MessageBoxResult.Yes) PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_LOGOFF, REASON); - return true; } }, @@ -351,7 +349,6 @@ namespace Flow.Launcher.Plugin.Sys Localize.flowlauncher_plugin_sys_dlgtitle_error(), MessageBoxButton.OK, MessageBoxImage.Error); } - return true; } }, @@ -425,13 +422,11 @@ namespace Flow.Launcher.Plugin.Sys { // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen. Context.API.HideMainWindow(); - _ = Context.API.ReloadAllPluginData().ContinueWith(_ => Context.API.ShowMsg( Localize.flowlauncher_plugin_sys_dlgtitle_success(), Localize.flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded()), TaskScheduler.Current); - return true; } }, @@ -511,7 +506,6 @@ namespace Flow.Launcher.Plugin.Sys else { Context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}"); - } return false; } From 5b0a30774e711fececa2088316657cc9338e01c8 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 2 Oct 2025 19:45:08 +0800 Subject: [PATCH 14/15] Fix code comment typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index cb3acf77f..57b9749f7 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -260,7 +260,7 @@ namespace Flow.Launcher.Plugin.Sys MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) { - // Save settings before restart to avoid data loss + // Save settings before advanced restart to avoid data loss Context.API.SaveAppAllSettings(); if (EnableShutdownPrivilege()) From 5ae159de5b69950df4f82b413ba91f4860f43f35 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Sun, 5 Oct 2025 18:44:40 +0800 Subject: [PATCH 15/15] Move to iNKORE.UI.WPF.Modern UI Framework (#3593) --- .../Resource/LocalizedDescriptionAttribute.cs | 24 - Flow.Launcher.Core/Resource/Theme.cs | 14 +- Flow.Launcher/App.xaml | 18 +- Flow.Launcher/App.xaml.cs | 4 + .../BoolToIMEConversionModeConverter.cs | 4 +- .../Converters/CornerRadiusFilterConverter.cs | 91 + .../Converters/PlacementRectangleConverter.cs | 32 + .../Converters/SharedSizeGroupConverter.cs | 19 + .../Converters/StringToKeyBindingConverter.cs | 2 +- Flow.Launcher/Flow.Launcher.csproj | 4 +- Flow.Launcher/Helper/BorderHelper.cs | 33 + Flow.Launcher/HotkeyControlDialog.xaml | 2 +- Flow.Launcher/HotkeyControlDialog.xaml.cs | 2 +- Flow.Launcher/MainWindow.xaml | 2 +- Flow.Launcher/MainWindow.xaml.cs | 7 +- Flow.Launcher/PluginUpdateWindow.xaml | 5 +- Flow.Launcher/PublicAPIInstance.cs | 2 +- Flow.Launcher/ReleaseNotesWindow.xaml | 24 +- Flow.Launcher/ReleaseNotesWindow.xaml.cs | 13 +- Flow.Launcher/Resources/Controls/Card.xaml | 139 - Flow.Launcher/Resources/Controls/Card.xaml.cs | 67 - .../Resources/Controls/CardGroup.xaml | 32 - .../Resources/Controls/CardGroup.xaml.cs | 47 - .../Controls/CardGroupCardStyleSelector.cs | 21 - .../Controls/CustomScrollViewerEx.cs | 253 ++ Flow.Launcher/Resources/Controls/ExCard.xaml | 312 -- .../Resources/Controls/ExCard.xaml.cs | 57 - .../Resources/Controls/HyperLink.xaml | 14 - .../Resources/Controls/HyperLink.xaml.cs | 39 - Flow.Launcher/Resources/Controls/InfoBar.xaml | 81 - .../Resources/Controls/InfoBar.xaml.cs | 222 - .../Controls/InstalledPluginDisplay.xaml | 4 +- .../Controls/InstalledPluginDisplay.xaml.cs | 2 +- .../Resources/CustomControlTemplate.xaml | 3811 +++-------------- Flow.Launcher/Resources/Dark.xaml | 1549 +------ Flow.Launcher/Resources/Light.xaml | 1552 +------ .../Resources/Pages/WelcomePage1.xaml | 11 +- .../Resources/Pages/WelcomePage2.xaml | 17 +- .../Resources/Pages/WelcomePage3.xaml | 153 +- .../Resources/Pages/WelcomePage4.xaml | 9 +- .../Resources/Pages/WelcomePage5.xaml | 13 +- .../Resources/SettingWindowStyle.xaml | 435 +- Flow.Launcher/ResultListBox.xaml | 1 + Flow.Launcher/SelectBrowserWindow.xaml | 2 +- Flow.Launcher/SelectFileManagerWindow.xaml | 6 +- Flow.Launcher/SelectFileManagerWindow.xaml.cs | 6 - .../ViewModels/SettingsPaneAboutViewModel.cs | 6 + .../SettingsPanePluginsViewModel.cs | 2 +- .../ViewModels/SettingsPaneThemeViewModel.cs | 13 +- .../SettingPages/Views/SettingsPaneAbout.xaml | 180 +- .../Views/SettingsPaneAbout.xaml.cs | 6 - .../Views/SettingsPaneGeneral.xaml | 725 ++-- .../Views/SettingsPaneHotkey.xaml | 736 ++-- .../Views/SettingsPanePluginStore.xaml | 102 +- .../Views/SettingsPanePlugins.xaml | 55 +- .../SettingPages/Views/SettingsPaneProxy.xaml | 77 +- .../SettingPages/Views/SettingsPaneTheme.xaml | 666 +-- Flow.Launcher/SettingWindow.xaml | 5 +- Flow.Launcher/SettingWindow.xaml.cs | 9 +- Flow.Launcher/Themes/Base.xaml | 23 +- Flow.Launcher/Themes/BlurWhite.xaml | 2 +- Flow.Launcher/Themes/Circle System.xaml | 2 +- Flow.Launcher/Themes/Cyan Dark.xaml | 12 +- Flow.Launcher/Themes/Dracula.xaml | 6 +- Flow.Launcher/Themes/Gray.xaml | 8 +- Flow.Launcher/Themes/Sublime.xaml | 6 +- Flow.Launcher/Themes/Win10System.xaml | 2 +- Flow.Launcher/Themes/Win11Light.xaml | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- Flow.Launcher/WelcomeWindow.xaml | 7 +- Flow.Launcher/WelcomeWindow.xaml.cs | 2 +- Flow.Launcher/packages.lock.json | 20 +- .../Views/ExplorerSettings.xaml | 15 +- .../ProgramSuffixes.xaml | 2 +- .../SettingsControl.xaml | 42 +- .../SettingsControl.xaml.cs | 23 + README.md | 14 +- 77 files changed, 2846 insertions(+), 9083 deletions(-) delete mode 100644 Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs create mode 100644 Flow.Launcher/Converters/CornerRadiusFilterConverter.cs create mode 100644 Flow.Launcher/Converters/PlacementRectangleConverter.cs create mode 100644 Flow.Launcher/Converters/SharedSizeGroupConverter.cs create mode 100644 Flow.Launcher/Helper/BorderHelper.cs delete mode 100644 Flow.Launcher/Resources/Controls/Card.xaml delete mode 100644 Flow.Launcher/Resources/Controls/Card.xaml.cs delete mode 100644 Flow.Launcher/Resources/Controls/CardGroup.xaml delete mode 100644 Flow.Launcher/Resources/Controls/CardGroup.xaml.cs delete mode 100644 Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs create mode 100644 Flow.Launcher/Resources/Controls/CustomScrollViewerEx.cs delete mode 100644 Flow.Launcher/Resources/Controls/ExCard.xaml delete mode 100644 Flow.Launcher/Resources/Controls/ExCard.xaml.cs delete mode 100644 Flow.Launcher/Resources/Controls/HyperLink.xaml delete mode 100644 Flow.Launcher/Resources/Controls/HyperLink.xaml.cs delete mode 100644 Flow.Launcher/Resources/Controls/InfoBar.xaml delete mode 100644 Flow.Launcher/Resources/Controls/InfoBar.xaml.cs diff --git a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs deleted file mode 100644 index acd9d9eb7..000000000 --- a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel; - -namespace Flow.Launcher.Core.Resource -{ - public class LocalizedDescriptionAttribute : DescriptionAttribute - { - private readonly string _resourceKey; - - public LocalizedDescriptionAttribute(string resourceKey) - { - _resourceKey = resourceKey; - } - - public override string Description - { - get - { - string description = PublicApi.Instance.GetTranslation(_resourceKey); - return string.IsNullOrWhiteSpace(description) ? - string.Format("[[{0}]]", _resourceKey) : description; - } - } - } -} diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index d1f7da2a2..c3bb6190f 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -449,9 +449,19 @@ namespace Flow.Launcher.Core.Resource } return false; } - catch (XamlParseException) + catch (XamlParseException e) { - _api.LogError(ClassName, $"Theme <{theme}> fail to parse"); + _api.LogException(ClassName, $"Theme <{theme}> fail to parse xaml", e); + if (theme != Constant.DefaultTheme) + { + _api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme)); + ChangeTheme(Constant.DefaultTheme); + } + return false; + } + catch (Exception e) + { + _api.LogException(ClassName, $"Theme <{theme}> fail to load", e); if (theme != Constant.DefaultTheme) { _api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme)); diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index 565bbe3c7..e922cd558 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -2,7 +2,8 @@ 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" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" ShutdownMode="OnMainWindowClose" Startup="OnStartup"> @@ -10,17 +11,17 @@ - + - + - + @@ -33,6 +34,15 @@ + + + 2 + 0 + 0 + 40 + 0 + 36 + \ No newline at end of file diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 58f8438d2..1ca3ce2c6 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -22,6 +22,7 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.ViewModel; +using iNKORE.UI.WPF.Modern.Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.Threading; @@ -56,6 +57,9 @@ namespace Flow.Launcher public App() { + // Do not use bitmap cache since it can cause WPF second window freezing issue + ShadowAssist.UseBitmapCache = false; + // Initialize settings _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); diff --git a/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs b/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs index 41e879913..82da6d936 100644 --- a/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs +++ b/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs @@ -5,7 +5,7 @@ using System.Windows.Input; namespace Flow.Launcher.Converters; -internal class BoolToIMEConversionModeConverter : IValueConverter +public class BoolToIMEConversionModeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { @@ -22,7 +22,7 @@ internal class BoolToIMEConversionModeConverter : IValueConverter } } -internal class BoolToIMEStateConverter : IValueConverter +public class BoolToIMEStateConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/Flow.Launcher/Converters/CornerRadiusFilterConverter.cs b/Flow.Launcher/Converters/CornerRadiusFilterConverter.cs new file mode 100644 index 000000000..fd43cafac --- /dev/null +++ b/Flow.Launcher/Converters/CornerRadiusFilterConverter.cs @@ -0,0 +1,91 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Flow.Launcher.Converters; + +public class CornerRadiusFilterConverter : DependencyObject, IValueConverter +{ + public CornerRadiusFilterKind Filter { get; set; } + + public double Scale { get; set; } = 1.0; + + public static CornerRadius Convert(CornerRadius radius, CornerRadiusFilterKind filterKind) + { + CornerRadius result = radius; + + switch (filterKind) + { + case CornerRadiusFilterKind.Top: + result.BottomLeft = 0; + result.BottomRight = 0; + break; + case CornerRadiusFilterKind.Right: + result.TopLeft = 0; + result.BottomLeft = 0; + break; + case CornerRadiusFilterKind.Bottom: + result.TopLeft = 0; + result.TopRight = 0; + break; + case CornerRadiusFilterKind.Left: + result.TopRight = 0; + result.BottomRight = 0; + break; + } + + return result; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var cornerRadius = (CornerRadius)value; + + var scale = Scale; + if (!double.IsNaN(scale)) + { + cornerRadius.TopLeft *= scale; + cornerRadius.TopRight *= scale; + cornerRadius.BottomRight *= scale; + cornerRadius.BottomLeft *= scale; + } + + var filterType = Filter; + if (filterType == CornerRadiusFilterKind.TopLeftValue || + filterType == CornerRadiusFilterKind.BottomRightValue) + { + return GetDoubleValue(cornerRadius, filterType); + } + + return Convert(cornerRadius, filterType); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + private static double GetDoubleValue(CornerRadius radius, CornerRadiusFilterKind filterKind) + { + switch (filterKind) + { + case CornerRadiusFilterKind.TopLeftValue: + return radius.TopLeft; + case CornerRadiusFilterKind.BottomRightValue: + return radius.BottomRight; + } + return 0; + } +} + +public enum CornerRadiusFilterKind +{ + None, + Top, + Right, + Bottom, + Left, + TopLeftValue, + BottomRightValue +} diff --git a/Flow.Launcher/Converters/PlacementRectangleConverter.cs b/Flow.Launcher/Converters/PlacementRectangleConverter.cs new file mode 100644 index 000000000..130d04e16 --- /dev/null +++ b/Flow.Launcher/Converters/PlacementRectangleConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Flow.Launcher.Converters; + +public class PlacementRectangleConverter : IMultiValueConverter +{ + public Thickness Margin { get; set; } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length == 2 && + values[0] is double width && + values[1] is double height) + { + var margin = Margin; + var topLeft = new Point(margin.Left, margin.Top); + var bottomRight = new Point(width - margin.Right, height - margin.Bottom); + var rect = new Rect(topLeft, bottomRight); + return rect; + } + + return Rect.Empty; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Flow.Launcher/Converters/SharedSizeGroupConverter.cs b/Flow.Launcher/Converters/SharedSizeGroupConverter.cs new file mode 100644 index 000000000..594787027 --- /dev/null +++ b/Flow.Launcher/Converters/SharedSizeGroupConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Flow.Launcher.Converters; + +public class SharedSizeGroupConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (Visibility)value != Visibility.Collapsed ? (string)parameter : null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Flow.Launcher/Converters/StringToKeyBindingConverter.cs b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs index 21bf584e7..b7bca41c5 100644 --- a/Flow.Launcher/Converters/StringToKeyBindingConverter.cs +++ b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs @@ -5,7 +5,7 @@ using System.Windows.Input; namespace Flow.Launcher.Converters; -class StringToKeyBindingConverter : IValueConverter +public class StringToKeyBindingConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index aa8e95429..8c7670426 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -138,6 +138,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -146,9 +147,6 @@ - - - all diff --git a/Flow.Launcher/Helper/BorderHelper.cs b/Flow.Launcher/Helper/BorderHelper.cs new file mode 100644 index 000000000..0f2a78e7d --- /dev/null +++ b/Flow.Launcher/Helper/BorderHelper.cs @@ -0,0 +1,33 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Flow.Launcher.Helper; + +public static class BorderHelper +{ + #region Child + + public static readonly DependencyProperty ChildProperty = + DependencyProperty.RegisterAttached( + "Child", + typeof(UIElement), + typeof(BorderHelper), + new PropertyMetadata(default(UIElement), OnChildChanged)); + + public static UIElement GetChild(Border border) + { + return (UIElement)border.GetValue(ChildProperty); + } + + public static void SetChild(Border border, UIElement value) + { + border.SetValue(ChildProperty, value); + } + + private static void OnChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Border)d).Child = (UIElement)e.NewValue; + } + + #endregion +} diff --git a/Flow.Launcher/HotkeyControlDialog.xaml b/Flow.Launcher/HotkeyControlDialog.xaml index d416f1bdc..9fdfda865 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml +++ b/Flow.Launcher/HotkeyControlDialog.xaml @@ -2,7 +2,7 @@ x:Class="Flow.Launcher.HotkeyControlDialog" 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" + xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Background="{DynamicResource PopuBGColor}" BorderBrush="{DynamicResource PopupButtonAreaBorderColor}" BorderThickness="0 1 0 0" diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index 740425f8b..e1fc86f95 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -9,7 +9,7 @@ using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using ModernWpf.Controls; +using iNKORE.UI.WPF.Modern.Controls; namespace Flow.Launcher; diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 132ec8389..dd47f9d4e 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -6,7 +6,7 @@ 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:ui="http://schemas.modernwpf.com/2019" + xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" Name="FlowMainWindow" Title="Flow Launcher" diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 01a7dc9bd..b2ba33269 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -25,7 +25,8 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.ViewModel; -using ModernWpf.Controls; +using iNKORE.UI.WPF.Modern; +using iNKORE.UI.WPF.Modern.Controls; using DataObject = System.Windows.DataObject; using Key = System.Windows.Input.Key; using MouseButtons = System.Windows.Forms.MouseButtons; @@ -191,11 +192,11 @@ namespace Flow.Launcher // Initialize color scheme if (_settings.ColorScheme == Constant.Light) { - ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Light; + ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light; } else if (_settings.ColorScheme == Constant.Dark) { - ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Dark; + ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark; } // Initialize position diff --git a/Flow.Launcher/PluginUpdateWindow.xaml b/Flow.Launcher/PluginUpdateWindow.xaml index 04cd1f7bc..a4bb06431 100644 --- a/Flow.Launcher/PluginUpdateWindow.xaml +++ b/Flow.Launcher/PluginUpdateWindow.xaml @@ -4,6 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:flowlauncher="clr-namespace:Flow.Launcher" + xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Title="{DynamicResource updateAllPluginsButtonContent}" Width="530" Background="{DynamicResource PopuBGColor}" @@ -66,13 +67,13 @@ Text="{DynamicResource updateAllPluginsButtonContent}" TextAlignment="Left" /> - - + - + @@ -161,18 +162,23 @@ Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" - Margin="18 0 18 0"> - + Margin="6 0 18 0"> + - + Height="500" + Margin="15 0 0 0" + Padding="0 0 15 0" + HorizontalAlignment="Stretch"> @@ -193,11 +199,11 @@ VerticalScrollBarVisibility="Disabled" Visibility="Collapsed" /> - + - + Properties.Settings.Default.GithubRepo + "/releases"; public ReleaseNotesWindow() { InitializeComponent(); - SeeMore.Uri = ReleaseNotes; - ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged; + ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged; } #region Window Events - private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args) + private void ThemeManager_ActualApplicationThemeChanged(ThemeManager sender, object args) { Application.Current.Dispatcher.Invoke(() => { - if (ModernWpf.ThemeManager.Current.ActualApplicationTheme == ModernWpf.ApplicationTheme.Light) + if (ThemeManager.Current.ActualApplicationTheme == ApplicationTheme.Light) { MarkdownViewer.MarkdownStyle = (Style)Application.Current.Resources["DocumentStyleGithubLikeLight"]; MarkdownViewer.Foreground = Brushes.Black; @@ -58,7 +58,7 @@ namespace Flow.Launcher private void Window_Closed(object sender, EventArgs e) { - ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged -= ThemeManager_ActualApplicationThemeChanged; + ThemeManager.Current.ActualApplicationThemeChanged -= ThemeManager_ActualApplicationThemeChanged; } #endregion @@ -147,7 +147,6 @@ namespace Flow.Launcher private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) { MarkdownScrollViewer.Height = e.NewSize.Height; - MarkdownScrollViewer.Width = e.NewSize.Width; } private void MarkdownViewer_MouseWheel(object sender, MouseWheelEventArgs e) diff --git a/Flow.Launcher/Resources/Controls/Card.xaml b/Flow.Launcher/Resources/Controls/Card.xaml deleted file mode 100644 index e3c5f8194..000000000 --- a/Flow.Launcher/Resources/Controls/Card.xaml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Flow.Launcher/Resources/Controls/Card.xaml.cs b/Flow.Launcher/Resources/Controls/Card.xaml.cs deleted file mode 100644 index 6a70dded2..000000000 --- a/Flow.Launcher/Resources/Controls/Card.xaml.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Windows; -using UserControl = System.Windows.Controls.UserControl; - -namespace Flow.Launcher.Resources.Controls -{ - public partial class Card : UserControl - { - public enum CardType - { - Default, - Inside, - InsideFit, - First, - Middle, - Last - } - - public Card() - { - InitializeComponent(); - } - - public string Title - { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register(nameof(Title), typeof(string), typeof(Card), new PropertyMetadata(string.Empty)); - - public string Sub - { - get { return (string)GetValue(SubProperty); } - set { SetValue(SubProperty, value); } - } - public static readonly DependencyProperty SubProperty = - DependencyProperty.Register(nameof(Sub), typeof(string), typeof(Card), new PropertyMetadata(string.Empty)); - - public string Icon - { - get { return (string)GetValue(IconProperty); } - set { SetValue(IconProperty, value); } - } - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register(nameof(Icon), typeof(string), typeof(Card), new PropertyMetadata(string.Empty)); - - /// - /// Gets or sets additional content for the UserControl - /// - public object AdditionalContent - { - get { return (object)GetValue(AdditionalContentProperty); } - set { SetValue(AdditionalContentProperty, value); } - } - public static readonly DependencyProperty AdditionalContentProperty = - DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(Card), - new PropertyMetadata(null)); - public CardType Type - { - get { return (CardType)GetValue(TypeProperty); } - set { SetValue(TypeProperty, value); } - } - public static readonly DependencyProperty TypeProperty = - DependencyProperty.Register(nameof(Type), typeof(CardType), typeof(Card), - new PropertyMetadata(CardType.Default)); - } -} diff --git a/Flow.Launcher/Resources/Controls/CardGroup.xaml b/Flow.Launcher/Resources/Controls/CardGroup.xaml deleted file mode 100644 index f48bf4b6c..000000000 --- a/Flow.Launcher/Resources/Controls/CardGroup.xaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - diff --git a/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs b/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs deleted file mode 100644 index b9588275c..000000000 --- a/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Windows; -using System.Windows.Controls; - -namespace Flow.Launcher.Resources.Controls; - -public partial class CardGroup : UserControl -{ - public enum CardGroupPosition - { - NotInGroup, - First, - Middle, - Last - } - - public new ObservableCollection Content - { - get { return (ObservableCollection)GetValue(ContentProperty); } - set { SetValue(ContentProperty, value); } - } - - public static new readonly DependencyProperty ContentProperty = - DependencyProperty.Register(nameof(Content), typeof(ObservableCollection), typeof(CardGroup)); - - public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached( - "Position", typeof(CardGroupPosition), typeof(CardGroup), - new FrameworkPropertyMetadata(CardGroupPosition.NotInGroup, FrameworkPropertyMetadataOptions.AffectsRender) - ); - - public static void SetPosition(UIElement element, CardGroupPosition value) - { - element.SetValue(PositionProperty, value); - } - - public static CardGroupPosition GetPosition(UIElement element) - { - return (CardGroupPosition)element.GetValue(PositionProperty); - } - - public CardGroup() - { - InitializeComponent(); - Content = new ObservableCollection(); - } -} diff --git a/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs b/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs deleted file mode 100644 index 605934e80..000000000 --- a/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Flow.Launcher.Resources.Controls; - -public class CardGroupCardStyleSelector : StyleSelector -{ - public Style FirstStyle { get; set; } - public Style MiddleStyle { get; set; } - public Style LastStyle { get; set; } - - public override Style SelectStyle(object item, DependencyObject container) - { - var itemsControl = ItemsControl.ItemsControlFromItemContainer(container); - var index = itemsControl.ItemContainerGenerator.IndexFromContainer(container); - - if (index == 0) return FirstStyle; - if (index == itemsControl.Items.Count - 1) return LastStyle; - return MiddleStyle; - } -} diff --git a/Flow.Launcher/Resources/Controls/CustomScrollViewerEx.cs b/Flow.Launcher/Resources/Controls/CustomScrollViewerEx.cs new file mode 100644 index 000000000..78985108c --- /dev/null +++ b/Flow.Launcher/Resources/Controls/CustomScrollViewerEx.cs @@ -0,0 +1,253 @@ +using iNKORE.UI.WPF.Modern.Controls; +using iNKORE.UI.WPF.Modern.Controls.Helpers; +using iNKORE.UI.WPF.Modern.Controls.Primitives; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Flow.Launcher.Resources.Controls +{ + // TODO: Use IsScrollAnimationEnabled property in future: https://github.com/iNKORE-NET/UI.WPF.Modern/pull/347 + public class CustomScrollViewerEx : ScrollViewer + { + private double LastVerticalLocation = 0; + private double LastHorizontalLocation = 0; + + public CustomScrollViewerEx() + { + Loaded += OnLoaded; + var valueSource = DependencyPropertyHelper.GetValueSource(this, AutoPanningMode.IsEnabledProperty).BaseValueSource; + if (valueSource == BaseValueSource.Default) + { + AutoPanningMode.SetIsEnabled(this, true); + } + } + + #region Orientation + + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register( + nameof(Orientation), + typeof(Orientation), + typeof(CustomScrollViewerEx), + new PropertyMetadata(Orientation.Vertical)); + + public Orientation Orientation + { + get => (Orientation)GetValue(OrientationProperty); + set => SetValue(OrientationProperty, value); + } + + #endregion + + #region AutoHideScrollBars + + public static readonly DependencyProperty AutoHideScrollBarsProperty = + ScrollViewerHelper.AutoHideScrollBarsProperty + .AddOwner( + typeof(CustomScrollViewerEx), + new PropertyMetadata(true, OnAutoHideScrollBarsChanged)); + + public bool AutoHideScrollBars + { + get => (bool)GetValue(AutoHideScrollBarsProperty); + set => SetValue(AutoHideScrollBarsProperty, value); + } + + private static void OnAutoHideScrollBarsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomScrollViewerEx sv) + { + sv.UpdateVisualState(); + } + } + + #endregion + + private void OnLoaded(object sender, RoutedEventArgs e) + { + LastVerticalLocation = VerticalOffset; + LastHorizontalLocation = HorizontalOffset; + UpdateVisualState(false); + } + + /// + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + if (Style == null && ReadLocalValue(StyleProperty) == DependencyProperty.UnsetValue) + { + SetResourceReference(StyleProperty, typeof(ScrollViewer)); + } + } + + /// + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + var Direction = GetDirection(); + ScrollViewerBehavior.SetIsAnimating(this, true); + + if (Direction == Orientation.Vertical) + { + if (ScrollableHeight > 0) + { + e.Handled = true; + } + + var WheelChange = e.Delta * (ViewportHeight / 1.5) / ActualHeight; + var newOffset = LastVerticalLocation - WheelChange; + + if (newOffset < 0) + { + newOffset = 0; + } + + if (newOffset > ScrollableHeight) + { + newOffset = ScrollableHeight; + } + + if (newOffset == LastVerticalLocation) + { + return; + } + + ScrollToVerticalOffset(LastVerticalLocation); + + ScrollToValue(newOffset, Direction); + LastVerticalLocation = newOffset; + } + else + { + if (ScrollableWidth > 0) + { + e.Handled = true; + } + + var WheelChange = e.Delta * (ViewportWidth / 1.5) / ActualWidth; + var newOffset = LastHorizontalLocation - WheelChange; + + if (newOffset < 0) + { + newOffset = 0; + } + + if (newOffset > ScrollableWidth) + { + newOffset = ScrollableWidth; + } + + if (newOffset == LastHorizontalLocation) + { + return; + } + + ScrollToHorizontalOffset(LastHorizontalLocation); + + ScrollToValue(newOffset, Direction); + LastHorizontalLocation = newOffset; + } + } + + /// + protected override void OnScrollChanged(ScrollChangedEventArgs e) + { + base.OnScrollChanged(e); + if (!ScrollViewerBehavior.GetIsAnimating(this)) + { + LastVerticalLocation = VerticalOffset; + LastHorizontalLocation = HorizontalOffset; + } + } + + private Orientation GetDirection() + { + var isShiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); + + if (Orientation == Orientation.Horizontal) + { + return isShiftDown ? Orientation.Vertical : Orientation.Horizontal; + } + else + { + return isShiftDown ? Orientation.Horizontal : Orientation.Vertical; + } + } + + /// + /// Causes the to load a new view into the viewport using the specified offsets and zoom factor. + /// + /// A value between 0 and that specifies the distance the content should be scrolled horizontally. + /// A value between 0 and that specifies the distance the content should be scrolled vertically. + /// A value between MinZoomFactor and MaxZoomFactor that specifies the required target ZoomFactor. + /// if the view is changed; otherwise, . + public bool ChangeView(double? horizontalOffset, double? verticalOffset, float? zoomFactor) + { + return ChangeView(horizontalOffset, verticalOffset, zoomFactor, false); + } + + /// + /// Causes the to load a new view into the viewport using the specified offsets and zoom factor, and optionally disables scrolling animation. + /// + /// A value between 0 and that specifies the distance the content should be scrolled horizontally. + /// A value between 0 and that specifies the distance the content should be scrolled vertically. + /// A value between MinZoomFactor and MaxZoomFactor that specifies the required target ZoomFactor. + /// to disable zoom/pan animations while changing the view; otherwise, . The default is false. + /// if the view is changed; otherwise, . + public bool ChangeView(double? horizontalOffset, double? verticalOffset, float? zoomFactor, bool disableAnimation) + { + if (disableAnimation) + { + if (horizontalOffset.HasValue) + { + ScrollToHorizontalOffset(horizontalOffset.Value); + } + + if (verticalOffset.HasValue) + { + ScrollToVerticalOffset(verticalOffset.Value); + } + } + else + { + if (horizontalOffset.HasValue) + { + ScrollToHorizontalOffset(LastHorizontalLocation); + ScrollToValue(Math.Min(ScrollableWidth, horizontalOffset.Value), Orientation.Horizontal); + LastHorizontalLocation = horizontalOffset.Value; + } + + if (verticalOffset.HasValue) + { + ScrollToVerticalOffset(LastVerticalLocation); + ScrollToValue(Math.Min(ScrollableHeight, verticalOffset.Value), Orientation.Vertical); + LastVerticalLocation = verticalOffset.Value; + } + } + + return true; + } + + private void ScrollToValue(double value, Orientation Direction) + { + if (Direction == Orientation.Vertical) + { + ScrollToVerticalOffset(value); + } + else + { + ScrollToHorizontalOffset(value); + } + + ScrollViewerBehavior.SetIsAnimating(this, false); + } + + private void UpdateVisualState(bool useTransitions = true) + { + var stateName = AutoHideScrollBars ? "NoIndicator" : "MouseIndicator"; + VisualStateManager.GoToState(this, stateName, useTransitions); + } + } +} diff --git a/Flow.Launcher/Resources/Controls/ExCard.xaml b/Flow.Launcher/Resources/Controls/ExCard.xaml deleted file mode 100644 index a70c0f4ea..000000000 --- a/Flow.Launcher/Resources/Controls/ExCard.xaml +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Flow.Launcher/Resources/Controls/ExCard.xaml.cs b/Flow.Launcher/Resources/Controls/ExCard.xaml.cs deleted file mode 100644 index f149951f0..000000000 --- a/Flow.Launcher/Resources/Controls/ExCard.xaml.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Flow.Launcher.Resources.Controls -{ - public partial class ExCard : UserControl - { - public ExCard() - { - InitializeComponent(); - } - public string Title - { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register(nameof(Title), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty)); - - public string Sub - { - get { return (string)GetValue(SubProperty); } - set { SetValue(SubProperty, value); } - } - public static readonly DependencyProperty SubProperty = - DependencyProperty.Register(nameof(Sub), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty)); - - public string Icon - { - get { return (string)GetValue(IconProperty); } - set { SetValue(IconProperty, value); } - } - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register(nameof(Icon), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty)); - - /// - /// Gets or sets additional content for the UserControl - /// - public object AdditionalContent - { - get { return (object)GetValue(AdditionalContentProperty); } - set { SetValue(AdditionalContentProperty, value); } - } - public static readonly DependencyProperty AdditionalContentProperty = - DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(ExCard), - new PropertyMetadata(null)); - - public object SideContent - { - get { return (object)GetValue(SideContentProperty); } - set { SetValue(SideContentProperty, value); } - } - public static readonly DependencyProperty SideContentProperty = - DependencyProperty.Register(nameof(SideContent), typeof(object), typeof(ExCard), - new PropertyMetadata(null)); - } -} diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml b/Flow.Launcher/Resources/Controls/HyperLink.xaml deleted file mode 100644 index 9ea550afd..000000000 --- a/Flow.Launcher/Resources/Controls/HyperLink.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs deleted file mode 100644 index 855cccdbd..000000000 --- a/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Navigation; - -namespace Flow.Launcher.Resources.Controls; - -public partial class HyperLink : UserControl -{ - public static readonly DependencyProperty UriProperty = DependencyProperty.Register( - nameof(Uri), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) - ); - - public string Uri - { - get => (string)GetValue(UriProperty); - set => SetValue(UriProperty, value); - } - - public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - nameof(Text), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) - ); - - public string Text - { - get => (string)GetValue(TextProperty); - set => SetValue(TextProperty, value); - } - - public HyperLink() - { - InitializeComponent(); - } - - private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - App.API.OpenUrl(e.Uri); - e.Handled = true; - } -} diff --git a/Flow.Launcher/Resources/Controls/InfoBar.xaml b/Flow.Launcher/Resources/Controls/InfoBar.xaml deleted file mode 100644 index 2ddcbdd0c..000000000 --- a/Flow.Launcher/Resources/Controls/InfoBar.xaml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + +