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
+}