Merge pull request #3396 from Flow-Launcher/placeholder_resize

Support Placeholder Text & Toggle Resize Mode
This commit is contained in:
Jack Ye 2025-03-29 16:57:36 +08:00 committed by GitHub
commit a3ef6fa177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 252 additions and 66 deletions

View file

@ -24,7 +24,7 @@ namespace Flow.Launcher.Core.Resource
{
#region Properties & Fields
public bool BlurEnabled { get; set; }
public bool BlurEnabled { get; private set; }
private const string ThemeMetadataNamePrefix = "Name:";
private const string ThemeMetadataIsDarkPrefix = "IsDark:";
@ -42,6 +42,8 @@ namespace Flow.Launcher.Core.Resource
private static string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
private static string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
private Thickness _themeResizeBorderThickness;
#endregion
#region Constructor
@ -463,7 +465,7 @@ namespace Flow.Launcher.Core.Resource
var effectSetter = new Setter
{
Property = Border.EffectProperty,
Property = UIElement.EffectProperty,
Value = new DropShadowEffect
{
Opacity = 0.3,
@ -473,12 +475,12 @@ namespace Flow.Launcher.Core.Resource
}
};
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is not Setter marginSetter)
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is not Setter marginSetter)
{
var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin);
marginSetter = new Setter()
{
Property = Border.MarginProperty,
Property = FrameworkElement.MarginProperty,
Value = margin,
};
windowBorderStyle.Setters.Add(marginSetter);
@ -508,12 +510,12 @@ namespace Flow.Launcher.Core.Resource
var dict = GetCurrentResourceDictionary();
var windowBorderStyle = dict["WindowBorderStyle"] as Style;
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) is Setter effectSetter)
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == UIElement.EffectProperty) is Setter effectSetter)
{
windowBorderStyle.Setters.Remove(effectSetter);
}
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is Setter marginSetter)
if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == FrameworkElement.MarginProperty) is Setter marginSetter)
{
var currentMargin = (Thickness)marginSetter.Value;
var newMargin = new Thickness(
@ -529,28 +531,41 @@ namespace Flow.Launcher.Core.Resource
UpdateResourceDictionary(dict);
}
public void SetResizeBorderThickness(WindowChrome windowChrome, bool fixedWindowSize)
{
if (fixedWindowSize)
{
windowChrome.ResizeBorderThickness = new Thickness(0);
}
else
{
windowChrome.ResizeBorderThickness = _themeResizeBorderThickness;
}
}
// because adding drop shadow effect will change the margin of the window,
// we need to update the window chrome thickness to correct set the resize border
private static void SetResizeBoarderThickness(Thickness? effectMargin)
private void SetResizeBoarderThickness(Thickness? effectMargin)
{
var window = Application.Current.MainWindow;
if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome)
{
Thickness thickness;
// Save the theme resize border thickness so that we can restore it if we change ResizeWindow setting
if (effectMargin == null)
{
thickness = SystemParameters.WindowResizeBorderThickness;
_themeResizeBorderThickness = SystemParameters.WindowResizeBorderThickness;
}
else
{
thickness = new Thickness(
_themeResizeBorderThickness = new Thickness(
effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left,
effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top,
effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right,
effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom);
}
windowChrome.ResizeBorderThickness = thickness;
// Apply the resize border thickness to the window chrome
SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
}
}
@ -582,7 +597,7 @@ namespace Flow.Launcher.Core.Resource
{
AutoDropShadow(useDropShadowEffect);
}
}, DispatcherPriority.Normal);
}, DispatcherPriority.Render);
}
/// <summary>
@ -596,7 +611,7 @@ namespace Flow.Launcher.Core.Resource
var (backdropType, _) = GetActualValue();
SetBlurForWindow(GetCurrentTheme(), backdropType);
}, DispatcherPriority.Normal);
}, DispatcherPriority.Render);
}
/// <summary>

View file

@ -1,12 +0,0 @@
using System;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource
{
[Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Theme>() instead.")]
public class ThemeManager
{
public static Theme Instance
=> Ioc.Default.GetRequiredService<Theme>();
}
}

View file

@ -68,11 +68,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
get => _theme;
set
{
if (value == _theme)
return;
_theme = value;
OnPropertyChanged();
OnPropertyChanged(nameof(MaxResultsToShow));
if (value != _theme)
{
_theme = value;
OnPropertyChanged();
OnPropertyChanged(nameof(MaxResultsToShow));
}
}
}
public bool UseDropShadowEffect { get; set; } = true;
@ -113,6 +114,33 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double? SettingWindowLeft { get; set; } = null;
public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal;
bool _showPlaceholder { get; set; } = false;
public bool ShowPlaceholder
{
get => _showPlaceholder;
set
{
if (_showPlaceholder != value)
{
_showPlaceholder = value;
OnPropertyChanged();
}
}
}
string _placeholderText { get; set; } = string.Empty;
public string PlaceholderText
{
get => _placeholderText;
set
{
if (_placeholderText != value)
{
_placeholderText = value;
OnPropertyChanged();
}
}
}
public int CustomExplorerIndex { get; set; } = 0;
[JsonIgnore]
@ -241,10 +269,26 @@ namespace Flow.Launcher.Infrastructure.UserSettings
/// </summary>
public double CustomWindowTop { get; set; } = 0;
public bool KeepMaxResults { get; set; } = false;
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
/// <summary>
/// Fixed window size
/// </summary>
private bool _keepMaxResults { get; set; } = false;
public bool KeepMaxResults
{
get => _keepMaxResults;
set
{
if (_keepMaxResults != value)
{
_keepMaxResults = value;
OnPropertyChanged();
}
}
}
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();

View file

@ -177,7 +177,6 @@ namespace Flow.Launcher
HotKeyMapper.Initialize();
// main windows needs initialized before theme change because of blur settings
// TODO: Clean ThemeManager.Instance in future
Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

View file

@ -39,7 +39,7 @@
<system:String x:Key="GameMode">Game Mode</system:String>
<system:String x:Key="GameModeToolTip">Suspend the use of Hotkeys.</system:String>
<system:String x:Key="PositionReset">Position Reset</system:String>
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String>
<system:String x:Key="queryTextBoxPlaceholder">Type here to search</system:String>
<!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Settings</system:String>
@ -72,8 +72,6 @@
<system:String x:Key="LastQueryEmpty">Empty last Query</system:String>
<system:String x:Key="LastQueryActionKeywordPreserved">Preserve Last Action Keyword</system:String>
<system:String x:Key="LastQueryActionKeywordSelected">Select Last Action Keyword</system:String>
<system:String x:Key="KeepMaxResults">Fixed Window Height</system:String>
<system:String x:Key="KeepMaxResultsToolTip">The window height is not adjustable by dragging.</system:String>
<system:String x:Key="maxShowResults">Maximum results shown</system:String>
<system:String x:Key="maxShowResultsToolTip">You can also quickly adjust this by using CTRL+Plus and CTRL+Minus.</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignore hotkeys in fullscreen mode</system:String>
@ -104,11 +102,6 @@
<system:String x:Key="AlwaysPreview">Always Preview</system:String>
<system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String>
<system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
<system:String x:Key="BackdropType">Backdrop Type</system:String>
<system:String x:Key="BackdropTypesNone">None</system:String>
<system:String x:Key="BackdropTypesAcrylic">Acrylic</system:String>
<system:String x:Key="BackdropTypesMica">Mica</system:String>
<system:String x:Key="BackdropTypesMicaAlt">Mica Alt</system:String>
<!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String>
@ -200,8 +193,19 @@
<system:String x:Key="AnimationSpeedCustom">Custom</system:String>
<system:String x:Key="Clock">Clock</system:String>
<system:String x:Key="Date">Date</system:String>
<system:String x:Key="BackdropType">Backdrop Type</system:String>
<system:String x:Key="BackdropTypesNone">None</system:String>
<system:String x:Key="BackdropTypesAcrylic">Acrylic</system:String>
<system:String x:Key="BackdropTypesMica">Mica</system:String>
<system:String x:Key="BackdropTypesMicaAlt">Mica Alt</system:String>
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
<system:String x:Key="ShowPlaceholder">Show placeholder</system:String>
<system:String x:Key="ShowPlaceholderTip">Display placeholder when query is empty</system:String>
<system:String x:Key="PlaceholderText">Placeholder text</system:String>
<system:String x:Key="PlaceholderTextTip">Change placeholder text. Input empty will use: {0}</system:String>
<system:String x:Key="KeepMaxResults">Fixed Window Size</system:String>
<system:String x:Key="KeepMaxResultsToolTip">The window size is not adjustable by dragging.</system:String>
<!-- Setting Hotkey -->
<system:String x:Key="hotkey">Hotkey</system:String>

View file

@ -218,6 +218,14 @@
<Grid x:Name="QueryBoxArea">
<Border MinHeight="30" Style="{DynamicResource QueryBoxBgStyle}">
<Grid>
<TextBox
x:Name="QueryTextPlaceholderBox"
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="False"
Style="{DynamicResource QuerySuggestionBoxStyle}"
Text="{Binding PlaceholderText, Mode=OneWay}"
Visibility="Collapsed" />
<TextBox
x:Name="QueryTextSuggestionBox"
Height="{Binding MainWindowHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

View file

@ -11,6 +11,7 @@ using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Shell;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
@ -115,6 +116,10 @@ namespace Flow.Launcher
welcomeWindow.Show();
}
// Initialize place holder
SetupPlaceholderText();
_viewModel.PlaceholderText = _settings.PlaceholderText;
// Hide window if need
UpdatePosition();
if (_settings.HideOnStartup)
@ -146,13 +151,17 @@ namespace Flow.Launcher
UpdatePosition();
// Refresh frame
await Ioc.Default.GetRequiredService<Theme>().RefreshFrameAsync();
await _theme.RefreshFrameAsync();
// Initialize resize mode after refreshing frame
SetupResizeMode();
// Reset preview
_viewModel.ResetPreview();
// Since the default main window visibility is visible, so we need set focus during startup
QueryTextBox.Focus();
// Set the initial state of the QueryTextBoxCursorMovedToEnd property
// Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
_viewModel.QueryTextCursorMovedToEnd = false;
@ -237,6 +246,15 @@ namespace Flow.Launcher
case nameof(Settings.WindowTop):
Top = _settings.WindowTop;
break;
case nameof(Settings.ShowPlaceholder):
SetupPlaceholderText();
break;
case nameof(Settings.PlaceholderText):
_viewModel.PlaceholderText = _settings.PlaceholderText;
break;
case nameof(Settings.KeepMaxResults):
SetupResizeMode();
break;
}
};
@ -437,23 +455,25 @@ namespace Flow.Launcher
{
_initialWidth = (int)Width;
_initialHeight = (int)Height;
handled = true;
}
else if (msg == Win32Helper.WM_EXITSIZEMOVE)
{
if (_initialHeight != (int)Height)
{
var shadowMargin = 0;
var (_, useDropShadowEffect) = _theme.GetActualValue();
if (useDropShadowEffect)
{
shadowMargin = 32;
}
if (!_settings.KeepMaxResults)
{
var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize;
// Get shadow margin
var shadowMargin = 0;
var (_, useDropShadowEffect) = _theme.GetActualValue();
if (useDropShadowEffect)
{
shadowMargin = 32;
}
// Calculate max results to show
var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize;
if (itemCount < 2)
{
_settings.MaxResultsToShow = 2;
@ -465,11 +485,16 @@ namespace Flow.Launcher
}
SizeToContent = SizeToContent.Height;
_viewModel.MainWindowWidth = Width;
}
if (_initialWidth != (int)Width)
{
if (!_settings.KeepMaxResults)
{
// Update width
_viewModel.MainWindowWidth = Width;
}
SizeToContent = SizeToContent.Height;
}
@ -1028,6 +1053,56 @@ namespace Flow.Launcher
#endregion
#region Placeholder
private void SetupPlaceholderText()
{
if (_settings.ShowPlaceholder)
{
QueryTextBox.TextChanged += QueryTextBox_TextChanged;
QueryTextSuggestionBox.TextChanged += QueryTextSuggestionBox_TextChanged;
SetPlaceholderText();
}
else
{
QueryTextBox.TextChanged -= QueryTextBox_TextChanged;
QueryTextSuggestionBox.TextChanged -= QueryTextSuggestionBox_TextChanged;
QueryTextPlaceholderBox.Visibility = Visibility.Collapsed;
}
}
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
SetPlaceholderText();
}
private void QueryTextSuggestionBox_TextChanged(object sender, TextChangedEventArgs e)
{
SetPlaceholderText();
}
private void SetPlaceholderText()
{
var queryText = QueryTextBox.Text;
var suggestionText = QueryTextSuggestionBox.Text;
QueryTextPlaceholderBox.Visibility = string.IsNullOrEmpty(queryText) && string.IsNullOrEmpty(suggestionText) ? Visibility.Visible : Visibility.Collapsed;
}
#endregion
#region Resize Mode
private void SetupResizeMode()
{
ResizeMode = _settings.KeepMaxResults ? ResizeMode.NoResize : ResizeMode.CanResize;
if (WindowChrome.GetWindowChrome(this) is WindowChrome windowChrome)
{
_theme.SetResizeBorderThickness(windowChrome, _settings.KeepMaxResults);
}
}
#endregion
#region IDisposable
protected virtual void Dispose(bool disposing)

View file

@ -259,6 +259,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set => Settings.SoundVolume = value;
}
public bool ShowPlaceholder
{
get => Settings.ShowPlaceholder;
set => Settings.ShowPlaceholder = value;
}
public string PlaceholderTextTip
{
get => string.Format(App.API.GetTranslation("PlaceholderTextTip"), App.API.GetTranslation("queryTextBoxPlaceholder"));
}
public string PlaceholderText
{
get => Settings.PlaceholderText;
set => Settings.PlaceholderText = value;
}
public bool UseClock
{
get => Settings.UseClock;

View file

@ -30,6 +30,7 @@
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel>
<!-- Page title -->
<TextBlock
Margin="5 23 0 5"
@ -38,6 +39,7 @@
Text="{DynamicResource appearance}"
TextAlignment="left"
Visibility="Collapsed" />
<!-- Theme Preview and Editor -->
<Grid>
<Grid.Style>
@ -68,6 +70,7 @@
<ColumnDefinition Width="8*" />
<ColumnDefinition MaxWidth="250" Style="{StaticResource SecondColumnStyle}" />
</Grid.ColumnDefinitions>
<!-- Theme Size Editor -->
<Border
Grid.Column="1"
@ -260,7 +263,8 @@
</StackPanel>
</ScrollViewer>
</Border>
<!-- Theme Preview -->
<!-- Theme preview -->
<Border
Grid.Column="0"
Background="{Binding PreviewBackground}"
@ -477,7 +481,6 @@
<!-- ✅ 추가 -->
</cc:Card>
<!-- Drop shadow effect -->
<cc:Card
Title="{DynamicResource queryWindowShadowEffect}"
@ -496,18 +499,20 @@
Text="{DynamicResource browserMoreThemes}"
Uri="{Binding LinkThemeGallery}" />
<!-- Fixed Height -->
<!-- Fixed size -->
<cc:CardGroup Margin="0 20 0 0">
<cc:Card
Title="{DynamicResource KeepMaxResults}"
Icon="&#xe8fd;"
Icon="&#xE744;"
Sub="{DynamicResource KeepMaxResultsToolTip}">
<ui:ToggleSwitch IsOn="{Binding KeepMaxResults}" />
<ui:ToggleSwitch
IsOn="{Binding KeepMaxResults}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Title="{DynamicResource maxShowResults}"
Icon="&#xe8fd;"
Sub="{DynamicResource maxShowResultsToolTip}"
Visibility="{Binding KeepMaxResults, Converter={StaticResource BoolToVisibilityConverter}}">
<ComboBox
@ -529,6 +534,29 @@
OnContent="{DynamicResource enable}" />
</cc:Card>
<!-- Placeholder text -->
<cc:CardGroup Margin="0 14 0 0">
<cc:Card
Title="{DynamicResource ShowPlaceholder}"
Icon="&#xE82F;"
Sub="{DynamicResource ShowPlaceholderTip}">
<ui:ToggleSwitch
IsOn="{Binding ShowPlaceholder}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Title="{DynamicResource PlaceholderText}"
Icon="&#xED59;"
Sub="{Binding PlaceholderTextTip}">
<TextBox
MinWidth="150"
Text="{Binding PlaceholderText}"
TextWrapping="NoWrap" />
</cc:Card>
</cc:CardGroup>
<!-- Time and date -->
<cc:CardGroup Margin="0 14 0 0">
<cc:Card Title="{DynamicResource Clock}" Icon="&#xec92;">

View file

@ -28,7 +28,6 @@
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="#72767d" />
<Setter Property="Background" Value="#36393f" />
<Setter Property="Height" Value="42" />
<Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="0,0,66,0" />

View file

@ -24,7 +24,6 @@
x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="#161614" />
<Setter Property="Foreground" Value="#a09b8c" />
<Setter Property="Padding" Value="0,0,66,0" />
<Setter Property="Height" Value="42" />

View file

@ -22,7 +22,6 @@
x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="#1f1d1f" />
<Setter Property="Foreground" Value="#71114b" />
</Style>

View file

@ -584,7 +584,7 @@ namespace Flow.Launcher.ViewModel
[RelayCommand]
private void IncreaseWidth()
{
Settings.WindowSize += 100;
MainWindowWidth += 100;
Settings.WindowLeft -= 50;
OnPropertyChanged(nameof(MainWindowWidth));
}
@ -592,14 +592,14 @@ namespace Flow.Launcher.ViewModel
[RelayCommand]
private void DecreaseWidth()
{
if (MainWindowWidth - 100 < 400 || Settings.WindowSize == 400)
if (MainWindowWidth - 100 < 400 || MainWindowWidth == 400)
{
Settings.WindowSize = 400;
MainWindowWidth = 400;
}
else
{
MainWindowWidth -= 100;
Settings.WindowLeft += 50;
Settings.WindowSize -= 100;
}
OnPropertyChanged(nameof(MainWindowWidth));
@ -765,6 +765,17 @@ namespace Flow.Launcher.ViewModel
public double ClockPanelOpacity { get; set; } = 1;
public double SearchIconOpacity { get; set; } = 1;
private string _placeholderText;
public string PlaceholderText
{
get => string.IsNullOrEmpty(_placeholderText) ? App.API.GetTranslation("queryTextBoxPlaceholder") : _placeholderText;
set
{
_placeholderText = value;
OnPropertyChanged();
}
}
public double MainWindowWidth
{
get => Settings.WindowSize;