[CmdPal] Added recenter window support. (#38943)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Added a new setting option to Command Palette that allows users to
choose whether the window should be recentered on every launch or
remember its last position. This enhancement improves user experience by
maintaining window positioning preferences across sessions.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #38310
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

This PR adds a "Recenter window on launch" setting to Command Palette
with the following changes:

1. Added a new `RecenterWindow` property to `SettingsModel` (default is
true for backward compatibility)
2. Added corresponding property to `SettingsViewModel` for binding
3. Created a new `WindowPosition` class to track window position and
size
4. Modified `MainWindow.xaml.cs` to:
    - Track window position and size changes
    - Update position memory when window is modified or dismissed
    - Respect the recenter setting when showing the window
5. Added UI controls in the settings page with proper localization
strings

![image](https://github.com/user-attachments/assets/1c1db9f6-b23e-47c0-9a03-93906d4c9a42)



This feature allows users who prefer to have the Command Palette appear
in a specific screen location to maintain that preference, while others
can continue using the centered window behavior.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
1. Manually verified that with the setting enabled (default), the window
centers on launch
2. Verified that with the setting disabled, the window appears at its
last position

---------

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
This commit is contained in:
Shawn Yuan
2025-10-29 03:37:45 +08:00
committed by GitHub
parent 1e40d6b15b
commit 0d18727e81
5 changed files with 110 additions and 4 deletions

View File

@@ -54,6 +54,8 @@ public partial class SettingsModel : ObservableObject
public bool DisableAnimations { get; set; } = true;
public WindowPosition? LastWindowPosition { get; set; }
// END SETTINGS
///////////////////////////////////////////////////////////////////////////
@@ -191,6 +193,7 @@ public partial class SettingsModel : ObservableObject
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(HistoryItem))]
[JsonSerializable(typeof(SettingsModel))]
[JsonSerializable(typeof(WindowPosition))]
[JsonSerializable(typeof(AppStateModel))]
[JsonSerializable(typeof(RecentCommandsManager))]
[JsonSerializable(typeof(List<string>), TypeInfoPropertyName = "StringList")]
@@ -208,4 +211,5 @@ public enum MonitorBehavior
ToPrimary = 1,
ToFocusedWindow = 2,
InPlace = 3,
ToLast = 4,
}

View File

@@ -0,0 +1,22 @@
// 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed class WindowPosition
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}

View File

@@ -63,6 +63,8 @@ public sealed partial class MainWindow : WindowEx,
private DesktopAcrylicController? _acrylicController;
private SystemBackdropConfiguration? _configurationSource;
private WindowPosition _currentWindowPosition = new();
public MainWindow()
{
InitializeComponent();
@@ -84,7 +86,9 @@ public sealed partial class MainWindow : WindowEx,
this.SetIcon();
AppWindow.Title = RS_.GetString("AppName");
PositionCentered();
RestoreWindowPosition();
UpdateWindowPositionInMemory();
SetAcrylic();
WeakReferenceMessenger.Default.Register<DismissMessage>(this);
@@ -155,6 +159,39 @@ public sealed partial class MainWindow : WindowEx,
PositionCentered(displayArea);
}
private void RestoreWindowPosition()
{
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.LastWindowPosition is not WindowPosition savedPosition)
{
PositionCentered();
return;
}
if (savedPosition.Width <= 0 || savedPosition.Height <= 0)
{
PositionCentered();
return;
}
AppWindow.Resize(new SizeInt32 { Width = savedPosition.Width, Height = savedPosition.Height });
var savedRect = new RectInt32(savedPosition.X, savedPosition.Y, savedPosition.Width, savedPosition.Height);
var displayArea = DisplayArea.GetFromRect(savedRect, DisplayAreaFallback.Nearest);
var workArea = displayArea.WorkArea;
var maxX = workArea.X + Math.Max(0, workArea.Width - savedPosition.Width);
var maxY = workArea.Y + Math.Max(0, workArea.Height - savedPosition.Height);
var targetPoint = new PointInt32
{
X = Math.Clamp(savedPosition.X, workArea.X, maxX),
Y = Math.Clamp(savedPosition.Y, workArea.Y, maxY),
};
AppWindow.Move(targetPoint);
}
private void PositionCentered(DisplayArea displayArea)
{
if (displayArea is not null)
@@ -169,6 +206,17 @@ public sealed partial class MainWindow : WindowEx,
}
}
private void UpdateWindowPositionInMemory()
{
_currentWindowPosition = new WindowPosition
{
X = AppWindow.Position.X,
Y = AppWindow.Position.Y,
Width = AppWindow.Size.Width,
Height = AppWindow.Size.Height,
};
}
private void HotReloadSettings()
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
@@ -251,8 +299,16 @@ public sealed partial class MainWindow : WindowEx,
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_RESTORE);
}
var display = GetScreen(hwnd, target);
PositionCentered(display);
if (target == MonitorBehavior.ToLast)
{
AppWindow.Resize(new SizeInt32 { Width = _currentWindowPosition.Width, Height = _currentWindowPosition.Height });
AppWindow.Move(new PointInt32 { X = _currentWindowPosition.X, Y = _currentWindowPosition.Y });
}
else
{
var display = GetScreen(hwnd, target);
PositionCentered(display);
}
// Check if the debugger is attached. If it is, we don't want to apply the tool window style,
// because that would make it hard to debug the app
@@ -413,6 +469,22 @@ public sealed partial class MainWindow : WindowEx,
internal void MainWindow_Closed(object sender, WindowEventArgs args)
{
var serviceProvider = App.Current.Services;
UpdateWindowPositionInMemory();
var settings = serviceProvider.GetService<SettingsModel>();
if (settings is not null)
{
settings.LastWindowPosition = new WindowPosition
{
X = _currentWindowPosition.X,
Y = _currentWindowPosition.Y,
Width = _currentWindowPosition.Width,
Height = _currentWindowPosition.Height,
};
SettingsModel.SaveSettings(settings);
}
var extensionService = serviceProvider.GetService<IExtensionService>()!;
extensionService.SignalStopExtensionsAsync();
@@ -484,6 +556,9 @@ public sealed partial class MainWindow : WindowEx,
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Save the current window position before hiding the window
UpdateWindowPositionInMemory();
// If there's a debugger attached...
if (System.Diagnostics.Debugger.IsAttached)
{

View File

@@ -63,6 +63,7 @@
<ComboBoxItem x:Uid="Run_Radio_Position_Primary_Monitor" />
<ComboBoxItem x:Uid="Run_Radio_Position_Focus" />
<ComboBoxItem x:Uid="Run_Radio_Position_In_Place" />
<ComboBoxItem x:Uid="Run_Radio_Position_LastPosition" />
</ComboBox>
</controls:SettingsCard>

View File

@@ -428,7 +428,11 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="MoreCommandsButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>More</value>
</data>
<data name="TrayMenu_Settings" xml:space="preserve">
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
<value>Last Position</value>
<comment>Reopen the window where it was last closed</comment>
</data>
<data name="TrayMenu_Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="TrayMenu_Close" xml:space="preserve">