Compare commits

...

12 Commits

Author SHA1 Message Date
Mike Griese
da880280be fix the BG regression on merge from main 2026-06-18 15:10:13 -05:00
Mike Griese
8d6a8e8a9e Merge remote-tracking branch 'origin/main' into dev/migrie/compact-cmdpal-2 2026-06-18 09:22:19 -05:00
Mike Griese
9f82e018f6 gotta do the clipping for clickthrough 2026-06-02 14:09:59 -05:00
Mike Griese
0c2bae21d4 always expand for dock pages 2026-06-02 10:22:46 -05:00
Mike Griese
c914bdf509 settings for position; fix a select-on-expand 2026-06-02 10:17:39 -05:00
Mike Griese
3851e06f45 better cold launch border 2026-06-02 05:49:18 -05:00
Mike Griese
b27ca5a9bf fuckin amazing, center the textbox vertically 2026-06-01 16:37:35 -05:00
Mike Griese
63701c9b91 holy oneshot batman 2026-06-01 16:09:42 -05:00
Mike Griese
cbb5e985c9 shit I think this works 2026-06-01 15:20:41 -05:00
Mike Griese
017e3278d9 resizing is working better 2026-06-01 13:56:35 -05:00
Mike Griese
e834031c7f Add a setting to show/hide the frame, for debugging 2026-06-01 11:01:40 -05:00
Mike Griese
981c1cefba What if the window was a smaller control inside of our HWND 2026-06-01 10:33:23 -05:00
20 changed files with 1326 additions and 201 deletions

View File

@@ -365,6 +365,13 @@ public sealed partial class MainListPage : DynamicListPage,
public override void UpdateSearchText(string oldSearch, string newSearch)
{
var oldWasEmpty = string.IsNullOrEmpty(oldSearch);
var newWasEmpty = string.IsNullOrEmpty(newSearch);
if (oldWasEmpty != newWasEmpty)
{
WeakReferenceMessenger.Default.Send<ExpandCompactModeMessage>(new(!newWasEmpty));
}
UpdateSearchTextCore(oldSearch, newSearch, isUserInput: true);
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ExpandCompactModeMessage(bool Expanded)
{
}

View File

@@ -42,6 +42,14 @@ public record SettingsModel
public bool AllowExternalReload { get; init; }
public bool CompactMode { get; set; } = true;
// When compact mode is on and the palette is centered on launch, this is the relative
// height from the bottom of the screen (as a percentage) at which the collapsed search
// box is vertically centered. 75 places it in the upper portion of the display. Ignored
// when compact mode is off.
public int CompactCenterHeightPercentage { get; set; } = 75;
private ImmutableDictionary<string, ProviderSettings>? _providerSettings
= ImmutableDictionary<string, ProviderSettings>.Empty;
@@ -137,6 +145,18 @@ public record SettingsModel
// </Gallery settings>
// Internal diagnostics settings
/// <summary>
/// Gets a value indicating whether the main window's HWND chrome (title bar, border,
/// system-drawn rounded corners) is visible. <strong>For internal debugging only.</strong>
/// Off by default. The setting is persisted but only honored in non-CI builds; release /
/// CI builds always force the borderless / transparent host window.
/// </summary>
public bool ShowHwndFrame { get; init; }
// </Internal diagnostics settings>
// END SETTINGS
///////////////////////////////////////////////////////////////////////////

View File

@@ -131,6 +131,25 @@ public partial class SettingsViewModel : INotifyPropertyChanged
}
}
public bool CompactMode
{
get => _settingsService.Settings.CompactMode;
set
{
_settingsService.UpdateSettings(s => s with { CompactMode = value });
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CompactMode)));
}
}
public double CompactCenterHeightPercentage
{
get => _settingsService.Settings.CompactCenterHeightPercentage;
set
{
_settingsService.UpdateSettings(s => s with { CompactCenterHeightPercentage = (int)value });
}
}
public bool IgnoreShortcutWhenFullscreen
{
get => _settingsService.Settings.IgnoreShortcutWhenFullscreen;

View File

@@ -85,6 +85,8 @@ public partial class ShellViewModel : ObservableObject,
public bool IsNested => _isNested && !_currentlyTransient;
public bool IsTransient => _currentlyTransient;
public PageViewModel NullPage { get; private set; }
public ShellViewModel(

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.CmdPal.UI.Controls.CmdPalMainControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="Transparent"
IsTabStop="False"
mc:Ignorable="d">
<UserControl.Resources>
<ThemeShadow x:Key="CardShadow" />
</UserControl.Resources>
<!-- Outer transparent host. Padding leaves room for the drop shadow. -->
<Grid x:Name="ShadowHost" Padding="{x:Bind ShadowPadding, Mode=OneWay}">
<!--
The "card" — this is what looks like the cmdpal window.
Border draws the 1px stroke and clips children to the rounded shape.
ThemeShadow + a Translation on Z casts the drop shadow outside the card.
-->
<Border
x:Name="CardBorder"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{x:Bind CardCornerRadius, Mode=OneWay}"
Shadow="{StaticResource CardShadow}"
Translation="0,0,32">
<Grid x:Name="CardContent">
<!-- System backdrop (Mica / Acrylic / etc.) drawn only behind the card -->
<controls:SystemBackdropElement x:Name="BackdropElement" CornerRadius="{x:Bind CardCornerRadius, Mode=OneWay}" />
<!-- Optional background image (sits between backdrop and content) -->
<ContentPresenter
x:Name="BackgroundLayerPresenter"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{x:Bind BackgroundLayer, Mode=OneWay}"
IsHitTestVisible="False" />
<!-- Main UI content (e.g. ShellPage) -->
<ContentPresenter
x:Name="MainContentPresenter"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{x:Bind MainContent, Mode=OneWay}" />
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,221 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.UI;
using WinUIEx;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// The visible "card" of the Command Palette — a control that renders the rounded
/// corners, border, shadow and system backdrop. The HWND that hosts it is borderless
/// and transparent, so all the chrome lives here instead of in window non-client area.
/// </summary>
public sealed partial class CmdPalMainControl : UserControl
{
public static readonly DependencyProperty MainContentProperty =
DependencyProperty.Register(
nameof(MainContent),
typeof(object),
typeof(CmdPalMainControl),
new PropertyMetadata(null));
public static readonly DependencyProperty BackgroundLayerProperty =
DependencyProperty.Register(
nameof(BackgroundLayer),
typeof(object),
typeof(CmdPalMainControl),
new PropertyMetadata(null));
public static readonly DependencyProperty ShadowPaddingProperty =
DependencyProperty.Register(
nameof(ShadowPadding),
typeof(Thickness),
typeof(CmdPalMainControl),
new PropertyMetadata(new Thickness(16)));
public static readonly DependencyProperty CardCornerRadiusProperty =
DependencyProperty.Register(
nameof(CardCornerRadius),
typeof(CornerRadius),
typeof(CmdPalMainControl),
new PropertyMetadata(new CornerRadius(8)));
/// <summary>
/// Gets or sets the main UI content hosted inside the card (e.g. the ShellPage).
/// </summary>
public object? MainContent
{
get => GetValue(MainContentProperty);
set => SetValue(MainContentProperty, value);
}
/// <summary>
/// Gets or sets a background layer rendered between the backdrop and the main content
/// (e.g. the BlurImageControl). Hit-testing is disabled on this layer.
/// </summary>
public object? BackgroundLayer
{
get => GetValue(BackgroundLayerProperty);
set => SetValue(BackgroundLayerProperty, value);
}
/// <summary>
/// Gets or sets the amount of transparent padding around the card. The drop shadow
/// is rendered into this padded area.
/// </summary>
public Thickness ShadowPadding
{
get => (Thickness)GetValue(ShadowPaddingProperty);
set => SetValue(ShadowPaddingProperty, value);
}
/// <summary>
/// Gets or sets the corner radius of the card. Applied to both the clipping border
/// and the backdrop element.
/// </summary>
public CornerRadius CardCornerRadius
{
get => (CornerRadius)GetValue(CardCornerRadiusProperty);
set => SetValue(CardCornerRadiusProperty, value);
}
/// <summary>
/// Gets the visible card border. Drag regions should be computed against this element
/// so they line up with what the user sees, not the (larger, transparent) HWND.
/// </summary>
public FrameworkElement CardElement => CardBorder;
/// <summary>
/// Gets the panel inside the card that hosts the backdrop, background layer, and main
/// content. Overlay UI (e.g. the dev ribbon) can be added to this panel so it draws
/// inside the rounded card.
/// </summary>
public Panel CardContentPanel => CardContent;
public CmdPalMainControl()
{
this.InitializeComponent();
}
/// <summary>
/// Clamps the maximum height of the visible card (in DIPs). Use this to keep an expanded
/// compact card from growing past the bottom of the display. Pass
/// <see cref="double.PositiveInfinity"/> to remove the clamp.
/// </summary>
public void SetCardMaxHeight(double maxHeightDip)
{
CardBorder.MaxHeight = maxHeightDip;
}
/// <summary>
/// Returns the current height of the visible card (in DIPs). When the card is in its
/// compact layout this is the height of just the search box, which callers use to center
/// the collapsed card on screen.
/// </summary>
public double GetCardHeight()
{
CardBorder.UpdateLayout();
return CardBorder.ActualHeight;
}
/// <summary>
/// Forwards the host window's activation state to the current backdrop so the system can
/// render its active / inactive appearance correctly.
/// </summary>
public void SetIsInputActive(bool isActive)
{
if (BackdropElement.SystemBackdrop is TintedControllerBackdrop tinted)
{
tinted.IsInputActive = isActive;
}
}
/// <summary>
/// Detaches any backdrop from the embedded element. Used during shutdown to release the
/// underlying controller eagerly.
/// </summary>
public void ClearBackdrop()
{
BackdropElement.SystemBackdrop = null;
}
/// <summary>
/// Applies a backdrop configuration to the embedded <see cref="SystemBackdropElement"/>.
/// </summary>
/// <param name="backdrop">Tint / opacity / fallback parameters from the theme service.</param>
/// <param name="kind">The controller kind selected by the user's backdrop style.</param>
/// <param name="isImageMode">When true, the background image control draws the tint, so no tint is applied to the backdrop itself.</param>
/// <param name="hasColorization">When true, custom tint properties are applied to Mica backdrops.</param>
public void ApplyBackdrop(BackdropParameters backdrop, BackdropControllerKind kind, bool isImageMode, bool hasColorization)
{
try
{
BackdropElement.SystemBackdrop = CreateBackdrop(backdrop, kind, isImageMode, hasColorization);
}
catch (Exception ex)
{
Logger.LogError("Failed to apply backdrop to CmdPalMainControl", ex);
}
}
private static Microsoft.UI.Xaml.Media.SystemBackdrop? CreateBackdrop(BackdropParameters backdrop, BackdropControllerKind kind, bool isImageMode, bool hasColorization)
{
// Image mode: don't tint here, BlurImageControl handles it (avoids double-tinting).
var effectiveTintOpacity = isImageMode ? 0.0f : backdrop.EffectiveOpacity;
switch (kind)
{
case BackdropControllerKind.Solid:
var solidTint = Color.FromArgb(
(byte)(backdrop.EffectiveOpacity * 255),
backdrop.TintColor.R,
backdrop.TintColor.G,
backdrop.TintColor.B);
return new TransparentTintBackdrop { TintColor = solidTint };
case BackdropControllerKind.Mica:
case BackdropControllerKind.MicaAlt:
if (!MicaController.IsSupported())
{
return new TransparentTintBackdrop { TintColor = backdrop.FallbackColor };
}
return new TintedMicaBackdrop
{
Kind = kind == BackdropControllerKind.MicaAlt ? MicaKind.BaseAlt : MicaKind.Base,
ApplyTint = hasColorization || isImageMode,
TintColor = backdrop.TintColor,
TintOpacity = effectiveTintOpacity,
FallbackColor = backdrop.FallbackColor,
LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
};
case BackdropControllerKind.Acrylic:
case BackdropControllerKind.AcrylicThin:
default:
if (!DesktopAcrylicController.IsSupported())
{
return new TransparentTintBackdrop { TintColor = backdrop.FallbackColor };
}
return new TintedDesktopAcrylicBackdrop
{
Kind = kind == BackdropControllerKind.AcrylicThin
? DesktopAcrylicKind.Thin
: DesktopAcrylicKind.Default,
TintColor = backdrop.TintColor,
TintOpacity = effectiveTintOpacity,
FallbackColor = backdrop.FallbackColor,
LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
};
}
}
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// Base class for tinted backdrops that wrap a controller from
/// <see cref="Microsoft.UI.Composition.SystemBackdrops"/> so they can be applied
/// to a single control via <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// </summary>
/// <remarks>
/// The stock <see cref="MicaBackdrop"/> / <see cref="DesktopAcrylicBackdrop"/> classes
/// don't expose tint color / opacity / luminosity customization. This base type plugs
/// the lower-level controllers into the new <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>
/// extensibility surface so we can keep all of CmdPal's theme-driven tinting.
/// </remarks>
internal abstract partial class TintedControllerBackdrop : SystemBackdrop
{
private SystemBackdropConfiguration? _config;
public Color TintColor { get; init; }
public float TintOpacity { get; init; }
public Color FallbackColor { get; init; }
public float LuminosityOpacity { get; init; }
/// <summary>
/// Gets a value indicating whether tint properties should be applied. Mica without
/// colorization wants the system defaults; in that case set this to false.
/// </summary>
public bool ApplyTint { get; init; } = true;
/// <summary>
/// Gets or sets a value indicating whether the host window is currently activated. The
/// system uses this to decide between the active and inactive backdrop appearance.
/// </summary>
public bool IsInputActive
{
get => _config?.IsInputActive ?? true;
set
{
if (_config is not null)
{
_config.IsInputActive = value;
}
}
}
protected SystemBackdropConfiguration? Configuration => _config;
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
{
base.OnTargetConnected(connectedTarget, xamlRoot);
_config = new SystemBackdropConfiguration
{
IsInputActive = true,
Theme = xamlRoot.Content is FrameworkElement fe
? ToBackdropTheme(fe.ActualTheme)
: SystemBackdropTheme.Default,
};
AttachController(connectedTarget, xamlRoot);
}
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
{
DetachController(disconnectedTarget);
_config = null;
base.OnTargetDisconnected(disconnectedTarget);
}
protected abstract void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot);
protected abstract void DetachController(ICompositionSupportsSystemBackdrop target);
private static SystemBackdropTheme ToBackdropTheme(ElementTheme theme) => theme switch
{
ElementTheme.Dark => SystemBackdropTheme.Dark,
ElementTheme.Light => SystemBackdropTheme.Light,
_ => SystemBackdropTheme.Default,
};
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// A tinted <see cref="DesktopAcrylicController"/> exposed as a <see cref="SystemBackdrop"/>
/// so it can be hosted by <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// </summary>
internal sealed partial class TintedDesktopAcrylicBackdrop : TintedControllerBackdrop, IDisposable
{
private DesktopAcrylicController? _controller;
public DesktopAcrylicKind Kind { get; init; } = DesktopAcrylicKind.Default;
protected override void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot)
{
if (!DesktopAcrylicController.IsSupported())
{
return;
}
_controller = new DesktopAcrylicController
{
Kind = Kind,
TintColor = TintColor,
TintOpacity = TintOpacity,
FallbackColor = FallbackColor,
LuminosityOpacity = LuminosityOpacity,
};
_controller.AddSystemBackdropTarget(target);
_controller.SetSystemBackdropConfiguration(Configuration);
}
protected override void DetachController(ICompositionSupportsSystemBackdrop target)
{
if (_controller is not null)
{
_controller.RemoveSystemBackdropTarget(target);
_controller.Dispose();
_controller = null;
}
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// A tinted <see cref="MicaController"/> exposed as a <see cref="SystemBackdrop"/>
/// so it can be hosted by <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// </summary>
internal sealed partial class TintedMicaBackdrop : TintedControllerBackdrop, IDisposable
{
private MicaController? _controller;
public MicaKind Kind { get; init; } = MicaKind.Base;
protected override void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot)
{
if (!MicaController.IsSupported())
{
return;
}
_controller = new MicaController { Kind = Kind };
// Only set tint properties when colorization is active.
// Otherwise let the system handle light/dark theme defaults automatically.
if (ApplyTint)
{
_controller.TintColor = TintColor;
_controller.TintOpacity = TintOpacity;
_controller.FallbackColor = FallbackColor;
_controller.LuminosityOpacity = LuminosityOpacity;
}
_controller.AddSystemBackdropTarget(target);
_controller.SetSystemBackdropConfiguration(Configuration);
}
protected override void DetachController(ICompositionSupportsSystemBackdrop target)
{
if (_controller is not null)
{
_controller.RemoveSystemBackdropTarget(target);
_controller.Dispose();
_controller = null;
}
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.CmdPal.UI;
/// <summary>
/// Converts a boolean to a <see cref="GridLength"/>: <c>true</c> yields a star (*) row that
/// fills the available space, while <c>false</c> yields an Auto row that sizes to its content.
/// This lets the expandable content row collapse to zero in compact mode so the card can
/// shrink to just the search box (a star row would otherwise reserve space during measure
/// even when its only child is collapsed).
/// </summary>
public partial class BoolToStarOrAutoGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var expanded = value is bool b && b;
return expanded ? new GridLength(1, GridUnitType.Star) : GridLength.Auto;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

View File

@@ -15,22 +15,32 @@
Activated="MainWindow_Activated"
Closed="MainWindow_Closed"
mc:Ignorable="d">
<Grid x:Name="RootElement">
<controls:BlurImageControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BlurAmount="{x:Bind ViewModel.BackgroundImageBlurAmount, Mode=OneWay}"
ImageBrightness="{x:Bind ViewModel.BackgroundImageBrightness, Mode=OneWay}"
ImageOpacity="{x:Bind ViewModel.EffectiveImageOpacity, Mode=OneWay}"
ImageSource="{x:Bind ViewModel.BackgroundImageSource, Mode=OneWay}"
ImageStretch="{x:Bind ViewModel.BackgroundImageStretch, Mode=OneWay}"
IsHitTestVisible="False"
IsHoldingEnabled="False"
TintColor="{x:Bind ViewModel.BackgroundImageTint, Mode=OneWay}"
TintIntensity="{x:Bind ViewModel.BackgroundImageTintIntensity, Mode=OneWay}"
Visibility="{x:Bind ViewModel.ShowBackgroundImage, Mode=OneWay}" />
<pages:ShellPage HostWindow="{x:Bind}" />
</Grid>
<!--
The whole window is borderless and transparent (see MainWindow.xaml.cs).
CmdPalMainControl is the visible "card" — it draws the rounded corners,
border, drop shadow, and hosts the SystemBackdropElement that paints
Mica / Acrylic / etc. behind the content.
-->
<controls:CmdPalMainControl x:Name="RootElement">
<controls:CmdPalMainControl.BackgroundLayer>
<controls:BlurImageControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BlurAmount="{x:Bind ViewModel.BackgroundImageBlurAmount, Mode=OneWay}"
ImageBrightness="{x:Bind ViewModel.BackgroundImageBrightness, Mode=OneWay}"
ImageOpacity="{x:Bind ViewModel.EffectiveImageOpacity, Mode=OneWay}"
ImageSource="{x:Bind ViewModel.BackgroundImageSource, Mode=OneWay}"
ImageStretch="{x:Bind ViewModel.BackgroundImageStretch, Mode=OneWay}"
IsHitTestVisible="False"
IsHoldingEnabled="False"
TintColor="{x:Bind ViewModel.BackgroundImageTint, Mode=OneWay}"
TintIntensity="{x:Bind ViewModel.BackgroundImageTintIntensity, Mode=OneWay}"
Visibility="{x:Bind ViewModel.ShowBackgroundImage, Mode=OneWay}" />
</controls:CmdPalMainControl.BackgroundLayer>
<controls:CmdPalMainControl.MainContent>
<pages:ShellPage HostWindow="{x:Bind}" />
</controls:CmdPalMainControl.MainContent>
</controls:CmdPalMainControl>
</winuiex:WindowEx>

View File

@@ -15,6 +15,7 @@ using Microsoft.CmdPal.UI.Dock;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Pages;
using Microsoft.CmdPal.UI.Services;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
@@ -22,8 +23,7 @@ using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CmdPal.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
@@ -32,13 +32,11 @@ using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Graphics;
using Windows.System;
using Windows.UI;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT;
using WinUIEx;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
@@ -58,6 +56,7 @@ public sealed partial class MainWindow : WindowEx,
IRecipient<DragCompletedMessage>,
IRecipient<ToggleDevRibbonMessage>,
IRecipient<GetHwndMessage>,
IRecipient<ExpandCompactModeMessage>,
IDisposable,
IHostWindow
{
@@ -90,12 +89,19 @@ public sealed partial class MainWindow : WindowEx,
private int _sessionMaxNavigationDepth;
private int _sessionErrorCount;
private DesktopAcrylicController? _acrylicController;
private MicaController? _micaController;
private SystemBackdropConfiguration? _configurationSource;
private bool _isUpdatingBackdrop;
private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan;
// Tracks the chrome mode currently applied to the HWND. Nullable so the first
// call to ApplyHwndFrameMode always runs, regardless of which mode we land in.
private bool? _hwndFrameVisible;
// Thickness (in DIPs) of the resize grip around the visible card's border. Shared
// by the InputNonClientPointerSource region registration (so WM_NCHITTEST actually
// fires over the border) and the WM_NCHITTEST handler (so it returns resize codes
// over the same band). These MUST match or the two disagree about where resizing is.
private const int ResizeBorderThicknessDip = 8;
private WindowPosition _currentWindowPosition = new();
private bool _preventHideWhenDeactivated;
@@ -127,7 +133,13 @@ public sealed partial class MainWindow : WindowEx,
CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value);
}
InitializeBackdropSupport();
// The HWND itself is borderless / transparent — the visible card lives inside
// RootElement (CmdPalMainControl) and draws its own corners, border, shadow, and
// backdrop via the SystemBackdropElement. The frame can be re-enabled via an
// internal-only setting (hot-reloaded through HotReloadSettings) to make the
// HWND bounds visible while debugging.
var initialSettings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
ApplyHwndFrameMode(ShouldShowHwndFrame(initialSettings));
_hiddenOwnerBehavior.ShowInTaskbar(this, Debugger.IsAttached);
@@ -164,6 +176,7 @@ public sealed partial class MainWindow : WindowEx,
WeakReferenceMessenger.Default.Register<DragCompletedMessage>(this);
WeakReferenceMessenger.Default.Register<ToggleDevRibbonMessage>(this);
WeakReferenceMessenger.Default.Register<GetHwndMessage>(this);
WeakReferenceMessenger.Default.Register<ExpandCompactModeMessage>(this);
// Hide our titlebar.
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
@@ -232,16 +245,24 @@ public sealed partial class MainWindow : WindowEx,
// the DIP size won't trigger it, leaving drag regions at the old physical coordinates.
RootElement.XamlRoot.Changed += XamlRoot_Changed;
// Add dev ribbon if enabled
// The visible card resizes inside the fixed-size HWND (e.g. compact <-> expanded),
// which does not raise WindowSizeChanged. Recompute the drag regions and the HWND
// clip region whenever the card's own size changes so they keep tracking it.
RootElement.CardElement.SizeChanged += CardElement_SizeChanged;
// Add dev ribbon if enabled. The ribbon lives inside the visible card so it
// doesn't draw into the transparent shadow area outside the rounded border.
if (!BuildInfo.IsCiBuild)
{
_devRibbon = new DevRibbon { Margin = new Thickness(-1, -1, 120, -1) };
RootElement.Children.Add(_devRibbon);
RootElement.CardContentPanel.Children.Add(_devRibbon);
}
}
private void XamlRoot_Changed(XamlRoot sender, XamlRootChangedEventArgs args) => UpdateRegionsForCustomTitleBar();
private void CardElement_SizeChanged(object sender, SizeChangedEventArgs e) => UpdateRegionsForCustomTitleBar();
private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs args) => UpdateRegionsForCustomTitleBar();
private void PositionCentered()
@@ -275,10 +296,77 @@ public sealed partial class MainWindow : WindowEx,
if (rect is not null)
{
MoveAndResizeDpiAware(rect.Value);
var finalRect = rect.Value;
// In compact mode, center the *visible collapsed card* (the search box) on the
// display, not the much larger transparent HWND. The card is anchored to the top
// of the HWND, so we offset the HWND upward by the card's center so that growing
// the card downward (when results appear) keeps the search box where it was.
if (TryGetCompactCardCenterOffsetPhysical(windowDpi, out var cardCenterFromHwndTop))
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
var workArea = displayArea.WorkArea;
// The setting is the relative height measured from the *bottom* of the screen,
// so a larger percentage places the search box higher up the display.
var fractionFromTop = GetCompactCenterFractionFromTop(settings);
var desiredCardCenterY = workArea.Y + (int)Math.Round(workArea.Height * fractionFromTop);
finalRect.Y = desiredCardCenterY - cardCenterFromHwndTop;
if (finalRect.Y < workArea.Y)
{
finalRect.Y = workArea.Y;
}
}
MoveAndResizeDpiAware(finalRect);
}
}
/// <summary>
/// When the palette is in compact mode and is being centered on launch, computes the
/// distance (in physical pixels) from the top of the HWND to the vertical center of the
/// collapsed card, so the caller can position the HWND such that the card is centered.
/// Returns false when the card should not be re-centered (compact mode off, or a summon
/// behavior that restores the last position).
/// </summary>
private bool TryGetCompactCardCenterOffsetPhysical(int windowDpi, out int cardCenterFromHwndTop)
{
cardCenterFromHwndTop = 0;
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
if (!settings.CompactMode || !IsCenteringSummon(settings))
{
return false;
}
// Make sure the card is actually collapsed before we measure it.
(RootElement.MainContent as ShellPage)?.EnsureCompactLayout();
var cardHeightDip = RootElement.GetCardHeight();
if (cardHeightDip <= 0)
{
return false;
}
var scale = windowDpi / 96.0;
var cardTopDip = RootElement.ShadowPadding.Top;
cardCenterFromHwndTop = (int)Math.Round((cardTopDip + (cardHeightDip / 2.0)) * scale);
return true;
}
// Every summon behavior except ToLast centers the window on its target display.
private static bool IsCenteringSummon(SettingsModel settings) => settings.SummonOn != MonitorBehavior.ToLast;
// Converts the "center height" setting (a percentage measured up from the bottom of the
// screen) into the fraction of the work area, measured from the top, at which the
// collapsed search box should be centered.
private static double GetCompactCenterFractionFromTop(SettingsModel settings)
{
var pct = Math.Clamp(settings.CompactCenterHeightPercentage, 0, 100);
return 1.0 - (pct / 100.0);
}
private void RestoreWindowPosition(WindowPosition? savedPosition)
{
if (savedPosition?.IsSizeValid != true)
@@ -380,17 +468,104 @@ public sealed partial class MainWindow : WindowEx,
_autoGoHomeInterval = settings.AutoGoHomeInterval;
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
ApplyHwndFrameMode(ShouldShowHwndFrame(settings));
// Start collapsed: the card shrinks to just the search box until there is a query.
HandleExpandCompactOnUiThread(false);
}
/// <summary>
/// Returns true if the user has opted in to seeing the OS-drawn HWND chrome (an internal
/// debugging setting). Always false in CI / release builds.
/// </summary>
private static bool ShouldShowHwndFrame(SettingsModel settings) =>
!BuildInfo.IsCiBuild && settings.ShowHwndFrame;
/// <summary>
/// Configures the HWND for the borderless / transparent main-window mode and (when
/// the internal debug toggle is enabled) overlays the OS-drawn chrome so the HWND's
/// real bounds are easy to spot. Hit testing is always handled by
/// <see cref="HitTestForCardResize"/> — the frame flag is purely visual.
/// </summary>
private void ApplyHwndFrameMode(bool showFrame)
{
if (_hwndFrameVisible == showFrame)
{
return;
}
_hwndFrameVisible = showFrame;
// The HWND itself never paints — the card draws the backdrop. Re-applying this
// each toggle is safe (it just reassigns SystemBackdrop) and guards against the
// OS replacing it when chrome changes.
InitializeBackdropSupport();
if (AppWindow.Presenter is OverlappedPresenter overlappedPresenter)
{
// When the debug flag is off we hide the OS chrome (no title bar, no border).
// When on we let the OS draw both so the HWND outline is obvious.
// This must actually be applied (not just relied on via WM_NCCALCSIZE): now
// that the HWND is clipped to the card region, the OS-drawn title bar / frame
// is no longer covered by our full-window transparent content, so DWM would
// otherwise repaint it (most visibly the inactive caption) behind the card
// when the window loses focus.
overlappedPresenter.SetBorderAndTitleBar(showFrame, showFrame);
// IsResizable must stay true so WS_THICKFRAME is present. The OS only honors
// resize-style WM_NCHITTEST results (HTLEFT, HTRIGHT, HT{TOP,BOTTOM}{,LEFT,RIGHT})
// when the window has a sizing frame, even though we drive the resize from a
// custom NCHITTEST handler. Setting it after SetBorderAndTitleBar makes sure a
// borderless window still keeps its sizing frame.
overlappedPresenter.IsResizable = true;
}
ApplyHwndBorderAttributes(showFrame);
// Drag regions are computed relative to the visible card; the chrome change can
// shift its on-screen position, so refresh.
UpdateRegionsForCustomTitleBar();
}
/// <summary>
/// Applies the DWM corner and border attributes for the current frame mode. This is
/// split out from <see cref="ApplyHwndFrameMode"/> because the DWM border color does
/// not reliably "take" when first set during window construction (before the HWND has
/// been shown on a cold process start) — leaving the faint OS outline visible until
/// the chrome is toggled. Re-applying it each time the window is shown guarantees the
/// borderless look on a cold start.
/// </summary>
private void ApplyHwndBorderAttributes(bool showFrame)
{
unsafe
{
// Rounded corners: let the OS pick when the debug frame is on, suppress
// otherwise so the card's CornerRadius isn't doubled by an OS rounding.
var corner = (uint)(showFrame
? DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DEFAULT
: DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND);
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(uint));
// DWMWA_BORDER_COLOR: 0xFFFFFFFE = DWMWA_COLOR_NONE (no border drawn);
// 0xFFFFFFFF = DWMWA_COLOR_DEFAULT (system default). With WS_THICKFRAME still
// on, DWM otherwise draws a faint 1px outline around the HWND — which the
// user sees as the "frame still appears around the sides" even when our
// ShowHwndFrame setting is off. Setting COLOR_NONE removes it.
const uint DWMWA_COLOR_NONE = 0xFFFFFFFEu;
const uint DWMWA_COLOR_DEFAULT = 0xFFFFFFFFu;
var borderColor = showFrame ? DWMWA_COLOR_DEFAULT : DWMWA_COLOR_NONE;
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, &borderColor, sizeof(uint));
}
}
private void InitializeBackdropSupport()
{
if (DesktopAcrylicController.IsSupported() || MicaController.IsSupported())
{
_configurationSource = new SystemBackdropConfiguration
{
IsInputActive = true,
};
}
// The window itself paints nothing (it's transparent). All actual backdrop
// rendering lives on the SystemBackdropElement inside CmdPalMainControl, so the
// mica/acrylic only fills the rounded card instead of the whole HWND. The empty
// tint here keeps the HWND fully transparent.
SystemBackdrop = new TransparentTintBackdrop { TintColor = Colors.Transparent };
}
private void UpdateBackdrop()
@@ -403,35 +578,14 @@ public sealed partial class MainWindow : WindowEx,
_isUpdatingBackdrop = true;
var backdrop = _themeService.Current.BackdropParameters;
var isImageMode = ViewModel.ShowBackgroundImage;
var config = BackdropStyles.Get(backdrop.Style);
try
{
switch (config.ControllerKind)
{
case BackdropControllerKind.Solid:
CleanupBackdropControllers();
var tintColor = Color.FromArgb(
(byte)(backdrop.EffectiveOpacity * 255),
backdrop.TintColor.R,
backdrop.TintColor.G,
backdrop.TintColor.B);
SetupTransparentBackdrop(tintColor);
break;
var backdrop = _themeService.Current.BackdropParameters;
var isImageMode = ViewModel.ShowBackgroundImage;
var config = BackdropStyles.Get(backdrop.Style);
var hasColorization = _themeService.Current.HasColorization;
case BackdropControllerKind.Mica:
case BackdropControllerKind.MicaAlt:
SetupMica(backdrop, isImageMode, config.ControllerKind);
break;
case BackdropControllerKind.Acrylic:
case BackdropControllerKind.AcrylicThin:
default:
SetupDesktopAcrylic(backdrop, isImageMode, config.ControllerKind);
break;
}
RootElement.ApplyBackdrop(backdrop, config.ControllerKind, isImageMode, hasColorization);
}
catch (Exception ex)
{
@@ -443,111 +597,6 @@ public sealed partial class MainWindow : WindowEx,
}
}
private void SetupTransparentBackdrop(Color tintColor)
{
if (SystemBackdrop is TransparentTintBackdrop existingBackdrop)
{
existingBackdrop.TintColor = tintColor;
}
else
{
SystemBackdrop = new TransparentTintBackdrop { TintColor = tintColor };
}
}
private void CleanupBackdropControllers()
{
if (_acrylicController is not null)
{
_acrylicController.RemoveAllSystemBackdropTargets();
_acrylicController.Dispose();
_acrylicController = null;
}
if (_micaController is not null)
{
_micaController.RemoveAllSystemBackdropTargets();
_micaController.Dispose();
_micaController = null;
}
}
private void SetupDesktopAcrylic(BackdropParameters backdrop, bool isImageMode, BackdropControllerKind kind)
{
CleanupBackdropControllers();
// Fall back to solid color if acrylic not supported
if (_configurationSource is null || !DesktopAcrylicController.IsSupported())
{
SetupTransparentBackdrop(backdrop.FallbackColor);
return;
}
// DesktopAcrylicController and SystemBackdrop can't be active simultaneously
SystemBackdrop = null;
// Image mode: no tint here, BlurImageControl handles it (avoids double-tinting)
var effectiveTintOpacity = isImageMode
? 0.0f
: backdrop.EffectiveOpacity;
_acrylicController = new DesktopAcrylicController
{
Kind = kind == BackdropControllerKind.AcrylicThin
? DesktopAcrylicKind.Thin
: DesktopAcrylicKind.Default,
TintColor = backdrop.TintColor,
TintOpacity = effectiveTintOpacity,
FallbackColor = backdrop.FallbackColor,
LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
};
// Requires "using WinRT;" for Window.As<>()
_acrylicController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
}
private void SetupMica(BackdropParameters backdrop, bool isImageMode, BackdropControllerKind kind)
{
CleanupBackdropControllers();
// Fall back to solid color if Mica not supported
if (_configurationSource is null || !MicaController.IsSupported())
{
SetupTransparentBackdrop(backdrop.FallbackColor);
return;
}
// MicaController and SystemBackdrop can't be active simultaneously
SystemBackdrop = null;
_configurationSource.Theme = _themeService.Current.Theme == ElementTheme.Dark
? SystemBackdropTheme.Dark
: SystemBackdropTheme.Light;
var hasColorization = _themeService.Current.HasColorization || isImageMode;
_micaController = new MicaController
{
Kind = kind == BackdropControllerKind.MicaAlt
? MicaKind.BaseAlt
: MicaKind.Base,
};
// Only set tint properties when colorization is active
// Otherwise let system handle light/dark theme defaults automatically
if (hasColorization)
{
// Image mode: no tint here, BlurImageControl handles it (avoids double-tinting)
_micaController.TintColor = backdrop.TintColor;
_micaController.TintOpacity = isImageMode ? 0.0f : backdrop.EffectiveOpacity;
_micaController.FallbackColor = backdrop.FallbackColor;
_micaController.LuminosityOpacity = backdrop.EffectiveLuminosityOpacity;
}
_micaController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
_micaController.SetSystemBackdropConfiguration(_configurationSource);
}
private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target)
{
var positionWindowForTargetMonitor = (HWND hwnd) =>
@@ -654,6 +703,13 @@ public sealed partial class MainWindow : WindowEx,
// Just to be sure, SHOW our hwnd.
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOW);
// Re-apply the borderless DWM attributes now that the window is actually shown.
// On a cold process start these are first set during construction before the HWND
// has ever been displayed, and DWM doesn't reliably honor the border color until
// the window exists on-screen — which left the faint OS outline visible until the
// chrome was toggled. Re-applying here makes the borderless look stick on cold start.
ApplyHwndBorderAttributes(_hwndFrameVisible ?? false);
// Once we're done, uncloak to avoid all animations
Uncloak();
@@ -939,8 +995,17 @@ public sealed partial class MainWindow : WindowEx,
private void DisposeAcrylic()
{
CleanupBackdropControllers();
_configurationSource = null!;
// The backdrop controllers now live on the SystemBackdropElement inside
// CmdPalMainControl. Clearing its SystemBackdrop fires OnTargetDisconnected on the
// current backdrop, which removes targets and disposes the underlying controller.
try
{
RootElement?.ClearBackdrop();
}
catch
{
// Best-effort cleanup; ignore errors during shutdown.
}
}
// Updates our window s.t. the top of the window is draggable.
@@ -955,31 +1020,113 @@ public sealed partial class MainWindow : WindowEx,
// Specify the interactive regions of the title bar.
var scaleAdjustment = xamlRoot.RasterizationScale;
// Get the rectangle around our XAML content. We're going to mark this
// rectangle as "Passthrough", so that the normal window operations
// (resizing, dragging) don't apply in this space.
var transform = RootElement.TransformToVisual(null);
// Reserve 16px of space at the top for dragging.
var topHeight = 16;
var bounds = transform.TransformBounds(new Rect(
0,
topHeight,
RootElement.ActualWidth,
RootElement.ActualHeight));
var contentRect = GetRect(bounds, scaleAdjustment);
var rectArray = new RectInt32[] { contentRect };
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
// Add a drag-able region on top
var w = RootElement.ActualWidth;
_ = RootElement.ActualHeight;
var dragSides = new RectInt32[]
// Drag/passthrough regions are computed against the visible card (the rounded
// border inside CmdPalMainControl), not the whole HWND. The HWND extends beyond
// the card to make room for the drop shadow, and we don't want that transparent
// shadow area to be draggable.
var card = RootElement.CardElement;
if (card.ActualWidth <= 0 || card.ActualHeight <= 0)
{
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall
return;
}
// All coordinates below are in the card's own (DIP) space: (0,0) is the
// top-left of the visible card, (w,h) is the bottom-right. GetRect transforms
// them into the physical-pixel client coordinates that
// InputNonClientPointerSource expects.
var transform = card.TransformToVisual(null);
var w = card.ActualWidth;
var h = card.ActualHeight;
RectInt32 CardRect(double x, double y, double rw, double rh) =>
GetRect(transform.TransformBounds(new Rect(x, y, rw, rh)), scaleAdjustment);
// Reserve some space at the top for dragging the window (caption).
const double dragHeight = 16;
// The resize grip straddles each card edge by `grip` DIPs on either side so the
// affordance reaches a little into the drop-shadow padding too.
const double grip = ResizeBorderThicknessDip;
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
// Mark the card's border ring AND the top drag bar as non-client (Caption).
// This is the critical bit: only regions registered here generate WM_NCHITTEST
// on our window proc. Without the side/bottom strips, the XAML content island
// swallows the pointer and we never get a hit-test to turn into a resize. Our
// HotKeyPrc WM_NCHITTEST handler then decides drag (caption) vs. resize
// (HTLEFT / HTRIGHT / HT{TOP,BOTTOM}{,LEFT,RIGHT}) per-pixel via geometry.
var caption = new RectInt32[]
{
CardRect(0, 0, w, dragHeight), // top drag bar
CardRect(-grip, -grip, 2 * grip, h + (2 * grip)), // left edge
CardRect(w - grip, -grip, 2 * grip, h + (2 * grip)), // right edge
CardRect(-grip, -grip, w + (2 * grip), 2 * grip), // top edge
CardRect(-grip, h - grip, w + (2 * grip), 2 * grip), // bottom edge
};
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Caption, dragSides);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Caption, caption);
// Everything inside the border ring (and below the drag bar) is interactive
// content. Marking it Passthrough keeps the search box, list, etc. clickable
// and explicitly carves it out of the caption regions above.
var interiorWidth = Math.Max(0, w - (2 * grip));
var interiorHeight = Math.Max(0, h - dragHeight - grip);
var passthrough = new RectInt32[]
{
CardRect(grip, dragHeight, interiorWidth, interiorHeight),
};
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, passthrough);
// Clip the HWND itself down to just the visible card. The window is intentionally
// larger than the card (the extra margin holds the drop shadow), but that
// transparent margin shouldn't be part of the window at all: clicks there should
// fall through to whatever is behind the palette, and the shadow must not be
// hit-testable. The region is updated here so it tracks the card as it grows /
// shrinks (e.g. compact <-> expanded).
ApplyCardWindowRegion(CardRect(0, 0, w, h));
}
/// <summary>
/// Restricts the HWND's visible / hit-testable area to the rectangle occupied by the
/// visible card (supplied in physical client pixels). Everything outside — the
/// transparent drop-shadow margin — becomes click-through and is excluded from the
/// window region. When the debug HWND frame is enabled the clip is removed so the full
/// window stays visible.
/// </summary>
private void ApplyCardWindowRegion(RectInt32 cardPhysical)
{
nint hwnd;
unsafe
{
hwnd = (nint)_hwnd.Value;
}
// Debug frame mode: keep the whole window visible / interactive, no clip.
if (_hwndFrameVisible == true)
{
_ = SetWindowRgn(hwnd, IntPtr.Zero, true);
return;
}
// CreateRectRgn coordinates are relative to the window's top-left. For this
// borderless popup the client origin coincides with the window origin, so the
// card's client-space physical rect maps directly into window space.
var region = CreateRectRgn(
cardPhysical.X,
cardPhysical.Y,
cardPhysical.X + cardPhysical.Width,
cardPhysical.Y + cardPhysical.Height);
if (region == IntPtr.Zero)
{
return;
}
// On success SetWindowRgn takes ownership of the region (the OS frees it), so we
// only delete it ourselves if the call failed.
if (SetWindowRgn(hwnd, region, true) == 0)
{
_ = DeleteObject(region);
}
}
private static RectInt32 GetRect(Rect bounds, double scale)
@@ -991,6 +1138,19 @@ public sealed partial class MainWindow : WindowEx,
_Height: (int)Math.Round(bounds.Height * scale));
}
// Raw interop for the window-region clip. Declared here (rather than via CsWin32)
// because SetWindowRgn transfers ownership of the HRGN to the OS on success, which is
// awkward to express through CsWin32's SafeHandle-returning region creator.
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, [MarshalAs(UnmanagedType.Bool)] bool bRedraw);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);
internal void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (!_themeServiceInitialized && args.WindowActivationState != WindowActivationState.Deactivated)
@@ -1043,9 +1203,9 @@ public sealed partial class MainWindow : WindowEx,
PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus());
}
if (_configurationSource is not null)
if (RootElement is not null)
{
_configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
RootElement.SetIsInputActive(args.WindowActivationState != WindowActivationState.Deactivated);
}
}
@@ -1333,6 +1493,29 @@ public sealed partial class MainWindow : WindowEx,
case PInvoke.WM_DPICHANGED when _suppressDpiChange:
return (LRESULT)IntPtr.Zero;
case PInvoke.WM_NCHITTEST:
{
var ht = HitTestForCardResize(lParam);
if (ht != 0)
{
return (LRESULT)(nint)ht;
}
break;
}
// Borderless mode: claim the entire window rectangle as client area.
// A resizable window has WS_THICKFRAME, which makes the OS reserve a
// non-client sizing frame *and* gives the window a DWM drop shadow / a thin
// frame line along the top. We keep WS_THICKFRAME (so our custom WM_NCHITTEST
// can still drive resizing) but tell the OS the whole window is client by
// returning 0 from WM_NCCALCSIZE — which removes that frame and its shadow.
// The visible card draws its own border + shadow inside the transparent HWND.
// When the debug frame is on we fall through to the default handling so the
// real OS chrome appears.
case PInvoke.WM_NCCALCSIZE when wParam.Value != 0 && _hwndFrameVisible != true:
return (LRESULT)0;
case PInvoke.WM_HOTKEY:
{
var hotkeyIndex = (int)wParam.Value;
@@ -1357,6 +1540,116 @@ public sealed partial class MainWindow : WindowEx,
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
}
/// <summary>
/// Custom WM_NCHITTEST handler that turns the visible card's border (the rounded
/// stroke drawn by <see cref="CmdPalMainControl"/>) into the window's resize handles.
/// Without this the borderless / transparent HWND has no visible resize affordance,
/// even though the OS still allows resizing along the (invisible) HWND edges.
/// </summary>
/// <returns>
/// A non-zero HT* value to override the system hit test, or 0 to fall through to
/// the default WndProc (which lets the InputNonClientPointerSource Caption /
/// Passthrough regions decide caption vs. client behavior inside the card).
/// </returns>
private uint HitTestForCardResize(LPARAM lParam)
{
// NB: We intentionally do *not* short-circuit when the debug frame is showing.
// The HWND frame toggle is purely a visual diagnostic; resize hit-testing
// remains ours in both modes so the card's border is always the grab area.
if (RootElement is null || RootElement.XamlRoot is null)
{
return 0;
}
// LPARAM packs the screen-space pointer position: low word = x, high word = y,
// both as signed 16-bit ints.
var ptX = (short)(lParam.Value & 0xFFFF);
var ptY = (short)((lParam.Value >> 16) & 0xFFFF);
if (!PInvoke.GetWindowRect(_hwnd, out var windowRect))
{
return 0;
}
// Convert the card's ShadowPadding (DIPs) into screen pixels so we can locate
// the visible card rect within the (larger, transparent) HWND.
var dpi = PInvoke.GetDpiForWindow(_hwnd);
var scale = dpi / 96.0;
var padding = RootElement.ShadowPadding;
var cardLeft = windowRect.left + (int)Math.Round(padding.Left * scale);
var cardTop = windowRect.top + (int)Math.Round(padding.Top * scale);
var cardRight = windowRect.right - (int)Math.Round(padding.Right * scale);
var cardBottom = windowRect.bottom - (int)Math.Round(padding.Bottom * scale);
// Width of the resize grip around the card's visible border, in screen pixels.
// Shared with the InputNonClientPointerSource region registration in
// UpdateRegionsForCustomTitleBar so the band where WM_NCHITTEST fires lines up
// exactly with the band where we return resize codes.
var grip = (int)Math.Round(ResizeBorderThicknessDip * scale);
var onLeftEdge = ptX >= cardLeft - grip && ptX < cardLeft + grip;
var onRightEdge = ptX > cardRight - grip && ptX <= cardRight + grip;
var onTopEdge = ptY >= cardTop - grip && ptY < cardTop + grip;
var onBottomEdge = ptY > cardBottom - grip && ptY <= cardBottom + grip;
// Corners get priority over edges.
if (onTopEdge && onLeftEdge)
{
return PInvoke.HTTOPLEFT;
}
if (onTopEdge && onRightEdge)
{
return PInvoke.HTTOPRIGHT;
}
if (onBottomEdge && onLeftEdge)
{
return PInvoke.HTBOTTOMLEFT;
}
if (onBottomEdge && onRightEdge)
{
return PInvoke.HTBOTTOMRIGHT;
}
var withinHorizontalSpan = ptX >= cardLeft - grip && ptX <= cardRight + grip;
var withinVerticalSpan = ptY >= cardTop - grip && ptY <= cardBottom + grip;
if (onTopEdge && withinHorizontalSpan)
{
return PInvoke.HTTOP;
}
if (onBottomEdge && withinHorizontalSpan)
{
return PInvoke.HTBOTTOM;
}
if (onLeftEdge && withinVerticalSpan)
{
return PInvoke.HTLEFT;
}
if (onRightEdge && withinVerticalSpan)
{
return PInvoke.HTRIGHT;
}
// Pointer is inside the card but away from the border: defer to the default
// hit test so the InputNonClientPointerSource Caption/Passthrough regions take
// effect for dragging vs. normal input.
if (ptX >= cardLeft && ptX <= cardRight && ptY >= cardTop && ptY <= cardBottom)
{
return 0;
}
// Pointer is in the transparent shadow padding around the card. Make that area
// click-through so the window behind us receives the mouse input.
return unchecked((uint)PInvoke.HTTRANSPARENT);
}
public void Dispose()
{
_localKeyboardListener.Dispose();
@@ -1415,4 +1708,51 @@ public sealed partial class MainWindow : WindowEx,
{
message.Hwnd = this.GetWindowHandle();
}
public void Receive(ExpandCompactModeMessage message)
{
this.DispatcherQueue.TryEnqueue(() => HandleExpandCompactOnUiThread(message.Expanded));
}
// The HWND is already as large as it will ever need to be (and it's transparent), so
// instead of resizing the window we simply shrink or grow the visible card inside it.
private void HandleExpandCompactOnUiThread(bool expanded)
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
// Only the compact + centered configuration needs a screen-fit clamp. There the card
// is anchored near the vertical center of the display, so an expanded list could run
// off the bottom edge; cap its height so it always fits. In every other case the card
// is free to fill the (fixed-size) HWND as before.
if (expanded && settings.CompactMode && IsCenteringSummon(settings))
{
RootElement.SetCardMaxHeight(ComputeExpandedCardMaxHeightDip());
}
else
{
RootElement.SetCardMaxHeight(double.PositiveInfinity);
}
}
// Computes how tall (in DIPs) the visible card may grow before it would extend past the
// bottom of the work area, given the card's current top on screen.
private double ComputeExpandedCardMaxHeightDip()
{
var dpi = (int)this.GetDpiForWindow();
var scale = dpi / 96.0;
var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest);
var workArea = displayArea.WorkArea;
var padding = RootElement.ShadowPadding;
var cardTopPhysical = AppWindow.Position.Y + (padding.Top * scale);
var availablePhysical = (workArea.Y + workArea.Height) - cardTopPhysical - (padding.Bottom * scale);
if (availablePhysical <= 0)
{
return double.PositiveInfinity;
}
return availablePhysical / scale;
}
}

View File

@@ -105,6 +105,19 @@ SIZE_MINIMIZED
HWND_NOTOPMOST
HWND_TOP
HTCAPTION
HTCLIENT
HTTRANSPARENT
HTNOWHERE
HTLEFT
HTRIGHT
HTTOP
HTBOTTOM
HTTOPLEFT
HTTOPRIGHT
HTBOTTOMLEFT
HTBOTTOMRIGHT
WM_NCHITTEST
WM_NCCALCSIZE
GetClassName
EVENT_SYSTEM_FOREGROUND
WINEVENT_OUTOFCONTEXT

View File

@@ -28,6 +28,7 @@
<cmdpalUI:DetailsSizeToGridLengthConverter x:Key="SizeToWidthConverter" />
<cmdpalUI:MessageStateToSeverityConverter x:Key="MessageStateToSeverityConverter" />
<cmdpalUI:BoolToStarOrAutoGridLengthConverter x:Key="ExpandedModeToRowHeightConverter" />
<cmdpalUI:DetailsDataTemplateSelector
x:Key="DetailsDataTemplateSelector"
@@ -183,15 +184,19 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<!--
In compact mode this row collapses to Auto so the card can shrink to just the
search box. A star row would otherwise reserve space during measure even when
its only child (the collapsed content) is hidden.
-->
<RowDefinition Height="{x:Bind ExpandedMode, Mode=OneWay, Converter={StaticResource ExpandedModeToRowHeightConverter}}" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="{ThemeResource LayerOnAcrylicPrimaryBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Back button and search box -->
@@ -383,6 +388,18 @@
</animations:Implicit.HideAnimations>
</ProgressBar>
</Grid>
<Grid
Grid.Row="1"
Background="{ThemeResource LayerOnAcrylicPrimaryBackgroundBrush}"
Visibility="{x:Bind ExpandedMode, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="ContentGrid" Grid.Row="1">
<Grid.ColumnDefinitions>
@@ -516,12 +533,13 @@
See https://github.com/microsoft/microsoft-ui-xaml/issues/5741
-->
<StackPanel
Grid.Row="0"
Grid.Row="1"
Margin="16,8,16,8"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
CornerRadius="{ThemeResource ControlCornerRadius}">
CornerRadius="{ThemeResource ControlCornerRadius}"
Visibility="Collapsed">
<InfoBar
CornerRadius="{ThemeResource ControlCornerRadius}"
IsOpen="{x:Bind ViewModel.CurrentPage.HasStatusMessage, Mode=OneWay}"
@@ -540,10 +558,11 @@
</StackPanel>
<Grid
Grid.Row="1"
Grid.Row="2"
Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}"
BorderBrush="{ThemeResource CmdPal.DividerStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0">
BorderThickness="0,1,0,0"
Visibility="{x:Bind ExpandedMode, Mode=OneWay}">
<cpcontrols:CommandBar CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
</Grid>

View File

@@ -50,6 +50,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
IRecipient<NavigateToPageMessage>,
IRecipient<ShowHideDockMessage>,
IRecipient<ShowPinToDockDialogMessage>,
IRecipient<ExpandCompactModeMessage>,
INotifyPropertyChanged,
IDisposable
{
@@ -71,6 +72,13 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private CancellationTokenSource? _focusAfterLoadedCts;
private WeakReference<Page>? _lastNavigatedPageRef;
// When the shell goes from compact (collapsed) to expanded, the content frame's page
// — which was collapsed and therefore never laid out — finally fires its Loaded event.
// That late Loaded would otherwise run the post-navigation focus/select logic and
// select-all the character the user just typed (which triggered the expand). This
// one-shot flag suppresses that select for the expand-driven load.
private bool _suppressSelectOnNextLoad;
private bool _isDisposed;
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
@@ -79,8 +87,13 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
public IHostWindow? HostWindow { get; set; }
public bool ExpandedMode { get; set; }
public ShellPage()
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
this.ExpandedMode = !settings.CompactMode;
this.InitializeComponent();
// how we are doing navigation around
@@ -104,6 +117,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Register<ShowHideDockMessage>(this);
WeakReferenceMessenger.Default.Register<ShowPinToDockDialogMessage>(this);
WeakReferenceMessenger.Default.Register<ExpandCompactModeMessage>(this);
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
AddHandler(KeyDownEvent, new KeyEventHandler(ShellPage_OnKeyDown), false);
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
@@ -470,6 +485,12 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
// When re-showing the palette, the previous session's query may still be present
// (e.g. after a light dismiss with HighlightSearchOnActivate). Recompute the
// compact/expanded state so a retained query restores the expanded results instead
// of being stuck in the collapsed search-only layout.
UpdateCompactModeForCurrentPage();
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
}
@@ -581,6 +602,12 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
// A real navigation always loads a fresh page that we do want to focus/select, so
// clear any stale suppression left over from a prior compact expand. (If this
// navigation itself expands compact mode, UpdateCompactModeForCurrentPage below
// will re-arm the flag for the page that's about to load.)
_suppressSelectOnNextLoad = false;
// This listens to the root frame to ensure that we also track the content's page VM as well that we passed as a parameter.
// This is currently used for both forward and backward navigation.
// As when we go back that we restore ourselves to the proper state within our VM
@@ -617,6 +644,42 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
_lastNavigatedPageRef = new WeakReference<Page>(element);
element.Loaded += FocusAfterLoaded;
}
UpdateCompactModeForCurrentPage();
}
/// <summary>
/// Updates the compact/expanded state after a navigation. On any nested (sub) page we
/// always show the full expanded UI; on the root page the search box drives the state,
/// so we collapse to the compact search box only when the query is empty. Driving this
/// from navigation (rather than only from search-text changes) makes alias-based
/// navigation expand correctly — an alias clears the search box before navigating, so
/// the search-text transition alone would otherwise leave the palette collapsed.
/// Transient pages always show the expanded UI, ignoring the compact setting entirely.
/// </summary>
private void UpdateCompactModeForCurrentPage()
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
if (!settings.CompactMode)
{
return;
}
// Transient pages ignore compact mode and always present as expanded.
if (ViewModel.IsTransient)
{
HandleExpandCompactOnUiThread(true);
return;
}
// The ShellViewModel's IsNested flag is only updated on forward navigation and is
// never cleared when navigating back to the root page. Gate it on the current
// page's own root-ness so a stale IsNested can't keep the home page expanded after
// returning to it (e.g. after following a 1-character alias and going back).
var isRootPage = ViewModel.CurrentPage?.IsRootPage ?? false;
var nested = ViewModel.IsNested && !isRootPage;
var hasQuery = !string.IsNullOrEmpty(ViewModel.CurrentPage?.SearchTextBox);
HandleExpandCompactOnUiThread(nested || hasQuery);
}
private void FocusAfterLoaded(object sender, RoutedEventArgs e)
@@ -649,6 +712,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
return;
}
// This Loaded can fire late when expanding out of compact mode (the page was
// collapsed and never laid out). In that case the user is mid-typing in the
// already-focused search box, so don't steal focus / select-all their input.
if (_suppressSelectOnNextLoad)
{
_suppressSelectOnNextLoad = false;
return;
}
SearchBox.Focus(FocusState.Programmatic);
SearchBox.SelectSearch();
}
@@ -842,6 +914,50 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
public void Receive(ExpandCompactModeMessage message)
{
// Re-evaluate from the current authoritative page state rather than applying the
// message's snapshot directly. The message can race with navigation: following a
// 1-character alias clears the home search (sending a "collapse") right as we
// navigate to a nested page that must stay expanded. Recomputing here keeps the
// final state consistent regardless of message/navigation ordering.
this.DispatcherQueue.TryEnqueue(UpdateCompactModeForCurrentPage);
}
private void HandleExpandCompactOnUiThread(bool expanded)
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
var newExpanded = settings.CompactMode ? expanded : true;
// Going from collapsed to expanded realizes the (previously collapsed) content
// page for the first time, which fires its deferred Loaded event. Suppress the
// resulting focus/select so we don't select-all the character the user just typed.
if (!this.ExpandedMode && newExpanded)
{
_suppressSelectOnNextLoad = true;
}
this.ExpandedMode = newExpanded;
PropertyChanged?.Invoke(this, new(nameof(ExpandedMode)));
}
/// <summary>
/// Forces the shell into its compact (collapsed) layout and flushes layout so the host can
/// read the resulting card height. Only has an effect when compact mode is enabled.
/// </summary>
public void EnsureCompactLayout()
{
var settings = App.Current.Services.GetRequiredService<ISettingsService>().Settings;
if (!settings.CompactMode)
{
return;
}
this.ExpandedMode = false;
PropertyChanged?.Invoke(this, new(nameof(ExpandedMode)));
this.UpdateLayout();
}
public void Dispose()
{
if (_isDisposed)

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="Microsoft.CmdPal.UI.Settings.GeneralPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -119,6 +119,29 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="Settings_GeneralPage_CompactMode_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE73F;}">
<ToggleSwitch AutomationProperties.AutomationId="CmdPal_GeneralPage_CompactMode" IsOn="{x:Bind viewModel.CompactMode, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard
x:Uid="Settings_GeneralPage_CompactCenterHeight_SettingsCard"
HeaderIcon="{ui:FontIcon Glyph=&#xE74A;}"
IsEnabled="{x:Bind viewModel.CompactMode, Mode=OneWay}">
<Slider
Width="100"
Height="100"
MinWidth="{StaticResource SettingActionControlMinWidth}"
AutomationProperties.AutomationId="CmdPal_GeneralPage_CompactCenterHeight"
Maximum="100"
Minimum="0"
Orientation="Vertical"
StepFrequency="5"
TickFrequency="25"
TickPlacement="Outside"
Value="{x:Bind viewModel.CompactCenterHeightPercentage, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- 'Behavior' section -->
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />

View File

@@ -82,6 +82,17 @@
Click="ToggleDevRibbonClicked"
Content="Toggle dev ribbon" />
</controls:SettingsCard>
<controls:SettingsCard
x:Name="ShowHwndFrameSettingsCard"
Description="Shows the OS-drawn title bar, border, and rounded corners on the Command Palette's HWND so its actual bounds are visible. Always off in CI / release builds."
Header="Show HWND frame"
HeaderIcon="{ui:FontIcon Glyph=&#xE737;}">
<ToggleSwitch
x:Name="ShowHwndFrameToggle"
AutomationProperties.AutomationId="CmdPal_InternalPage_ShowHwndFrame"
IsOn="{x:Bind ShowHwndFrame, Mode=OneTime}"
Toggled="ShowHwndFrameToggle_Toggled" />
</controls:SettingsCard>
<!-- Gallery Section -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="Extension Gallery" />

View File

@@ -26,6 +26,8 @@ public sealed partial class InternalPage : Page
public string GalleryFeedUrl => _settingsService.Settings.GalleryFeedUrl ?? string.Empty;
public bool ShowHwndFrame => _settingsService.Settings.ShowHwndFrame;
public InternalPage()
{
InitializeComponent();
@@ -120,4 +122,16 @@ public sealed partial class InternalPage : Page
{
WeakReferenceMessenger.Default.Send(new ToggleDevRibbonMessage());
}
private void ShowHwndFrameToggle_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggle)
{
var newValue = toggle.IsOn;
if (newValue != _settingsService.Settings.ShowHwndFrame)
{
_settingsService.UpdateSettings(s => s with { ShowHwndFrame = newValue });
}
}
}
}

View File

@@ -380,6 +380,18 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="Settings_GeneralPage_HighlightSearch_SettingsCard.Description" xml:space="preserve">
<value>Selects the previous search text at launch</value>
</data>
<data name="Settings_GeneralPage_CompactMode_SettingsCard.Header" xml:space="preserve">
<value>Compact mode</value>
</data>
<data name="Settings_GeneralPage_CompactMode_SettingsCard.Description" xml:space="preserve">
<value>Shrinks the palette to just the search box until you start typing</value>
</data>
<data name="Settings_GeneralPage_CompactCenterHeight_SettingsCard.Header" xml:space="preserve">
<value>Search box position</value>
</data>
<data name="Settings_GeneralPage_CompactCenterHeight_SettingsCard.Description" xml:space="preserve">
<value>Relative height from the bottom of the screen where the collapsed search box is centered. Only applies in compact mode.</value>
</data>
<data name="Settings_GeneralPage_KeepPreviousQuery_SettingsCard.Header" xml:space="preserve">
<value>Keep previous query</value>
</data>