From 5c0a498c38a4e02f478f17d59c236e332ce5e787 Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Wed, 29 May 2024 15:41:02 -0700 Subject: [PATCH 01/14] initial --- tools/PI/DevHome.PI/BarWindow.cs | 3 + tools/PI/DevHome.PI/BarWindowVertical.xaml | 2 +- tools/PI/DevHome.PI/BarWindowVertical.xaml.cs | 134 --------------- tools/PI/DevHome.PI/Helpers/SnapHelper.cs | 153 ++++++++++++++++++ .../ViewModels/BarWindowViewModel.cs | 3 + 5 files changed, 160 insertions(+), 135 deletions(-) create mode 100644 tools/PI/DevHome.PI/Helpers/SnapHelper.cs diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index cc65711b76..3be12731fb 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -22,6 +22,7 @@ public partial class BarWindow private readonly BarWindowHorizontal _horizontalWindow; private readonly BarWindowVertical _verticalWindow; private readonly BarWindowViewModel _viewModel = new(); + private readonly SnapHelper _snapHelper; internal HWND CurrentHwnd { @@ -80,6 +81,7 @@ public BarWindow() { _horizontalWindow = new BarWindowHorizontal(_viewModel); _verticalWindow = new BarWindowVertical(_viewModel); + _snapHelper = new(_viewModel); _horizontalWindow.Closed += Window_Closed; _verticalWindow.Closed += Window_Closed; @@ -99,6 +101,7 @@ private void Window_Closed(object sender, WindowEventArgs args) { // If we receive a window closed event, clean up the system TargetAppData.Instance.ClearAppData(); + _snapHelper.Close(); var primaryWindow = Application.Current.GetService(); primaryWindow.ClearBarWindow(); diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml b/tools/PI/DevHome.PI/BarWindowVertical.xaml index 5ef7f3c187..0e973a61b0 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml @@ -11,7 +11,7 @@ mc:Ignorable="d" Title="" MinHeight="700" MinWidth="70" MaxWidth="70" Width="70" Height="700" TaskBarIcon="Images/pi.ico" IsTitleBarVisible="False" - Closed="WindowEx_Closed" > + IsAlwaysOnTop="{x:Bind _viewModel.IsAlwaysOnTop, Mode=TwoWay}"> diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs b/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs index c42a568d89..82ba5714ba 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs @@ -38,16 +38,9 @@ public partial class BarWindowVertical : WindowEx private int _appWindowPosX; // = 0; private int _appWindowPosY; // = 0; private bool isWindowMoving; // = false; - private const int UnsnapGap = 9; - - private readonly WINEVENTPROC _winPositionEventDelegate; - private readonly WINEVENTPROC _winFocusEventDelegate; private Button? _selectedExternalToolButton; - private HWINEVENTHOOK _positionEventHook; - private HWINEVENTHOOK _focusEventHook; - internal HWND ThisHwnd { get; private set; } public Microsoft.UI.Dispatching.DispatcherQueue TheDispatcher @@ -63,24 +56,6 @@ public BarWindowVertical(BarWindowViewModel model) TheDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); InitializeComponent(); - _viewModel.PropertyChanged += ViewModel_PropertyChanged; - _winPositionEventDelegate = new(WinPositionEventProc); - _winFocusEventDelegate = new(WinFocusEventProc); - } - - private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(BarWindowViewModel.IsSnapped)) - { - if (_viewModel.IsSnapped) - { - Snap(); - } - else - { - Unsnap(); - } - } } private void MainPanel_Loaded(object sender, RoutedEventArgs e) @@ -117,21 +92,6 @@ private void ExternalToolButton_Click(object sender, RoutedEventArgs e) } } - private void WindowEx_Closed(object sender, WindowEventArgs args) - { - if (_positionEventHook != IntPtr.Zero) - { - PInvoke.UnhookWinEvent(_positionEventHook); - _positionEventHook = HWINEVENTHOOK.Null; - } - - if (_focusEventHook != HWINEVENTHOOK.Null) - { - PInvoke.UnhookWinEvent(_focusEventHook); - _focusEventHook = HWINEVENTHOOK.Null; - } - } - private void ExternalToolButton_PointerPressed(object sender, PointerRoutedEventArgs e) { _selectedExternalToolButton = (Button)sender; @@ -161,100 +121,6 @@ internal void SetRequestedTheme(ElementTheme theme) } } - private void WinPositionEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime) - { - // Filter out events for non-main windows. - if (idObject != 0 || idChild != 0) - { - return; - } - - if (hwnd == TargetAppData.Instance.HWnd) - { - if (eventType == PInvoke.EVENT_OBJECT_LOCATIONCHANGE) - { - if (_viewModel.IsSnapped) - { - // If the window has been maximized, un-snap the bar window and free-float it. - if (PInvoke.IsZoomed(TargetAppData.Instance.HWnd)) - { - _viewModel.IsSnapped = false; - } - else - { - // Reposition the window to match the moved/resized/minimized/restored target window. - // If the target window was maximized and has now been restored, we want - // to resnap to it, but not do all the other work we do when we resnap - // to a new window. - SnapToWindow(); - } - } - } - - // If the window we're watching closes, we unsnap - if (eventType == PInvoke.EVENT_OBJECT_DESTROY) - { - Unsnap(); - } - } - } - - private void WinFocusEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime) - { - // If we're snapped to a target window, and that window loses and then regains focus, - // we need to bring our window to the front also, to be in-sync. Otherwise, we can - // end up with the target in the foreground, but our window partially obscured. - if (hwnd == TargetAppData.Instance.HWnd && _viewModel.IsSnapped) - { - this.SetIsAlwaysOnTop(true); - this.SetIsAlwaysOnTop(false); - return; - } - } - - private void Snap() - { - Debug.Assert(_positionEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); - Debug.Assert(_focusEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); - - _positionEventHook = WatchWindowPositionEvents(_winPositionEventDelegate, (uint)TargetAppData.Instance.ProcessId); - _focusEventHook = WatchWindowFocusEvents(_winFocusEventDelegate, (uint)TargetAppData.Instance.ProcessId); - - SnapToWindow(); - } - - private void Unsnap() - { - // Set a gap from the associated app window to provide positive feedback. - this.MoveAndResize( - AppWindow.Position.X + UnsnapGap, - AppWindow.Position.Y, - AppWindow.Size.Width, - AppWindow.Size.Height); - - if (_positionEventHook != HWINEVENTHOOK.Null) - { - PInvoke.UnhookWinEvent(_positionEventHook); - _positionEventHook = HWINEVENTHOOK.Null; - } - - if (_focusEventHook != HWINEVENTHOOK.Null) - { - PInvoke.UnhookWinEvent(_focusEventHook); - _focusEventHook = HWINEVENTHOOK.Null; - } - } - - private void SnapToWindow() - { - Debug.Assert(_viewModel.IsSnapped, "We're not snapped!"); - - WindowHelper.SnapToWindow(TargetAppData.Instance.HWnd, ThisHwnd, AppWindow.Size); - - this.SetIsAlwaysOnTop(true); - this.SetIsAlwaysOnTop(false); - } - private void CloseButton_Click(object sender, RoutedEventArgs e) { var primaryWindow = Application.Current.GetService(); diff --git a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs new file mode 100644 index 0000000000..67a6cdd3d2 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using DevHome.PI.Models; +using DevHome.PI.ViewModels; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Accessibility; + +namespace DevHome.PI.Helpers; + +public class SnapHelper +{ + private const int UnsnapGap = 9; + + private readonly WINEVENTPROC _winPositionEventDelegate; + private readonly WINEVENTPROC _winFocusEventDelegate; + private readonly BarWindowViewModel _viewModel; + + private HWINEVENTHOOK _positionEventHook; + private HWINEVENTHOOK _focusEventHook; + + public SnapHelper(BarWindowViewModel viewModel) + { + _viewModel = viewModel; + _viewModel.PropertyChanged += ViewModel_PropertyChanged; + _winPositionEventDelegate = new(WinPositionEventProc); + _winFocusEventDelegate = new(WinFocusEventProc); + } + + private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(BarWindowViewModel.IsSnapped)) + { + if (_viewModel.IsSnapped) + { + Snap(); + } + else + { + Unsnap(); + } + } + } + + private void WinPositionEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime) + { + // Filter out events for non-main windows. + if (idObject != 0 || idChild != 0) + { + return; + } + + if (hwnd == TargetAppData.Instance.HWnd) + { + if (eventType == PInvoke.EVENT_OBJECT_LOCATIONCHANGE) + { + if (_viewModel.IsSnapped) + { + // If the window has been maximized, un-snap the bar window and free-float it. + if (PInvoke.IsZoomed(TargetAppData.Instance.HWnd)) + { + _viewModel.IsSnapped = false; + } + else + { + // Reposition the window to match the moved/resized/minimized/restored target window. + // If the target window was maximized and has now been restored, we want + // to resnap to it, but not do all the other work we do when we resnap + // to a new window. + SnapToWindow(); + } + } + } + + // If the window we're watching closes, we unsnap + if (eventType == PInvoke.EVENT_OBJECT_DESTROY) + { + Unsnap(); + } + } + } + + private void WinFocusEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime) + { + // If we're snapped to a target window, and that window loses and then regains focus, + // we need to bring our window to the front also, to be in-sync. Otherwise, we can + // end up with the target in the foreground, but our window partially obscured. + if (hwnd == TargetAppData.Instance.HWnd && _viewModel.IsSnapped) + { + _viewModel.IsAlwaysOnTop = true; + _viewModel.IsAlwaysOnTop = false; + return; + } + } + + private void Snap() + { + Debug.Assert(_positionEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); + Debug.Assert(_focusEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); + + _positionEventHook = WindowHelper.WatchWindowPositionEvents(_winPositionEventDelegate, (uint)TargetAppData.Instance.ProcessId); + _focusEventHook = WindowHelper.WatchWindowFocusEvents(_winFocusEventDelegate, (uint)TargetAppData.Instance.ProcessId); + + SnapToWindow(); + } + + private void Unsnap() + { + // Set a gap from the associated app window to provide positive feedback. + this.MoveAndResize(AppWindow.Position.X + UnsnapGap, AppWindow.Position.Y, AppWindow.Size.Width, AppWindow.Size.Height); + + if (_positionEventHook != HWINEVENTHOOK.Null) + { + PInvoke.UnhookWinEvent(_positionEventHook); + _positionEventHook = HWINEVENTHOOK.Null; + } + + if (_focusEventHook != HWINEVENTHOOK.Null) + { + PInvoke.UnhookWinEvent(_focusEventHook); + _focusEventHook = HWINEVENTHOOK.Null; + } + } + + private void SnapToWindow() + { + Debug.Assert(_viewModel.IsSnapped, "We're not snapped!"); + + WindowHelper.SnapToWindow(TargetAppData.Instance.HWnd, ThisHwnd, AppWindow.Size); + + _viewModel.IsAlwaysOnTop = true; + _viewModel.IsAlwaysOnTop = false; + } + + public void Close() + { + if (_positionEventHook != HWINEVENTHOOK.Null) + { + PInvoke.UnhookWinEvent(_positionEventHook); + _positionEventHook = HWINEVENTHOOK.Null; + } + + if (_focusEventHook != HWINEVENTHOOK.Null) + { + PInvoke.UnhookWinEvent(_focusEventHook); + _focusEventHook = HWINEVENTHOOK.Null; + } + } +} diff --git a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs index 8f0a24dfb8..13d2314155 100644 --- a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs +++ b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs @@ -66,6 +66,9 @@ public partial class BarWindowViewModel : ObservableObject [ObservableProperty] private bool _showingExpandedContent; + [ObservableProperty] + private bool _isAlwaysOnTop = true; + public BarWindowViewModel() { _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); From dec93501c70e4f49a9256b4100253ba44d8c8974 Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Wed, 29 May 2024 17:11:41 -0700 Subject: [PATCH 02/14] passing vertical window to snaphelper --- tools/PI/DevHome.PI/BarWindow.cs | 2 +- tools/PI/DevHome.PI/BarWindowHorizontal.xaml | 2 +- tools/PI/DevHome.PI/BarWindowVertical.xaml | 5 ++--- tools/PI/DevHome.PI/Helpers/SnapHelper.cs | 10 +++++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index 3be12731fb..705d8f1e6e 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -81,7 +81,7 @@ public BarWindow() { _horizontalWindow = new BarWindowHorizontal(_viewModel); _verticalWindow = new BarWindowVertical(_viewModel); - _snapHelper = new(_viewModel); + _snapHelper = new(_viewModel, _verticalWindow); _horizontalWindow.Closed += Window_Closed; _verticalWindow.Closed += Window_Closed; diff --git a/tools/PI/DevHome.PI/BarWindowHorizontal.xaml b/tools/PI/DevHome.PI/BarWindowHorizontal.xaml index 06c36c5760..1cafc9b5f7 100644 --- a/tools/PI/DevHome.PI/BarWindowHorizontal.xaml +++ b/tools/PI/DevHome.PI/BarWindowHorizontal.xaml @@ -36,7 +36,7 @@ @@ -60,7 +60,7 @@ - + @@ -70,10 +70,10 @@ - - diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml b/tools/PI/DevHome.PI/BarWindowVertical.xaml index cb69fa71d9..118e22bec5 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml @@ -32,7 +32,7 @@ Style="{StaticResource ChromeButton}" HorizontalAlignment="Center" IsEnabled="{x:Bind _viewModel.IsSnappingEnabled, Mode=OneWay}" - Click="{x:Bind _viewModel.PerformSnapCommand}" > + Command="{x:Bind _viewModel.PerformSnapCommand}" > - diff --git a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs index 031b2f8f16..798ec825a1 100644 --- a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs +++ b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs @@ -138,6 +138,10 @@ private void SnapToWindow() PInvoke.GetWindowRect(TargetAppData.Instance.HWnd, out var rect); barWindow.UpdateBarWindowPosition(new PointInt32(rect.right - SnapOffsetHorizontal, rect.top)); - barWindow.ResetBarWindowVisibility(); + + if (TargetAppData.Instance.HWnd == PInvoke.GetForegroundWindow()) + { + barWindow.ResetBarWindowVisibility(); + } } } diff --git a/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs b/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs index 1c9c132fb8..482e8fb67c 100644 --- a/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs +++ b/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs @@ -41,7 +41,7 @@ public void ShowBarWindow() else { // Activate is unreliable so use SetForegroundWindow - PInvoke.SetForegroundWindow((HWND)DBarWindow.CurrentWindowHandle); + PInvoke.SetForegroundWindow(DBarWindow.CurrentHwnd); } } diff --git a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs index 09939e7ed4..bfbfa4b73d 100644 --- a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs +++ b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs @@ -131,7 +131,7 @@ partial void OnBarOrientationChanged(Orientation value) } [RelayCommand] - public void SwitchLayoutCommand() + public void SwitchLayout() { if (BarOrientation == Orientation.Horizontal) { @@ -144,7 +144,7 @@ public void SwitchLayoutCommand() } [RelayCommand] - public void PerformSnapCommand() + public void PerformSnap() { if (IsSnapped) { @@ -159,7 +159,7 @@ public void PerformSnapCommand() } [RelayCommand] - public void ShowBigWindowCommand() + public void ShowBigWindow() { if (!ShowingExpandedContent) { @@ -174,7 +174,7 @@ public void ShowBigWindowCommand() } [RelayCommand] - public void ProcessChooserCommand() + public void ProcessChooser() { // Need to be in a horizontal layout BarOrientation = Orientation.Horizontal; @@ -191,14 +191,17 @@ private void TargetApp_PropertyChanged(object? sender, PropertyChangedEventArgs { if (e.PropertyName == nameof(TargetAppData.HWnd)) { - IsSnappingEnabled = TargetAppData.Instance.HWnd != HWND.Null; - - // If snapped, retarget to the new window - if (IsSnapped) + _dispatcher.TryEnqueue(() => { - _snapHelper.Unsnap(); - _snapHelper.Snap(); - } + IsSnappingEnabled = TargetAppData.Instance.HWnd != HWND.Null; + + // If snapped, retarget to the new window + if (IsSnapped) + { + _snapHelper.Unsnap(); + _snapHelper.Snap(); + } + }); } else if (e.PropertyName == nameof(TargetAppData.TargetProcess)) { From 76e0217502050a45bc403a6f888a6364e5439174 Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Fri, 31 May 2024 16:04:42 -0700 Subject: [PATCH 09/14] add comments --- tools/PI/DevHome.PI/BarWindow.cs | 2 +- tools/PI/DevHome.PI/Helpers/SnapHelper.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index 58a50cedc5..e0372567ef 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -135,7 +135,7 @@ public void UpdateBarWindowPosition(PointInt32 position) _viewModel.WindowPosition = position; } - public void ResetBarWindowVisibility() + public void ResetBarWindowOnTop() { _viewModel.IsAlwaysOnTop = true; _viewModel.IsAlwaysOnTop = false; diff --git a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs index 798ec825a1..b67d6d1feb 100644 --- a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs +++ b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs @@ -120,7 +120,7 @@ private void WinFocusEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND Debug.Assert(barWindow != null, "BarWindow should not be null."); if (hwnd == TargetAppData.Instance.HWnd && barWindow.IsBarSnappedToWindow()) { - barWindow.ResetBarWindowVisibility(); + barWindow.ResetBarWindowOnTop(); return; } } @@ -131,6 +131,7 @@ private void SnapToWindow() Debug.Assert(barWindow != null, "BarWindow should not be null."); Debug.Assert(barWindow.IsBarSnappedToWindow(), "We're not snapped!"); + // If BarWindow is snapped to a TargetApp and BarWindow is in foreground, bring TargetApp to foreground. if (barWindow.CurrentHwnd == PInvoke.GetForegroundWindow()) { PInvoke.SetForegroundWindow(TargetAppData.Instance.HWnd); @@ -139,9 +140,10 @@ private void SnapToWindow() PInvoke.GetWindowRect(TargetAppData.Instance.HWnd, out var rect); barWindow.UpdateBarWindowPosition(new PointInt32(rect.right - SnapOffsetHorizontal, rect.top)); + // Only reset BarWindow on top, if TargetApp is in foreground. if (TargetAppData.Instance.HWnd == PInvoke.GetForegroundWindow()) { - barWindow.ResetBarWindowVisibility(); + barWindow.ResetBarWindowOnTop(); } } } From 0ccddf269ea52e03c1fb773442dd8a9d2848846a Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Mon, 3 Jun 2024 13:09:38 -0700 Subject: [PATCH 10/14] add comment --- tools/PI/DevHome.PI/BarWindow.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index e0372567ef..7211c61dfb 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -137,6 +137,12 @@ public void UpdateBarWindowPosition(PointInt32 position) public void ResetBarWindowOnTop() { + // If we're snapped to a target window, and that window loses and then regains focus, + // we need to bring our window to the front also, to be in-sync. Otherwise, we can + // end up with the target in the foreground, but our window partially obscured. + // We set IsAlwaysOnTop to true to get it in foreground and then set to false, + // this ensures we don't steal focus from target window and at the same time + // bar window is not partially obscured. _viewModel.IsAlwaysOnTop = true; _viewModel.IsAlwaysOnTop = false; } From 9919f63b85a86c5f99527d7b57ad00be7b55189b Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Mon, 3 Jun 2024 14:59:34 -0700 Subject: [PATCH 11/14] update based on PR feedback --- tools/PI/DevHome.PI/BarWindow.cs | 2 +- tools/PI/DevHome.PI/Helpers/SnapHelper.cs | 1 - .../ViewModels/BarWindowViewModel.cs | 20 +++++++++---------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index 7211c61dfb..d7599ec4df 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -149,7 +149,7 @@ public void ResetBarWindowOnTop() public void UnsnapBarWindow() { - _viewModel.IsSnapped = false; + _viewModel.UnsnapBarWindow(); } public bool IsBarSnappedToWindow() diff --git a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs index b67d6d1feb..74fc7865a8 100644 --- a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs +++ b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs @@ -129,7 +129,6 @@ private void SnapToWindow() { var barWindow = Application.Current.GetService().DBarWindow; Debug.Assert(barWindow != null, "BarWindow should not be null."); - Debug.Assert(barWindow.IsBarSnappedToWindow(), "We're not snapped!"); // If BarWindow is snapped to a TargetApp and BarWindow is in foreground, bring TargetApp to foreground. if (barWindow.CurrentHwnd == PInvoke.GetForegroundWindow()) diff --git a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs index bfbfa4b73d..48a754a069 100644 --- a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs +++ b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs @@ -104,16 +104,7 @@ public BarWindowViewModel() partial void OnIsSnappedChanged(bool value) { - if (IsSnapped) - { - CurrentSnapButtonText = _UnsnapButtonText; - _snapHelper.Snap(); - } - else - { - CurrentSnapButtonText = _SnapButtonText; - _snapHelper.Unsnap(); - } + CurrentSnapButtonText = IsSnapped ? _UnsnapButtonText : _SnapButtonText; } partial void OnBarOrientationChanged(Orientation value) @@ -143,17 +134,24 @@ public void SwitchLayout() } } + public void UnsnapBarWindow() + { + _snapHelper.Unsnap(); + IsSnapped = false; + } + [RelayCommand] public void PerformSnap() { if (IsSnapped) { - IsSnapped = false; + UnsnapBarWindow(); } else { // First need to be in a Vertical layout BarOrientation = Orientation.Vertical; + _snapHelper.Snap(); IsSnapped = true; } } From cac09c35b6cf3a3b6ca1088dcd80583ef80d5f61 Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Mon, 3 Jun 2024 15:14:58 -0700 Subject: [PATCH 12/14] moving ResetBarWindowOnTop to viewmodel --- tools/PI/DevHome.PI/BarWindow.cs | 9 +-------- tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tools/PI/DevHome.PI/BarWindow.cs b/tools/PI/DevHome.PI/BarWindow.cs index d7599ec4df..7c432873f9 100644 --- a/tools/PI/DevHome.PI/BarWindow.cs +++ b/tools/PI/DevHome.PI/BarWindow.cs @@ -137,14 +137,7 @@ public void UpdateBarWindowPosition(PointInt32 position) public void ResetBarWindowOnTop() { - // If we're snapped to a target window, and that window loses and then regains focus, - // we need to bring our window to the front also, to be in-sync. Otherwise, we can - // end up with the target in the foreground, but our window partially obscured. - // We set IsAlwaysOnTop to true to get it in foreground and then set to false, - // this ensures we don't steal focus from target window and at the same time - // bar window is not partially obscured. - _viewModel.IsAlwaysOnTop = true; - _viewModel.IsAlwaysOnTop = false; + _viewModel.ResetBarWindowOnTop(); } public void UnsnapBarWindow() diff --git a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs index 48a754a069..aee449f556 100644 --- a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs +++ b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs @@ -121,6 +121,18 @@ partial void OnBarOrientationChanged(Orientation value) } } + public void ResetBarWindowOnTop() + { + // If we're snapped to a target window, and that window loses and then regains focus, + // we need to bring our window to the front also, to be in-sync. Otherwise, we can + // end up with the target in the foreground, but our window partially obscured. + // We set IsAlwaysOnTop to true to get it in foreground and then set to false, + // this ensures we don't steal focus from target window and at the same time + // bar window is not partially obscured. + IsAlwaysOnTop = true; + IsAlwaysOnTop = false; + } + [RelayCommand] public void SwitchLayout() { From 124d2c3eabd3c4243bbe12c898a6e97782d2af7c Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Tue, 4 Jun 2024 15:33:21 -0700 Subject: [PATCH 13/14] PR feedback --- tools/PI/DevHome.PI/BarWindowHorizontal.xaml | 4 +- tools/PI/DevHome.PI/BarWindowVertical.xaml | 4 +- tools/PI/DevHome.PI/BarWindowVertical.xaml.cs | 2 +- tools/PI/DevHome.PI/Helpers/SnapHelper.cs | 59 +++++++++++-------- .../ViewModels/BarWindowViewModel.cs | 4 +- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/tools/PI/DevHome.PI/BarWindowHorizontal.xaml b/tools/PI/DevHome.PI/BarWindowHorizontal.xaml index 7dd676efdc..cd6dd8026c 100644 --- a/tools/PI/DevHome.PI/BarWindowHorizontal.xaml +++ b/tools/PI/DevHome.PI/BarWindowHorizontal.xaml @@ -38,7 +38,7 @@ @@ -73,7 +73,7 @@ - diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml b/tools/PI/DevHome.PI/BarWindowVertical.xaml index 118e22bec5..bf125d3cd6 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml @@ -32,7 +32,7 @@ Style="{StaticResource ChromeButton}" HorizontalAlignment="Center" IsEnabled="{x:Bind _viewModel.IsSnappingEnabled, Mode=OneWay}" - Command="{x:Bind _viewModel.PerformSnapCommand}" > + Command="{x:Bind _viewModel.ToggleSnapCommand}" > - diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs b/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs index 7ccac6380a..d3191b5984 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml.cs @@ -73,7 +73,7 @@ private void MainPanel_Loaded(object sender, RoutedEventArgs e) private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(BarWindowViewModel.WindowPosition)) + if (string.Equals(e.PropertyName, nameof(BarWindowViewModel.WindowPosition), StringComparison.OrdinalIgnoreCase)) { this.Move(_viewModel.WindowPosition.X, _viewModel.WindowPosition.Y); } diff --git a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs index 74fc7865a8..094c9b6b54 100644 --- a/tools/PI/DevHome.PI/Helpers/SnapHelper.cs +++ b/tools/PI/DevHome.PI/Helpers/SnapHelper.cs @@ -40,8 +40,8 @@ public SnapHelper() public void Snap() { - Debug.Assert(_positionEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); - Debug.Assert(_focusEventHook == HWINEVENTHOOK.Null, "Hook should be cleared"); + Debug.Assert(_positionEventHook == HWINEVENTHOOK.Null, "Hook should be null"); + Debug.Assert(_focusEventHook == HWINEVENTHOOK.Null, "Hook should be null"); _positionEventHook = WindowHelper.WatchWindowPositionEvents(_winPositionEventDelegate, (uint)TargetAppData.Instance.ProcessId); _focusEventHook = WindowHelper.WatchWindowFocusEvents(_winFocusEventDelegate, (uint)TargetAppData.Instance.ProcessId); @@ -79,46 +79,53 @@ private void WinPositionEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, H return; } - if (hwnd == TargetAppData.Instance.HWnd) + if (hwnd != TargetAppData.Instance.HWnd) { - if (eventType == PInvoke.EVENT_OBJECT_LOCATIONCHANGE) + return; + } + + if (eventType == PInvoke.EVENT_OBJECT_LOCATIONCHANGE) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + if (barWindow.IsBarSnappedToWindow()) { - var barWindow = Application.Current.GetService().DBarWindow; - Debug.Assert(barWindow != null, "BarWindow should not be null."); - if (barWindow.IsBarSnappedToWindow()) + // If the window has been maximized, un-snap the bar window and free-float it. + if (PInvoke.IsZoomed(TargetAppData.Instance.HWnd)) { - // If the window has been maximized, un-snap the bar window and free-float it. - if (PInvoke.IsZoomed(TargetAppData.Instance.HWnd)) - { - barWindow.UnsnapBarWindow(); - } - else - { - // Reposition the window to match the moved/resized/minimized/restored target window. - // If the target window was maximized and has now been restored, we want - // to resnap to it, but not do all the other work we do when we resnap - // to a new window. - SnapToWindow(); - } + barWindow.UnsnapBarWindow(); + } + else + { + // Reposition the window to match the moved/resized/minimized/restored target window. + // If the target window was maximized and has now been restored, we want + // to resnap to it, but not do all the other work we do when we resnap + // to a new window. + SnapToWindow(); } } + } - // If the window we're watching closes, we unsnap - if (eventType == PInvoke.EVENT_OBJECT_DESTROY) - { - Unsnap(); - } + // If the window we're watching closes, we unsnap + if (eventType == PInvoke.EVENT_OBJECT_DESTROY) + { + Unsnap(); } } private void WinFocusEventProc(HWINEVENTHOOK hWinEventHook, uint eventType, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime) { + if (hwnd != TargetAppData.Instance.HWnd) + { + return; + } + // If we're snapped to a target window, and that window loses and then regains focus, // we need to bring our window to the front also, to be in-sync. Otherwise, we can // end up with the target in the foreground, but our window partially obscured. var barWindow = Application.Current.GetService().DBarWindow; Debug.Assert(barWindow != null, "BarWindow should not be null."); - if (hwnd == TargetAppData.Instance.HWnd && barWindow.IsBarSnappedToWindow()) + if (barWindow.IsBarSnappedToWindow()) { barWindow.ResetBarWindowOnTop(); return; diff --git a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs index aee449f556..51dbef3cd3 100644 --- a/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs +++ b/tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs @@ -153,7 +153,7 @@ public void UnsnapBarWindow() } [RelayCommand] - public void PerformSnap() + public void ToggleSnap() { if (IsSnapped) { @@ -169,7 +169,7 @@ public void PerformSnap() } [RelayCommand] - public void ShowBigWindow() + public void ToggleExpandedContentVisibility() { if (!ShowingExpandedContent) { From 68ed7b14288b2b2ba4ecff9036a6d34b50e35370 Mon Sep 17 00:00:00 2001 From: Zarana Kiran Desai Date: Tue, 4 Jun 2024 19:13:57 -0700 Subject: [PATCH 14/14] update missed in main merge --- tools/PI/DevHome.PI/BarWindowVertical.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/PI/DevHome.PI/BarWindowVertical.xaml b/tools/PI/DevHome.PI/BarWindowVertical.xaml index 7a0b1bead8..dde57ec1b4 100644 --- a/tools/PI/DevHome.PI/BarWindowVertical.xaml +++ b/tools/PI/DevHome.PI/BarWindowVertical.xaml @@ -11,7 +11,8 @@ mc:Ignorable="d" Title="" MinHeight="700" MinWidth="70" MaxWidth="70" Width="70" Height="700" TaskBarIcon="Images/pi.ico" IsTitleBarVisible="False" - IsAlwaysOnTop="{x:Bind _viewModel.IsAlwaysOnTop, Mode=OneWay}"> + IsAlwaysOnTop="{x:Bind _viewModel.IsAlwaysOnTop, Mode=OneWay}" + Closed="WindowEx_Closed">