Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs
Mike Griese d48286a3eb Scale the toast window for DPI (#38198)
Ah of course, AppWindow.Resize doesn't use DIPs. Why would it? It's not like literally everything else in XAML does.

Related to half of https://github.com/zadjii-msft/PowerToys/issues/508
2025-04-03 06:44:10 -05:00

122 lines
3.9 KiB
C#

// 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 CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.Graphics;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.WindowsAndMessaging;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
namespace Microsoft.CmdPal.UI;
public sealed partial class ToastWindow : Window,
IRecipient<QuitMessage>
{
private readonly HWND _hwnd;
public ToastViewModel ViewModel { get; } = new();
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
public ToastWindow()
{
this.InitializeComponent();
AppWindow.Hide();
AppWindow.IsShownInSwitchers = false;
ExtendsContentIntoTitleBar = true;
AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
this.SetIcon();
AppWindow.Title = RS_.GetString("ToastWindowTitle");
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
PInvoke.EnableWindow(_hwnd, false);
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
}
private static double GetScaleFactor(HWND hwnd)
{
try
{
var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
_ = PInvoke.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
return dpiX / 96.0;
}
catch (Exception ex)
{
Logger.LogError($"Failed to get scale factor, error: {ex.Message}");
return 1.0;
}
}
private void PositionCentered()
{
var intSize = new SizeInt32
{
Width = Convert.ToInt32(ToastText.ActualWidth),
Height = Convert.ToInt32(ToastText.ActualHeight),
};
var scaleAdjustment = GetScaleFactor(_hwnd);
var scaled = new SizeInt32
{
Width = (int)Math.Round(intSize.Width * scaleAdjustment),
Height = (int)Math.Round(intSize.Height * scaleAdjustment),
};
AppWindow.Resize(scaled);
var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest);
if (displayArea is not null)
{
var centeredPosition = AppWindow.Position;
centeredPosition.X = (displayArea.WorkArea.Width - AppWindow.Size.Width) / 2;
var monitorHeight = displayArea.WorkArea.Height;
var windowHeight = AppWindow.Size.Height;
centeredPosition.Y = monitorHeight - (windowHeight * 2);
AppWindow.Move(centeredPosition);
}
}
public void ShowToast(string message)
{
ViewModel.ToastMessage = message;
DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
() =>
{
PositionCentered();
// SW_SHOWNA prevents us from getting activated (and stealing FG)
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_SHOWNA);
_debounceTimer.Debounce(
() =>
{
AppWindow.Hide();
},
interval: TimeSpan.FromMilliseconds(2500),
immediate: false);
});
}
public void Receive(QuitMessage message)
{
// This might come in on a background thread
DispatcherQueue.TryEnqueue(() => Close());
}
}