Skip to content

Code Quality: Migrate to InputNonClientPointerSource #14342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System.Threading;

namespace Files.App.Data.Parameters
{
Expand All @@ -16,7 +14,5 @@ public class PropertiesPageNavigationParameter
public IShellPage AppInstance;

public Window Window;

public AppWindow AppWindow;
}
}
180 changes: 35 additions & 145 deletions src/Files.App/Helpers/UI/DragZoneHelper.cs
Original file line number Diff line number Diff line change
@@ -1,169 +1,59 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Graphics;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using WinRT.Interop;

namespace Files.App.Helpers
{
public static class DragZoneHelper
{
/// <summary>
/// Get Scale Adjustment
/// </summary>
/// <param name="window"></param>
/// <returns>scale factor percent</returns>
public static double GetScaleAdjustment(Window window) => window.Content.XamlRoot.RasterizationScale;
public delegate int SetTitleBarDragRegionDelegate(InputNonClientPointerSource source, SizeInt32 size, double scaleFactor, Func<UIElement, RectInt32?, RectInt32> getScaledRect);

/// <summary>
/// Calculate dragging-zones of title bar<br/>
/// <strong>You MUST transform the rectangles with <see cref="GetScaleAdjustment"/> before calling <see cref="AppWindowTitleBar.SetDragRectangles"/></strong>
/// Informs the bearer to refresh the drag region.
/// will not set<see cref="NonClientRegionKind.LeftBorder"/>, <see cref="NonClientRegionKind.RightBorder"/>, <see cref="NonClientRegionKind.Caption"/>when titleBarHeight less than 0
/// </summary>
/// <param name="viewportWidth"></param>
/// <param name="dragZoneHeight"></param>
/// <param name="dragZoneLeftIndent"></param>
/// <param name="nonDraggingZones"></param>
/// <returns></returns>
public static IEnumerable<RectInt32> GetDragZones(int viewportWidth, int dragZoneHeight, int dragZoneLeftIndent, IEnumerable<RectInt32> nonDraggingZones)
/// <param name="element"></param>
/// <param name="window"></param>
/// <param name="setTitleBarDragRegion"></param>
public static void RaiseSetTitleBarDragRegion(this Window window, SetTitleBarDragRegionDelegate setTitleBarDragRegion)
{
var draggingZonesX = new List<Range> { new(dragZoneLeftIndent, viewportWidth) };
var draggingZonesY = new List<IEnumerable<Range>> { new[] { new Range(0, dragZoneHeight) } };

foreach (var nonDraggingZone in nonDraggingZones)
{
for (var i = 0; i < draggingZonesX.Count; ++i)
{
var x = draggingZonesX[i];
var y = draggingZonesY[i].ToArray();
var xSubtrahend = new Range(nonDraggingZone.X, nonDraggingZone.X + nonDraggingZone.Width);
var ySubtrahend = new Range(nonDraggingZone.Y, nonDraggingZone.Y + nonDraggingZone.Height);
var xResult = (x - xSubtrahend).ToArray();
if (xResult.Length is 1 && xResult[0] == x)
continue;
var yResult = (y - ySubtrahend).ToArray();
switch (xResult.Length)
{
case 0:
draggingZonesY[i] = yResult;
break;
case 1:
draggingZonesX.RemoveAt(i);
draggingZonesY.RemoveAt(i);
if (xResult[0].Lower == x.Lower)
{
draggingZonesY.InsertRange(i, new[] { y, yResult });
draggingZonesX.InsertRange(i, new[]
{
x with { Upper = xResult[0].Upper },
x with { Lower = xSubtrahend.Lower }
});
}
else // xResult[0].Upper == x.Upper
{
draggingZonesY.InsertRange(i, new[] { yResult, y });
draggingZonesX.InsertRange(i, new[]
{
x with { Upper = xSubtrahend.Upper },
x with { Lower = xResult[0].Lower }
});
}
++i;
break;
case 2:
draggingZonesX.RemoveAt(i);
draggingZonesY.RemoveAt(i);
draggingZonesY.InsertRange(i, new[] { y, yResult, y });
draggingZonesX.InsertRange(i, new[]
{
x with { Upper = xResult[0].Upper },
xSubtrahend,
x with { Lower = xResult[1].Lower }
});
++i;
++i;
break;
}
}
}

var rects = draggingZonesX
.SelectMany((rangeX, i) => draggingZonesY[i]
.Select(rangeY => new RectInt32(rangeX.Lower, rangeY.Lower, rangeX.Distance, rangeY.Distance)))
.OrderBy(t => t.Y)
.ThenBy(t => t.X).ToList();
for (var i = 0; i < rects.Count - 1; ++i)
if (!window.AppWindow.IsVisible)
return;
// UIElement.RasterizationScale is always 1
var source = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
var uiElement = window.Content;
var scaleFactor = uiElement.XamlRoot.RasterizationScale;
var size = window.AppWindow.Size;
// If the number of regions is 0 or 1, AppWindow will automatically reset to the default region next time, but if it is >=2, it will not and need to be manually cleared
source.ClearRegionRects(NonClientRegionKind.Passthrough);
var titleBarHeight = setTitleBarDragRegion(source, size, scaleFactor, GetScaledRect);
if (titleBarHeight >= 0)
{
var now = rects[i];
var next = rects[i + 1];
if (now.Height == next.Height && now.X + now.Width == next.X)
{
rects.RemoveRange(i, 2);
rects.Insert(i, now with { Width = now.Width + next.Width });
}
// region under the buttons
const int borderThickness = 5;
source.SetRegionRects(NonClientRegionKind.LeftBorder, [GetScaledRect(uiElement, new(0, 0, borderThickness, titleBarHeight))]);
source.SetRegionRects(NonClientRegionKind.RightBorder, [GetScaledRect(uiElement, new(size.Width, 0, borderThickness, titleBarHeight))]);
source.SetRegionRects(NonClientRegionKind.Caption, [GetScaledRect(uiElement, new(0, 0, size.Width, titleBarHeight))]);
}

return rects;
}

/// <summary>
/// Set dragging-zones of title bar
/// </summary>
/// <param name="window"></param>
/// <param name="dragZoneHeight"></param>
/// <param name="dragZoneLeftIndent"></param>
/// <param name="nonDraggingZones"></param>
public static void SetDragZones(Window window, int dragZoneHeight = 40, int dragZoneLeftIndent = 0, IEnumerable<RectInt32>? nonDraggingZones = null)
private static RectInt32 GetScaledRect(this UIElement uiElement, RectInt32? r = null)
{
var hWnd = WindowNative.GetWindowHandle(window);
var windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
var appWindow = AppWindow.GetFromWindowId(windowId);
var scaleAdjustment = GetScaleAdjustment(window);
var windowWidth = (int)(appWindow.Size.Width / scaleAdjustment);
nonDraggingZones ??= Array.Empty<RectInt32>();
#if DEBUG
// Subtract the toolbar area (center-top in window), only in DEBUG mode.
nonDraggingZones = nonDraggingZones.Concat(new RectInt32[] { new((windowWidth - DebugToolbarWidth) / 2, 0, DebugToolbarWidth, DebugToolbarHeight) });
#endif
appWindow.TitleBar.SetDragRectangles(
GetDragZones(windowWidth, dragZoneHeight, dragZoneLeftIndent, nonDraggingZones)
.Select(rect => new RectInt32(
(int)(rect.X * scaleAdjustment),
(int)(rect.Y * scaleAdjustment),
(int)(rect.Width * scaleAdjustment),
(int)(rect.Height * scaleAdjustment)))
.ToArray());
}

private const int DebugToolbarWidth = 217;
private const int DebugToolbarHeight = 25;
}

file record Range(int Lower, int Upper)
{
public int Distance => Upper - Lower;

private bool Intersects(Range other) => other.Lower <= Upper && other.Upper >= Lower;

public static IEnumerable<Range> operator -(Range minuend, Range subtrahend)
{
if (!minuend.Intersects(subtrahend))
if (r is { } rect)
{
yield return minuend;
yield break;
var scaleFactor = uiElement.XamlRoot.RasterizationScale;
return new((int)(rect.X * scaleFactor), (int)(rect.Y * scaleFactor), (int)(rect.Width * scaleFactor),
(int)(rect.Height * scaleFactor));
}
else
{
var pos = uiElement.TransformToVisual(null).TransformPoint(new(0, 0));
rect = new RectInt32((int)pos.X, (int)pos.Y, (int)uiElement.ActualSize.X, (int)uiElement.ActualSize.Y);
return GetScaledRect(uiElement, rect);
}
if (minuend.Lower < subtrahend.Lower)
yield return minuend with { Upper = subtrahend.Lower };
if (minuend.Upper > subtrahend.Upper)
yield return minuend with { Lower = subtrahend.Upper };
}

public static IEnumerable<Range> operator -(IEnumerable<Range> minuends, Range subtrahend)
=> minuends.SelectMany(minuend => minuend - subtrahend);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ public static void OpenPropertiesWindow(object item, IShellPage associatedInstan
{
Parameter = item,
AppInstance = associatedInstance,
AppWindow = appWindow,
Window = propertiesWindow
},
new SuppressNavigationTransitionInfo());
Expand Down
13 changes: 5 additions & 8 deletions src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@ public NavigationViewItemButtonStyleItem SelectedNavigationViewItem
if (SetProperty(ref _SelectedNavigationViewItem, value) &&
!_selectionChangedAutomatically)
{
var parameter = new PropertiesPageNavigationParameter()
var parameter = new PropertiesPageNavigationParameter
{
AppInstance = _parameter.AppInstance,
CancellationTokenSource = ChangedPropertiesCancellationTokenSource,
Parameter = _parameter.Parameter,
Window = Window,
AppWindow = AppWindow,
Window = Window
};

var page = value.ItemType switch
Expand Down Expand Up @@ -79,8 +78,7 @@ public NavigationViewItemButtonStyleItem SelectedNavigationViewItem

private readonly Window Window;

private readonly AppWindow AppWindow;

private AppWindow AppWindow => Window.AppWindow;
private readonly Frame _mainFrame;

private readonly BaseProperties _baseProperties;
Expand All @@ -93,12 +91,11 @@ public NavigationViewItemButtonStyleItem SelectedNavigationViewItem
public IAsyncRelayCommand SaveChangedPropertiesCommand { get; }
public IRelayCommand CancelChangedPropertiesCommand { get; }

public MainPropertiesViewModel(Window window, AppWindow appWindow, Frame mainFrame, BaseProperties baseProperties, PropertiesPageNavigationParameter parameter)
public MainPropertiesViewModel(Window window, Frame mainFrame, BaseProperties baseProperties, PropertiesPageNavigationParameter parameter)
{
ChangedPropertiesCancellationTokenSource = new();

Window = window;
AppWindow = appWindow;
_mainFrame = mainFrame;
_parameter = parameter;
_baseProperties = baseProperties;
Expand All @@ -108,7 +105,7 @@ public MainPropertiesViewModel(Window window, AppWindow appWindow, Frame mainFra
CancelChangedPropertiesCommand = new RelayCommand(ExecuteCancelChangedPropertiesCommand);

NavigationViewItems = PropertiesNavigationViewItemFactory.Initialize(parameter.Parameter);
SelectedNavigationViewItem = NavigationViewItems.Where(x => x.ItemType == PropertiesNavigationViewItemType.General).First();
SelectedNavigationViewItem = NavigationViewItems.First(x => x.ItemType == PropertiesNavigationViewItemType.General);
}

private void ExecuteDoBackwardNavigationCommand()
Expand Down
18 changes: 10 additions & 8 deletions src/Files.App/Views/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Windows.ApplicationModel;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation.Metadata;
using Windows.Graphics;
using Windows.Services.Store;
using WinRT.Interop;
using VirtualKey = Windows.System.VirtualKey;
Expand Down Expand Up @@ -121,7 +122,7 @@ private async Task AppRunningAsAdminPromptAsync()
// WINUI3
private ContentDialog SetContentDialogRoot(ContentDialog contentDialog)
{
if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

return contentDialog;
Expand All @@ -139,21 +140,20 @@ private void UserSettingsService_OnSettingChangedEvent(object? sender, SettingCh

private void HorizontalMultitaskingControl_Loaded(object sender, RoutedEventArgs e)
{
TabControl.DragArea.SizeChanged += (_, _) => SetRectDragRegion();

if (ViewModel.MultitaskingControl is not UserControls.TabBar.TabBar)
TabControl.DragArea.SizeChanged += (_, _) => MainWindow.Instance.RaiseSetTitleBarDragRegion(SetTitleBarDragRegion);
if (ViewModel.MultitaskingControl is not TabBar)
{
ViewModel.MultitaskingControl = TabControl;
ViewModel.MultitaskingControls.Add(TabControl);
ViewModel.MultitaskingControl.CurrentInstanceChanged += MultitaskingControl_CurrentInstanceChanged;
}
}

private void SetRectDragRegion()
private int SetTitleBarDragRegion(InputNonClientPointerSource source, SizeInt32 size, double scaleFactor, Func<UIElement, RectInt32?, RectInt32> getScaledRect)
{
DragZoneHelper.SetDragZones(
MainWindow.Instance,
dragZoneLeftIndent: (int)(TabControl.ActualWidth + TabControl.Margin.Left - TabControl.DragArea.ActualWidth));
var height = (int)TabControl.ActualHeight;
source.SetRegionRects(NonClientRegionKind.Passthrough, [getScaledRect(this, new RectInt32(0, 0, (int)(TabControl.ActualWidth + TabControl.Margin.Left - TabControl.DragArea.ActualWidth), height))]);
return height;
}

public async void TabItemContent_ContentChanged(object? sender, CustomTabViewItemParameter e)
Expand Down Expand Up @@ -289,6 +289,8 @@ protected override void OnLostFocus(RoutedEventArgs e)

private void Page_Loaded(object sender, RoutedEventArgs e)
{
MainWindow.Instance.AppWindow.Changed += (_, _) => MainWindow.Instance.RaiseSetTitleBarDragRegion(SetTitleBarDragRegion);

// Defers the status bar loading until after the page has loaded to improve startup perf
FindName(nameof(StatusBarControl));
FindName(nameof(InnerNavigationToolbar));
Expand Down
4 changes: 2 additions & 2 deletions src/Files.App/Views/Properties/CustomizationPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ protected override void OnNavigatedTo(NavigationEventArgs e)

base.OnNavigatedTo(e);

CustomizationViewModel = new(AppInstance, BaseProperties, parameter.AppWindow);
CustomizationViewModel = new(AppInstance, BaseProperties, parameter.Window.AppWindow);
}

public async override Task<bool> SaveChangesAsync()
public override async Task<bool> SaveChangesAsync()
=> await CustomizationViewModel.UpdateIcon();

public override void Dispose()
Expand Down
Loading