From 3928c609d9d09686dac3eee75000514a1cd3640b Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Sat, 6 Dec 2025 12:37:06 -0600 Subject: [PATCH] part deux --- .../Microsoft.CommandPalette.UI.Models.csproj | 6 -- .../ToastViewModel.cs | 11 +++ .../Microsoft.CommandPalette.UI.csproj | 5 + .../Pages/ShellPage.xaml.cs | 22 ++++- .../ToastWindow.xaml | 24 +++++ .../ToastWindow.xaml.cs | 97 +++++++++++++++++++ 6 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/ToastViewModel.cs create mode 100644 src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml create mode 100644 src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml.cs diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Microsoft.CommandPalette.UI.Models.csproj b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Microsoft.CommandPalette.UI.Models.csproj index d3e3dac226..646d5bb94d 100644 --- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Microsoft.CommandPalette.UI.Models.csproj +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Microsoft.CommandPalette.UI.Models.csproj @@ -14,12 +14,6 @@ SA1313; - - AdaptiveCards.ObjectModel.WinUI3;AdaptiveCards.Rendering.WinUI3 - true - - - diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/ToastViewModel.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/ToastViewModel.cs new file mode 100644 index 0000000000..f2decd0c15 --- /dev/null +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/ToastViewModel.cs @@ -0,0 +1,11 @@ +// 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.CommandPalette.UI.ViewModels; + +public partial class ToastViewModel : ObservableObject +{ + [ObservableProperty] + public partial string ToastMessage { get; set; } = string.Empty; +} diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj index 8bbabf8088..3d91f82c90 100644 --- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj @@ -164,6 +164,11 @@ + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs index c93f84e6a5..9073e20a2d 100644 --- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs @@ -2,14 +2,34 @@ // 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.ComponentModel; +using System.Text; using Microsoft.CommandPalette.ViewModels; -using Microsoft.UI.Xaml.Controls; +using Windows.System; namespace Microsoft.CommandPalette.UI.Pages; public sealed partial class ShellPage : Page { private readonly ShellViewModel viewModel; + private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread(); + private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + + private readonly SlideNavigationTransitionInfo _slideRightTransition = new() { Effect = SlideNavigationTransitionEffect.FromRight }; + private readonly SuppressNavigationTransitionInfo _noAnimation = new(); + + private readonly ToastWindow _toast = new(); + + private readonly CompositeFormat _pageNavigatedAnnouncement; + + + private CancellationTokenSource? _focusAfterLoadedCts; + + public event PropertyChangedEventHandler? PropertyChanged; + + private WeakReference? _lastNavigatedPageRef; + private SettingsWindow? _settingsWindow; public ShellPage(ShellViewModel viewModel) { diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml new file mode 100644 index 0000000000..95412988c8 --- /dev/null +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml.cs new file mode 100644 index 0000000000..5bdbb4ee3f --- /dev/null +++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/ToastWindow.xaml.cs @@ -0,0 +1,97 @@ +// 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.CommandPalette.UI.Models.Messages; +using Windows.System; +using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance; + +namespace Microsoft.CommandPalette.UI; + +public sealed partial class ToastWindow : WindowEx, + IRecipient +{ + private readonly HWND _hwnd; + private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); + private readonly HiddenOwnerWindowBehavior _hiddenOwnerWindowBehavior = new(); + + public ToastViewModel ViewModel { get; } = new(); + + public ToastWindow() + { + this.InitializeComponent(); + AppWindow.Hide(); + ExtendsContentIntoTitleBar = true; + AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay); + this.SetIcon(); + AppWindow.Title = RS_.GetString("ToastWindowTitle"); + AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed; + _hiddenOwnerWindowBehavior.ShowInTaskbar(this, false); + + _hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32()); + PInvoke.EnableWindow(_hwnd, false); + + WeakReferenceMessenger.Default.Register(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() + { + this.SetWindowSize(ToastText.ActualWidth, ToastText.ActualHeight); + + 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 + 8); // Align with other shell toasts, like the volume indicator. + 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()); + } +}