diff --git a/src/Files.App.CsWin32/Windows.Win32.Extras.cs b/src/Files.App.CsWin32/Windows.Win32.Extras.cs index c12b9bc62569..e626c5390633 100644 --- a/src/Files.App.CsWin32/Windows.Win32.Extras.cs +++ b/src/Files.App.CsWin32/Windows.Win32.Extras.cs @@ -16,5 +16,51 @@ namespace UI.WindowsAndMessaging { [UnmanagedFunctionPointer(CallingConvention.Winapi)] public delegate LRESULT WNDPROC(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam); + + /// Contains information about the size and position of a window. + /// + /// Learn more about this API from docs.microsoft.com. + /// + public partial struct WINDOWPOS + { + /// + /// Type: HWND A handle to the window. + /// Read more on docs.microsoft.com. + /// + internal HWND hwnd; + + /// + /// Type: HWND The position of the window in Z order (front-to-back position). This member can be a handle to the window behind which this window is placed, or can be one of the special values listed with the SetWindowPos function. + /// Read more on docs.microsoft.com. + /// + internal HWND hwndInsertAfter; + + /// + /// Type: int The position of the left edge of the window. + /// Read more on docs.microsoft.com. + /// + internal int x; + + /// + /// Type: int The position of the top edge of the window. + /// Read more on docs.microsoft.com. + /// + internal int y; + + /// + /// Type: int The window width, in pixels. + /// Read more on docs.microsoft.com. + /// + internal int cx; + + /// + /// Type: int The window height, in pixels. + /// Read more on docs.microsoft.com. + /// + internal int cy; + + /// Type: UINT + public SET_WINDOW_POS_FLAGS flags; + } } } diff --git a/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs b/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs index ff57f45abc32..a93c4b707a9f 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Input; using Microsoft.UI.Xaml; using System.Reflection; +using System.Runtime.InteropServices; using Windows.Win32; using Windows.Win32.UI.WindowsAndMessaging; @@ -58,5 +59,16 @@ public static void ChangeCursor(this UIElement uiElement, InputCursor cursor) [cursor] ); } + + /// + /// Force window to stay at bottom of other upper windows. + /// + /// The lParam of the message. + public static void ForceWindowPosition(nint lParam) + { + var windowPos = Marshal.PtrToStructure(lParam); + windowPos.flags |= SET_WINDOW_POS_FLAGS.SWP_NOZORDER; + Marshal.StructureToPtr(windowPos, lParam, false); + } } } diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index e80e55247649..3f08fdfbb482 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -19,6 +19,8 @@ public sealed partial class MainWindow : WinUIEx.WindowEx public static MainWindow Instance => _Instance ??= new(); public nint WindowHandle { get; } + private bool CanWindowToFront { get; set; } = true; + private readonly object _canWindowToFrontLock = new(); public MainWindow() { @@ -35,6 +37,8 @@ public MainWindow() AppWindow.TitleBar.ButtonPressedBackgroundColor = Colors.Transparent; AppWindow.TitleBar.ButtonHoverBackgroundColor = Colors.Transparent; AppWindow.SetIcon(AppLifecycleHelper.AppIconPath); + + WinUIEx.WindowManager.Get(this).WindowMessageReceived += WindowManager_WindowMessageReceived; } public void ShowSplashScreen() @@ -339,5 +343,28 @@ x.tabItem.NavigationParameter.NavigationParameter is PaneNavigationArguments pan } } } + + public bool SetCanWindowToFront(bool canWindowToFront) + { + lock (_canWindowToFrontLock) + { + if (CanWindowToFront != canWindowToFront) + { + CanWindowToFront = canWindowToFront; + return true; + } + return false; + } + } + + private const int WM_WINDOWPOSCHANGING = 0x0046; + private void WindowManager_WindowMessageReceived(object? sender, WinUIEx.Messaging.WindowMessageEventArgs e) + { + if ((!CanWindowToFront) && e.Message.MessageId == WM_WINDOWPOSCHANGING) + { + Win32Helper.ForceWindowPosition(e.Message.LParam); + e.Handled = true; + } + } } } diff --git a/src/Files.App/Views/Layouts/BaseLayoutPage.cs b/src/Files.App/Views/Layouts/BaseLayoutPage.cs index 4a78f5a34e13..0acc0707a171 100644 --- a/src/Files.App/Views/Layouts/BaseLayoutPage.cs +++ b/src/Files.App/Views/Layouts/BaseLayoutPage.cs @@ -66,6 +66,7 @@ public abstract class BaseLayoutPage : Page, IBaseLayoutPage, INotifyPropertyCha private CancellationTokenSource? groupingCancellationToken; private bool shiftPressed; + private bool itemDragging; private ListedItem? dragOverItem = null; private ListedItem? hoveredItem = null; @@ -1012,6 +1013,9 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti var storageItemList = orderedItems.Where(x => !(x.IsHiddenItem && x.IsLinkItem && x.IsRecycleBinItem && x.IsShortcut)).Select(x => VirtualStorageItem.FromListedItem(x)); e.Data.SetStorageItems(storageItemList, false); } + + MainWindow.Instance.SetCanWindowToFront(false); + itemDragging = true; } catch (Exception) { @@ -1019,6 +1023,13 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti } } + protected virtual void FileList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) + { + itemDragging = false; + MainWindow.Instance.SetCanWindowToFront(true); + // No need to bring the window to the front + } + private void Item_DragLeave(object sender, DragEventArgs e) { var item = GetItemFromElement(sender); @@ -1147,6 +1158,10 @@ protected void FileList_ContainerContentChanging(ListViewBase sender, ContainerC { RefreshContainer(args.ItemContainer, args.InRecycleQueue); RefreshItem(args.ItemContainer, args.Item, args.InRecycleQueue, args); + + itemDragging = false; + MainWindow.Instance.SetCanWindowToFront(true); + // No need to bring the window to the front } private void RefreshContainer(SelectorItem container, bool inRecycleQueue) @@ -1154,6 +1169,8 @@ private void RefreshContainer(SelectorItem container, bool inRecycleQueue) container.PointerPressed -= FileListItem_PointerPressed; container.PointerEntered -= FileListItem_PointerEntered; container.PointerExited -= FileListItem_PointerExited; + container.Tapped -= FileListItem_Tapped; + container.DoubleTapped -= FileListItem_DoubleTapped; container.RightTapped -= FileListItem_RightTapped; if (inRecycleQueue) @@ -1163,12 +1180,11 @@ private void RefreshContainer(SelectorItem container, bool inRecycleQueue) else { container.PointerPressed += FileListItem_PointerPressed; + container.PointerEntered += FileListItem_PointerEntered; + container.PointerExited += FileListItem_PointerExited; + container.Tapped += FileListItem_Tapped; + container.DoubleTapped += FileListItem_DoubleTapped; container.RightTapped += FileListItem_RightTapped; - if (UserSettingsService.FoldersSettingsService.SelectFilesOnHover) - { - container.PointerEntered += FileListItem_PointerEntered; - container.PointerExited += FileListItem_PointerExited; - } } } @@ -1200,6 +1216,10 @@ private void RefreshItem(SelectorItem container, object item, bool inRecycleQueu protected internal void FileListItem_PointerPressed(object sender, PointerRoutedEventArgs e) { + // Set can window to front and bring the window to the front if necessary + if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true)) + Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle)); + if (sender is not SelectorItem selectorItem) return; @@ -1225,6 +1245,10 @@ protected internal void FileListItem_PointerPressed(object sender, PointerRouted protected internal void FileListItem_PointerEntered(object sender, PointerRoutedEventArgs e) { + // Set can window to front before the item is dragged + if (sender is SelectorItem selectorItem && selectorItem.IsSelected) + MainWindow.Instance.SetCanWindowToFront(false); + if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover) return; @@ -1271,6 +1295,10 @@ selectedItems is not null && protected internal void FileListItem_PointerExited(object sender, PointerRoutedEventArgs e) { + // Set can window to front + if (!itemDragging) + MainWindow.Instance.SetCanWindowToFront(true); + if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover) return; @@ -1278,8 +1306,26 @@ protected internal void FileListItem_PointerExited(object sender, PointerRoutedE hoveredItem = null; } + protected void FileListItem_Tapped(object sender, TappedRoutedEventArgs e) + { + // Set can window to front and bring the window to the front if necessary + if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true)) + Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle)); + } + + protected void FileListItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) + { + // Set can window to front and bring the window to the front if necessary + if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true)) + Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle)); + } + protected void FileListItem_RightTapped(object sender, RightTappedRoutedEventArgs e) { + // Set can window to front and bring the window to the front if necessary + if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true)) + Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle)); + var rightClickedItem = GetItemFromElement(sender); if (rightClickedItem is not null && !((SelectorItem)sender).IsSelected) diff --git a/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml b/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml index aca14800c17f..b9d707b42174 100644 --- a/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml +++ b/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml @@ -183,6 +183,7 @@ CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}" ContainerContentChanging="FileList_ContainerContentChanging" DoubleTapped="FileList_DoubleTapped" + DragItemsCompleted="FileList_DragItemsCompleted" DragItemsStarting="FileList_DragItemsStarting" DragOver="ItemsLayout_DragOver" Drop="ItemsLayout_Drop" diff --git a/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml b/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml index 2c6c5d87bbdd..265bdf3e78a0 100644 --- a/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml +++ b/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml @@ -229,6 +229,7 @@ CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}" ContainerContentChanging="FileList_ContainerContentChanging" DoubleTapped="FileList_DoubleTapped" + DragItemsCompleted="FileList_DragItemsCompleted" DragItemsStarting="FileList_DragItemsStarting" DragOver="ItemsLayout_DragOver" Drop="ItemsLayout_Drop" diff --git a/src/Files.App/Views/Layouts/GridLayoutPage.xaml b/src/Files.App/Views/Layouts/GridLayoutPage.xaml index bb3ddef90e59..e4f61e8a851d 100644 --- a/src/Files.App/Views/Layouts/GridLayoutPage.xaml +++ b/src/Files.App/Views/Layouts/GridLayoutPage.xaml @@ -787,6 +787,7 @@ ContainerContentChanging="FileList_ContainerContentChanging" DoubleTapped="FileList_DoubleTapped" DragEnter="ItemsLayout_DragEnter" + DragItemsCompleted="FileList_DragItemsCompleted" DragItemsStarting="FileList_DragItemsStarting" DragLeave="ItemsLayout_DragLeave" DragOver="ItemsLayout_DragOver"