diff --git a/Flow.Launcher/ActionKeywords.xaml b/Flow.Launcher/ActionKeywords.xaml index 5af76f37f39..b575b3859df 100644 --- a/Flow.Launcher/ActionKeywords.xaml +++ b/Flow.Launcher/ActionKeywords.xaml @@ -2,6 +2,7 @@ x:Class="Flow.Launcher.ActionKeywords" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Flow.Launcher.Resources.Controls" Title="{DynamicResource actionKeywordsTitle}" Width="450" Background="{DynamicResource PopuBGColor}" @@ -29,29 +30,16 @@ - + Grid.Column="0" + Grid.ColumnSpan="2" + ShowIcon="False" + ShowTitle="False" + ShowMinimizeButton="False" + ShowMaximizeRestoreButton="False" + ShowCloseButton="True" + CloseButtonClick="BtnCancel_OnClick" /> - - - - - - - - - + - - - - - - - - - + + + - - - - - - - - - - - + + + diff --git a/Flow.Launcher/MessageBoxEx.xaml.cs b/Flow.Launcher/MessageBoxEx.xaml.cs index 907bfb926d5..c50c732396e 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -189,6 +189,8 @@ private void Button_Click(object sender, RoutedEventArgs e) private void Button_Cancel(object sender, RoutedEventArgs e) { + e.Handled = true; + if (_button == MessageBoxButton.YesNo) // Follow System.Windows.MessageBox behavior return; diff --git a/Flow.Launcher/PluginSettingsWindow.xaml b/Flow.Launcher/PluginSettingsWindow.xaml index ac1abd9e2c8..81a012d9e42 100644 --- a/Flow.Launcher/PluginSettingsWindow.xaml +++ b/Flow.Launcher/PluginSettingsWindow.xaml @@ -2,16 +2,14 @@ x:Class="Flow.Launcher.PluginSettingsWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Flow.Launcher.Resources.Controls" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Width="800" Height="Auto" MinWidth="600" MinHeight="300" - Loaded="OnLoaded" ResizeMode="CanResize" SnapsToDevicePixels="True" - StateChanged="Window_StateChanged" - Activated="Window_Activated" UseLayoutRounding="True" WindowStartupLocation="CenterScreen"> @@ -50,131 +48,17 @@ - - - - - - - - - - - - - - + IconSource="/Images/app.png" /> diff --git a/Flow.Launcher/PluginSettingsWindow.xaml.cs b/Flow.Launcher/PluginSettingsWindow.xaml.cs index 3dfad752da0..a58ca7db9dc 100644 --- a/Flow.Launcher/PluginSettingsWindow.xaml.cs +++ b/Flow.Launcher/PluginSettingsWindow.xaml.cs @@ -12,7 +12,6 @@ namespace Flow.Launcher; public partial class PluginSettingsWindow { private readonly Settings _settings; - private WindowState _lastNonMinimizedWindowState = WindowState.Normal; public string PluginId { get; } @@ -59,74 +58,11 @@ private void NumberBox_OnValueChanged(NumberBox sender, NumberBoxValueChangedEve } } - private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState.Minimized; - } - - private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState switch - { - WindowState.Maximized => WindowState.Normal, - _ => WindowState.Maximized - }; - } - - private void OnCloseButtonClick(object sender, RoutedEventArgs e) - { - Close(); - } - private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) { Close(); } - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (WindowState != WindowState.Minimized) - { - _lastNonMinimizedWindowState = WindowState; - } - - RefreshMaximizeRestoreButton(); - } - - private void Window_StateChanged(object sender, EventArgs e) - { - if (WindowState != WindowState.Minimized) - { - _lastNonMinimizedWindowState = WindowState; - } - - RefreshMaximizeRestoreButton(); - } - - private void Window_Activated(object sender, EventArgs e) - { - // Band-aid fix: Rare edge case where Alt+Tab activates the window but doesn't trigger StateChanged - // So we need to restore/maximize it here if it's still minimized - if (WindowState == WindowState.Minimized) - { - WindowState = _lastNonMinimizedWindowState; - } - } - - private void RefreshMaximizeRestoreButton() - { - if (WindowState == WindowState.Maximized) - { - MaximizeButton.Visibility = Visibility.Hidden; - RestoreButton.Visibility = Visibility.Visible; - } - else - { - MaximizeButton.Visibility = Visibility.Visible; - RestoreButton.Visibility = Visibility.Hidden; - } - } - protected override void OnClosed(EventArgs e) { if (!App.LoadingOrExiting) diff --git a/Flow.Launcher/PluginUpdateWindow.xaml b/Flow.Launcher/PluginUpdateWindow.xaml index a4bb0643169..edd23a95298 100644 --- a/Flow.Launcher/PluginUpdateWindow.xaml +++ b/Flow.Launcher/PluginUpdateWindow.xaml @@ -2,6 +2,7 @@ x:Class="Flow.Launcher.PluginUpdateWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Flow.Launcher.Resources.Controls" 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" @@ -30,35 +31,15 @@ - - - - - - - - - + + + - - - - - - - - - - - - - + + + diff --git a/Flow.Launcher/ProgressBoxEx.xaml.cs b/Flow.Launcher/ProgressBoxEx.xaml.cs index 11946334869..49693e0c475 100644 --- a/Flow.Launcher/ProgressBoxEx.xaml.cs +++ b/Flow.Launcher/ProgressBoxEx.xaml.cs @@ -98,14 +98,10 @@ private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e) private void Button_Cancel(object sender, RoutedEventArgs e) { + e.Handled = true; ForceClose(); } - private void Button_Minimize(object sender, RoutedEventArgs e) - { - WindowState = WindowState.Minimized; - } - private void Button_Background(object sender, RoutedEventArgs e) { Hide(); diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml b/Flow.Launcher/ReleaseNotesWindow.xaml index 6072f40f13c..57492f4a6fd 100644 --- a/Flow.Launcher/ReleaseNotesWindow.xaml +++ b/Flow.Launcher/ReleaseNotesWindow.xaml @@ -20,7 +20,6 @@ Foreground="{DynamicResource PopupTextColor}" Loaded="Window_Loaded" ResizeMode="CanResize" - StateChanged="Window_StateChanged" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> @@ -36,145 +35,24 @@ - - - - - - - - - - + - - - - - - - - + Margin="6 0 18 0" + x:Name="SeeMore" + Content="{DynamicResource seeMoreReleaseNotes}" + NavigateUri="{Binding ReleaseNotes}" /> WindowState.Normal, - _ => WindowState.Maximized - }; - } - - private void OnCloseButtonClick(object sender, RoutedEventArgs e) - { - Close(); - } - - private void RefreshMaximizeRestoreButton() - { - if (WindowState == WindowState.Maximized) - { - MaximizeButton.Visibility = Visibility.Hidden; - RestoreButton.Visibility = Visibility.Visible; - } - else - { - MaximizeButton.Visibility = Visibility.Visible; - RestoreButton.Visibility = Visibility.Hidden; - } - } - - private void Window_StateChanged(object sender, EventArgs e) - { - RefreshMaximizeRestoreButton(); - } - - #endregion - #region Control Events private void MarkdownViewer_Loaded(object sender, RoutedEventArgs e) diff --git a/Flow.Launcher/ReportWindow.xaml b/Flow.Launcher/ReportWindow.xaml index 5e799e2a339..0a4e4276470 100644 --- a/Flow.Launcher/ReportWindow.xaml +++ b/Flow.Launcher/ReportWindow.xaml @@ -2,6 +2,7 @@ x:Class="Flow.Launcher.ReportWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Flow.Launcher.Resources.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="{DynamicResource reportWindow_flowlauncher_got_an_error}" @@ -26,48 +27,13 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/CustomWindowTitleBar.xaml.cs b/Flow.Launcher/Resources/Controls/CustomWindowTitleBar.xaml.cs new file mode 100644 index 00000000000..f07eb294c2b --- /dev/null +++ b/Flow.Launcher/Resources/Controls/CustomWindowTitleBar.xaml.cs @@ -0,0 +1,461 @@ +using System; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Flow.Launcher.Resources.Controls +{ + public partial class CustomWindowTitleBar : UserControl + { + private static readonly ImageSource DefaultWindowIcon = CreateDefaultWindowIcon(); + + public sealed class WindowStateChangedEventArgs : EventArgs + { + public WindowStateChangedEventArgs(WindowState previousState, WindowState currentState) + { + PreviousState = previousState; + CurrentState = currentState; + } + + public WindowState PreviousState { get; } + + public WindowState CurrentState { get; } + } + + public static readonly DependencyProperty IconSourceProperty = + DependencyProperty.Register( + name: nameof(IconSource), + propertyType: typeof(ImageSource), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: null) + ); + + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register( + name: nameof(Title), + propertyType: typeof(string), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: null) + ); + + public static readonly DependencyProperty ShowIconProperty = + DependencyProperty.Register( + name: nameof(ShowIcon), + propertyType: typeof(bool), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback: OnButtonVisibilityOptionChanged) + ); + + public static readonly DependencyProperty ShowTitleProperty = + DependencyProperty.Register( + name: nameof(ShowTitle), + propertyType: typeof(bool), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback: OnButtonVisibilityOptionChanged) + ); + + public static readonly DependencyProperty ShowMinimizeButtonProperty = + DependencyProperty.Register( + name: nameof(ShowMinimizeButton), + propertyType: typeof(bool), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback: OnButtonVisibilityOptionChanged) + ); + + public static readonly DependencyProperty ShowMaximizeRestoreButtonProperty = + DependencyProperty.Register( + name: nameof(ShowMaximizeRestoreButton), + propertyType: typeof(bool), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback: OnButtonVisibilityOptionChanged) + ); + + public static readonly DependencyProperty ShowCloseButtonProperty = + DependencyProperty.Register( + name: nameof(ShowCloseButton), + propertyType: typeof(bool), + ownerType: typeof(CustomWindowTitleBar), + typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback: OnButtonVisibilityOptionChanged) + ); + + /// + /// Occurs when the minimize button is clicked. + /// + /// + /// Set to in a subscriber to suppress + /// the control's default minimize behavior. + /// + public event RoutedEventHandler MinimizeButtonClick; + + /// + /// Occurs when the maximize or restore button is clicked. + /// + /// + /// Set to in a subscriber to suppress + /// the control's default maximize/restore toggle behavior. + /// + public event RoutedEventHandler MaximizeRestoreButtonClick; + + /// + /// Occurs when the close button is clicked. + /// + /// + /// Set to in a subscriber to suppress + /// the control's default host-window close behavior. + /// + public event RoutedEventHandler CloseButtonClick; + + /// + /// Occurs when changes. + /// + public event EventHandler LastNonMinimizedWindowStateChanged; + + private Window _hostWindow; + private WindowState _lastNonMinimizedWindowState = WindowState.Normal; + + private Button MinimizeButtonElement => FindName("MinimizeButton") as Button; + private Button MaximizeButtonElement => FindName("MaximizeButton") as Button; + private Button RestoreButtonElement => FindName("RestoreButton") as Button; + private Button CloseButtonElement => FindName("CloseButton") as Button; + private Image IconImageElement => FindName("IconImage") as Image; + private TextBlock TitleTextBlockElement => FindName("TitleTextBlock") as TextBlock; + + /// + /// Initializes a new instance of the class. + /// + public CustomWindowTitleBar() + { + InitializeComponent(); + Loaded += CustomWindowTitleBar_Loaded; + Unloaded += CustomWindowTitleBar_Unloaded; + } + + /// + /// Gets or sets the icon source shown in the title bar. + /// + /// + /// If unset (), the control falls back to the host window icon, then to the default app icon. + /// + public ImageSource IconSource + { + get => (ImageSource)GetValue(IconSourceProperty); + set => SetValue(IconSourceProperty, value); + } + + /// + /// Gets or sets the title text shown in the title bar. + /// + /// + /// If unset (), the control uses the host window title when available. + /// + public string Title + { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + /// + /// Gets or sets a value indicating whether the window icon is visible. + /// + public bool ShowIcon + { + get => (bool)GetValue(ShowIconProperty); + set => SetValue(ShowIconProperty, value); + } + + /// + /// Gets or sets a value indicating whether the title text is visible. + /// + public bool ShowTitle + { + get => (bool)GetValue(ShowTitleProperty); + set => SetValue(ShowTitleProperty, value); + } + + /// + /// Gets or sets a value indicating whether the minimize button is visible. + /// + public bool ShowMinimizeButton + { + get => (bool)GetValue(ShowMinimizeButtonProperty); + set => SetValue(ShowMinimizeButtonProperty, value); + } + + /// + /// Gets or sets a value indicating whether the maximize/restore button is visible. + /// + public bool ShowMaximizeRestoreButton + { + get => (bool)GetValue(ShowMaximizeRestoreButtonProperty); + set => SetValue(ShowMaximizeRestoreButtonProperty, value); + } + + /// + /// Gets or sets a value indicating whether the close button is visible. + /// + public bool ShowCloseButton + { + get => (bool)GetValue(ShowCloseButtonProperty); + set => SetValue(ShowCloseButtonProperty, value); + } + + /// + /// Gets the last observed window state that was not minimized. + /// + public WindowState LastNonMinimizedWindowState { + get => _lastNonMinimizedWindowState; + } + + private void CustomWindowTitleBar_Loaded(object sender, RoutedEventArgs e) + { + AttachToHostWindow(); + UpdateElementsVisibility(); + } + + private static void OnButtonVisibilityOptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomWindowTitleBar control) + { + control.UpdateElementsVisibility(); + } + } + + private void CustomWindowTitleBar_Unloaded(object sender, RoutedEventArgs e) + { + DetachFromHostWindow(); + } + + private void AttachToHostWindow() + { + var window = Window.GetWindow(this); + if (_hostWindow == window) + { + return; + } + + DetachFromHostWindow(); + + _hostWindow = window; + if (_hostWindow == null) + { + return; + } + + if (IconSource is null) + { + IconSource = _hostWindow.Icon ?? DefaultWindowIcon; + } + + if (Title is null && !string.IsNullOrEmpty(_hostWindow.Title)) + { + Title = _hostWindow.Title; + } + + if (_hostWindow.WindowState != WindowState.Minimized) + { + UpdateLastNonMinimizedWindowState(_hostWindow.WindowState); + } + + _hostWindow.StateChanged += HostWindow_StateChanged; + _hostWindow.Activated += HostWindow_Activated; + _hostWindow.Closed += HostWindow_Closed; + } + + private void DetachFromHostWindow() + { + if (_hostWindow == null) + { + return; + } + + _hostWindow.StateChanged -= HostWindow_StateChanged; + _hostWindow.Activated -= HostWindow_Activated; + _hostWindow.Closed -= HostWindow_Closed; + _hostWindow = null; + } + + private void HostWindow_StateChanged(object sender, EventArgs e) + { + if (_hostWindow == null) + { + return; + } + + if (_hostWindow.WindowState != WindowState.Minimized) + { + UpdateLastNonMinimizedWindowState(_hostWindow.WindowState); + } + + UpdateElementsVisibility(); + } + + private void HostWindow_Activated(object sender, EventArgs e) + { + if (_hostWindow == null) + { + return; + } + + // Band-aid fix: Rare edge case where Alt+Tab activates the window but doesn't trigger StateChanged. + if (_hostWindow.WindowState == WindowState.Minimized) + { + _hostWindow.WindowState = _lastNonMinimizedWindowState; + } + } + + private void HostWindow_Closed(object sender, EventArgs e) + { + DetachFromHostWindow(); + } + + private void UpdateLastNonMinimizedWindowState(WindowState state) + { + if (state == WindowState.Minimized || _lastNonMinimizedWindowState == state) + { + return; + } + + var previousState = _lastNonMinimizedWindowState; + _lastNonMinimizedWindowState = state; + LastNonMinimizedWindowStateChanged?.Invoke(this, + new WindowStateChangedEventArgs(previousState, _lastNonMinimizedWindowState) + ); + } + + private void UpdateElementsVisibility() + { + var iconImage = IconImageElement; + if (iconImage != null) + { + iconImage.Visibility = ShowIcon ? Visibility.Visible : Visibility.Collapsed; + } + + var titleTextBlock = TitleTextBlockElement; + if (titleTextBlock != null) + { + titleTextBlock.Visibility = ShowTitle ? Visibility.Visible : Visibility.Collapsed; + } + + var minimizeButton = MinimizeButtonElement; + if (minimizeButton != null) + { + minimizeButton.Visibility = ShowMinimizeButton ? Visibility.Visible : Visibility.Collapsed; + } + + var closeButton = CloseButtonElement; + if (closeButton != null) + { + closeButton.Visibility = ShowCloseButton ? Visibility.Visible : Visibility.Collapsed; + } + + var maximizeButton = MaximizeButtonElement; + var restoreButton = RestoreButtonElement; + if (maximizeButton == null || restoreButton == null) + { + return; + } + + if (!ShowMaximizeRestoreButton) + { + maximizeButton.Visibility = Visibility.Collapsed; + restoreButton.Visibility = Visibility.Collapsed; + return; + } + + if (_hostWindow?.WindowState == WindowState.Maximized) + { + maximizeButton.Visibility = Visibility.Collapsed; + restoreButton.Visibility = Visibility.Visible; + } + else + { + maximizeButton.Visibility = Visibility.Visible; + restoreButton.Visibility = Visibility.Collapsed; + } + } + + private void MinimizeButton_OnClick(object sender, RoutedEventArgs e) + { + if (!ShowMinimizeButton) + { + return; + } + + MinimizeButtonClick?.Invoke(this, e); + + // External handlers can override the built-in behavior by marking the routed event as handled. + if (e.Handled) + { + return; + } + + if (_hostWindow == null) + { + return; + } + + _hostWindow.WindowState = WindowState.Minimized; + } + + private void MaximizeRestoreButton_OnClick(object sender, RoutedEventArgs e) + { + if (!ShowMaximizeRestoreButton) + { + return; + } + + MaximizeRestoreButtonClick?.Invoke(this, e); + + // External handlers can override the built-in behavior by marking the routed event as handled. + if (e.Handled) + { + return; + } + + if (_hostWindow == null) + { + return; + } + + _hostWindow.WindowState = _hostWindow.WindowState switch + { + WindowState.Maximized => WindowState.Normal, + _ => WindowState.Maximized + }; + } + + private void CloseButton_OnClick(object sender, RoutedEventArgs e) + { + if (!ShowCloseButton) + { + return; + } + + CloseButtonClick?.Invoke(this, e); + + // External handlers can override the built-in behavior by marking the routed event as handled. + if (e.Handled) + { + return; + } + + if (_hostWindow == null) + { + return; + } + + _hostWindow.Close(); + } + + private static ImageSource CreateDefaultWindowIcon() + { + var icon = new BitmapImage(); + icon.BeginInit(); + icon.UriSource = new Uri("/Images/app.png", UriKind.Relative); + icon.EndInit(); + icon.Freeze(); + return icon; + } + } +} diff --git a/Flow.Launcher/Resources/CustomControlTemplate.xaml b/Flow.Launcher/Resources/CustomControlTemplate.xaml index a2d1ba13aa1..a1d516a156b 100644 --- a/Flow.Launcher/Resources/CustomControlTemplate.xaml +++ b/Flow.Launcher/Resources/CustomControlTemplate.xaml @@ -169,6 +169,18 @@ + + - - - - - + + - - - - - - - - - + + - - - - - - - - - - - - - - - + LastNonMinimizedWindowStateChanged="OnLastNonMinimizedWindowStateChanged" /> WindowState.Normal, - _ => WindowState.Maximized - }; - } - - private void OnCloseButtonClick(object sender, RoutedEventArgs e) - { - Close(); - } - - private void RefreshMaximizeRestoreButton() - { - if (WindowState == WindowState.Maximized) - { - MaximizeButton.Visibility = Visibility.Hidden; - RestoreButton.Visibility = Visibility.Visible; - } - else - { - MaximizeButton.Visibility = Visibility.Visible; - RestoreButton.Visibility = Visibility.Hidden; - } - } - - #endregion - #region Window Position public void UpdatePositionAndState() diff --git a/Flow.Launcher/WelcomeWindow.xaml b/Flow.Launcher/WelcomeWindow.xaml index a37e38eb833..a00f2dd56e5 100644 --- a/Flow.Launcher/WelcomeWindow.xaml +++ b/Flow.Launcher/WelcomeWindow.xaml @@ -2,6 +2,7 @@ x:Class="Flow.Launcher.WelcomeWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Flow.Launcher.Resources.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Flow.Launcher" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -23,7 +24,6 @@ MouseDown="window_MouseDown" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> - @@ -33,51 +33,12 @@ - - - - - - - - - - - - - + + +