From 9188de99ad92cc86d4d2253796c4fbdd0b3ba68d Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:54:17 +0000 Subject: [PATCH] Restrict document window drag docking to chrome targets --- .../Controls/DocumentTabStrip.axaml.cs | 4 +++- .../Controls/HostWindow.axaml.cs | 2 ++ src/Dock.Avalonia/Internal/DockHelpers.cs | 5 +++++ src/Dock.Avalonia/Internal/HostWindowState.cs | 4 +++- .../Internal/WindowDragHelper.cs | 16 +++++++++++++- src/Dock.Settings/DockSettings.cs | 22 +++++++++++++++++++ 6 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs b/src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs index 2118f62ff..64325c5e9 100644 --- a/src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs +++ b/src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs @@ -16,6 +16,7 @@ using Avalonia.Styling; using Dock.Avalonia.Contract; using Dock.Avalonia.Automation.Peers; +using Dock.Settings; namespace Dock.Avalonia.Controls; @@ -631,7 +632,8 @@ private WindowDragHelper CreateDragHelper() DataContext is Dock.Model.Core.IDock { CanCloseLastDockable: false }; return baseAllow || overrideAllow; - }); + }, + getDockScope: _ => DockSettings.DocumentWindowDragScope); } private void AttachToWindow() diff --git a/src/Dock.Avalonia/Controls/HostWindow.axaml.cs b/src/Dock.Avalonia/Controls/HostWindow.axaml.cs index 09c5a24cf..9a27ae51b 100644 --- a/src/Dock.Avalonia/Controls/HostWindow.axaml.cs +++ b/src/Dock.Avalonia/Controls/HostWindow.axaml.cs @@ -87,6 +87,8 @@ public bool DocumentChromeControlsWholeWindow /// public IHostWindowState HostWindowState => _hostWindowState; + internal WindowDragDockScope WindowDragDockScope { get; set; } = WindowDragDockScope.FullWindow; + /// public bool IsTracked { get; set; } diff --git a/src/Dock.Avalonia/Internal/DockHelpers.cs b/src/Dock.Avalonia/Internal/DockHelpers.cs index 904b35434..e44456239 100644 --- a/src/Dock.Avalonia/Internal/DockHelpers.cs +++ b/src/Dock.Avalonia/Internal/DockHelpers.cs @@ -210,6 +210,11 @@ public static void LogDropAreas(Visual? root, string context) return localHit; } + return GetExternalControl(dockControl, point, property); + } + + public static Control? GetExternalControl(DockControl dockControl, Point point, StyledProperty property) + { if (dockControl.GetVisualRoot() is not Visual visualRoot) { return null; diff --git a/src/Dock.Avalonia/Internal/HostWindowState.cs b/src/Dock.Avalonia/Internal/HostWindowState.cs index 1c6e4a84c..10c39637f 100644 --- a/src/Dock.Avalonia/Internal/HostWindowState.cs +++ b/src/Dock.Avalonia/Internal/HostWindowState.cs @@ -396,7 +396,9 @@ public void Process(PixelPoint point, EventType eventType) } var dockControlPoint = dockControl.PointToClient(screenPoint); - var dropControl = DockHelpers.GetControlIncludingExternal(dockControl, dockControlPoint, DockProperties.IsDropAreaProperty); + var dropControl = _hostWindow.WindowDragDockScope == WindowDragDockScope.WindowChrome + ? DockHelpers.GetExternalControl(dockControl, dockControlPoint, DockProperties.IsDropAreaProperty) + : DockHelpers.GetControlIncludingExternal(dockControl, dockControlPoint, DockProperties.IsDropAreaProperty); if (dropControl is { }) { var isDropEnabled = dropControl.GetValue(DockProperties.IsDropEnabledProperty); diff --git a/src/Dock.Avalonia/Internal/WindowDragHelper.cs b/src/Dock.Avalonia/Internal/WindowDragHelper.cs index 7b36dbf52..c2f6a00b8 100644 --- a/src/Dock.Avalonia/Internal/WindowDragHelper.cs +++ b/src/Dock.Avalonia/Internal/WindowDragHelper.cs @@ -20,23 +20,31 @@ internal class WindowDragHelper private readonly Control _owner; private readonly Func _isEnabled; private readonly Func _canStartDrag; + private readonly Func _getDockScope; private readonly bool _handlePointerPressed; private bool _handledPointerPressed; private Point _dragStartPoint; private bool _pointerPressed; private bool _isDragging; + private WindowDragDockScope _dockScope = WindowDragDockScope.FullWindow; private PointerPressedEventArgs? _lastPointerPressedArgs; private Window? _dragWindow; private EventHandler? _positionChangedHandler; private IDisposable[]? _disposables; private IDisposable? _releasedEventDisposable; - public WindowDragHelper(Control owner, Func isEnabled, Func canStartDrag, bool handlePointerPressed = true) + public WindowDragHelper( + Control owner, + Func isEnabled, + Func canStartDrag, + bool handlePointerPressed = true, + Func? getDockScope = null) { _owner = owner; _isEnabled = isEnabled; _canStartDrag = canStartDrag; _handlePointerPressed = handlePointerPressed; + _getDockScope = getDockScope ?? (_ => WindowDragDockScope.FullWindow); } public void Attach() @@ -74,6 +82,7 @@ public void Detach() _pointerPressed = false; _isDragging = false; _handledPointerPressed = false; + _dockScope = WindowDragDockScope.FullWindow; _lastPointerPressedArgs = null; } @@ -97,6 +106,7 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { _dragStartPoint = e.GetPosition(_owner); _pointerPressed = true; + _dockScope = _getDockScope(source); if (_handlePointerPressed) { e.Handled = true; @@ -135,6 +145,8 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) if (_dragWindow is HostWindow hostWindow) { + hostWindow.WindowDragDockScope = WindowDragDockScope.FullWindow; + if (hostWindow.HostWindowState is HostWindowState state) { var point = hostWindow.PointToScreen(e.GetPosition(hostWindow)) - @@ -147,6 +159,7 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) } _dragWindow = null; + _dockScope = WindowDragDockScope.FullWindow; if (shouldHandleRelease) { @@ -221,6 +234,7 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) } _dragWindow = hostWindow; + hostWindow.WindowDragDockScope = _dockScope; _positionChangedHandler = (_, _) => { diff --git a/src/Dock.Settings/DockSettings.cs b/src/Dock.Settings/DockSettings.cs index d2bc1fd6d..8b162977e 100644 --- a/src/Dock.Settings/DockSettings.cs +++ b/src/Dock.Settings/DockSettings.cs @@ -74,6 +74,12 @@ public static class DockSettings /// public static bool BringWindowsToFrontOnDrag = true; + /// + /// Controls which background-window docking targets are considered when a floating document + /// window is dragged by its document tab strip. + /// + public static WindowDragDockScope DocumentWindowDragScope = WindowDragDockScope.WindowChrome; + /// /// Close all floating windows when the main (non-host) window closes. /// @@ -262,3 +268,19 @@ public enum DockCommandBarMergingScope /// ActiveDockable } + +/// +/// Controls which docking targets are considered during window move-drag operations. +/// +public enum WindowDragDockScope +{ + /// + /// Consider all window docking targets, including the main dock area. + /// + FullWindow, + + /// + /// Consider only chrome-like targets such as external tab strips and titlebars. + /// + WindowChrome +}