Skip to content
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
22 changes: 22 additions & 0 deletions docfx/articles/dock-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ property instead.
is dropped as a global dock target (for example, dropping into a different window).
The default value is `0.5`.

`DockSettings.GlobalDockingPreset` controls how global docking decisions are made.
The default value is `DockGlobalDockingPreset.GlobalFirst`.

- `DockGlobalDockingPreset.LocalFirst`:
- Uses legacy local-first operation selection.
- Resolves global target from the immediate drop context.
- `DockGlobalDockingPreset.GlobalFirst`:
- Uses global-first operation selection when a global target is active.
- Resolves global target to the outermost global target in the owner chain.

Example:

```csharp
DockSettings.GlobalDockingPreset = DockGlobalDockingPreset.GlobalFirst;
DockSettings.GlobalDockingProportion = 0.33;
```

Set these before docking interactions start (typically during app startup).

## Floating window owner

`DockSettings.UseOwnerForFloatingWindows` keeps floating windows above the main window by setting it as their owner. This is applied when `IDockWindow.OwnerMode` is `DockWindowOwnerMode.Default` and can be overridden per window.
Expand Down Expand Up @@ -235,6 +254,9 @@ AppBuilder.Configure<App>()
| `CommandBarMergingEnabled` | `DockSettings.CommandBarMergingEnabled` | Enable command bar merging. |
| `CommandBarMergingScope` | `DockSettings.CommandBarMergingScope` | Merge scope for command bars. |

`GlobalDockingPreset` is currently configured directly on `DockSettings`
rather than through `DockSettingsOptions`.

## Hide on close

`FactoryBase` exposes two properties that control whether closing a tool or
Expand Down
2 changes: 2 additions & 0 deletions docfx/articles/dock-targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The default themes define template parts like `PART_TopIndicator` and `PART_TopS

To customize global targets, edit the `GlobalDockTarget` template. Its indicator panels use `DockSettings.GlobalDockingProportion` to size the split areas.

Global docking behavior can be switched between legacy local-first and modern global-first modes using `DockSettings.GlobalDockingPreset`.

## Adaptive global targets

`IRootDock.EnableAdaptiveGlobalDockTargets` reduces global docking indicators to only show options that change the layout. Enable it when you want to minimize drop choices in large dashboards:
Expand Down
162 changes: 94 additions & 68 deletions src/Dock.Avalonia/Internal/DockControlState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ internal class DockDragContext
public bool DoDragDrop { get; set; }
public Point TargetPoint { get; set; }
public Visual? TargetDockControl { get; set; }
public DockOperation ResolvedOperation { get; set; }
public bool UseGlobalOperation { get; set; }
public bool HasResolvedOperation { get; set; }

public PixelPoint DragOffset { get; set; }

Expand All @@ -32,6 +35,7 @@ public void Start(Control dragControl, Point point)
DoDragDrop = false;
TargetPoint = default;
TargetDockControl = null;
ClearResolvedOperation();
}

public void End()
Expand All @@ -42,6 +46,14 @@ public void End()
DoDragDrop = false;
TargetPoint = default;
TargetDockControl = null;
ClearResolvedOperation();
}

public void ClearResolvedOperation()
{
ResolvedOperation = DockOperation.None;
UseGlobalOperation = false;
HasResolvedOperation = false;
}
}

Expand All @@ -50,6 +62,13 @@ public void End()
/// </summary>
internal class DockControlState : DockManagerState, IDockControlState
{
private readonly record struct DockOperationResolution(
DockOperation LocalOperation,
DockOperation GlobalOperation,
bool UseGlobalOperation,
DockOperation SelectedOperation,
bool IsValid);

private readonly DockDragContext _context = new();
private readonly DragPreviewHelper _dragPreviewHelper = new();

Expand Down Expand Up @@ -117,14 +136,29 @@ private void Enter(Point point, DragAction dragAction, Visual relativeTo)
AddAdorners(isLocalValid, isGlobalValid);
}

private void Over(Point point, DragAction dragAction, Control dropControl, Visual relativeTo)
private DockOperationResolution Over(Point point, DragAction dragAction, Control dropControl, Visual relativeTo)
{
var resolution = ResolveDockOperation(point, dragAction, dropControl, relativeTo, updateAdornerState: true);
_context.ResolvedOperation = resolution.SelectedOperation;
_context.UseGlobalOperation = resolution.UseGlobalOperation;
_context.HasResolvedOperation = resolution.SelectedOperation != DockOperation.None;
return resolution;
}

private DockOperationResolution ResolveDockOperation(
Point point,
DragAction dragAction,
Control dropControl,
Visual relativeTo,
bool updateAdornerState)
{
var localOperation = DockOperation.Fill;
var globalOperation = DockOperation.None;
var hasLocalAdorner = LocalAdornerHelper.Adorner is DockTarget;
var hasLocalAdorner = false;

if (LocalAdornerHelper.Adorner is DockTarget dockTarget)
{
hasLocalAdorner = true;
localOperation = dockTarget.GetDockOperation(point, dropControl, relativeTo, dragAction, ValidateLocal, IsDockTargetVisible);
}

Expand All @@ -137,39 +171,37 @@ private void Over(Point point, DragAction dragAction, Control dropControl, Visua
hasLocalAdorner,
localOperation,
globalOperation);
if (useGlobalOperation)
{
ValidateGlobal(point, globalOperation, dragAction, relativeTo);
}
else
var selectedOperation = useGlobalOperation ? globalOperation : localOperation;
var isValid = useGlobalOperation
? ValidateGlobal(point, globalOperation, dragAction, relativeTo)
: ValidateLocal(point, localOperation, dragAction, relativeTo);

if (updateAdornerState)
{
ValidateLocal(point, localOperation, dragAction, relativeTo);
LocalAdornerHelper.SetGlobalDockActive(useGlobalOperation);
}

LocalAdornerHelper.SetGlobalDockActive(useGlobalOperation);
return new DockOperationResolution(
localOperation,
globalOperation,
useGlobalOperation,
selectedOperation,
isValid);
}

private void Drop(Point point, DragAction dragAction, Control dropControl, Visual relativeTo)
private void Drop(
Point point,
DragAction dragAction,
Control dropControl,
Visual relativeTo,
bool useGlobalOperation,
DockOperation selectedOperation)
{
var localOperation = DockOperation.Fill;
var globalOperation = DockOperation.None;
var hasLocalAdorner = LocalAdornerHelper.Adorner is DockTarget;

if (LocalAdornerHelper.Adorner is DockTarget dockTarget)
if (selectedOperation == DockOperation.None)
{
localOperation = dockTarget.GetDockOperation(point, dropControl, relativeTo, dragAction, ValidateLocal, IsDockTargetVisible);
}

if (GlobalAdornerHelper.Adorner is GlobalDockTarget globalDockTarget)
{
globalOperation = globalDockTarget.GetDockOperation(point, dropControl, relativeTo, dragAction, ValidateGlobal, IsDockTargetVisible);
return;
}

var useGlobalOperation = GlobalDocking.ShouldUseGlobalOperation(
hasLocalAdorner,
localOperation,
globalOperation);

RemoveAdorners();

if (_context.DragControl is null || DropControl is null)
Expand All @@ -191,7 +223,7 @@ private void Drop(Point point, DragAction dragAction, Control dropControl, Visua
var targetRoot = targetDock.Factory?.FindRoot(targetDock, _ => true);

// Validate before executing global docking; if validation fails, fall back to floating when possible.
if (!ValidateGlobal(point, globalOperation, dragAction, relativeTo))
if (!ValidateGlobal(point, selectedOperation, dragAction, relativeTo))
{
if (CanFloatDockable(sourceDockable))
{
Expand All @@ -214,7 +246,7 @@ private void Drop(Point point, DragAction dragAction, Control dropControl, Visua
// return;
// }

Execute(point, globalOperation, dragAction, relativeTo, sourceDockable, targetDock);
Execute(point, selectedOperation, dragAction, relativeTo, sourceDockable, targetDock);

GlobalDocking.TryApplyGlobalDockingProportion(
sourceDockable,
Expand Down Expand Up @@ -251,13 +283,14 @@ private void Drop(Point point, DragAction dragAction, Control dropControl, Visua
return;
}

Execute(point, localOperation, dragAction, relativeTo, sourceDockable, target);
Execute(point, selectedOperation, dragAction, relativeTo, sourceDockable, target);
}
}
}

private void Leave()
{
_context.ClearResolvedOperation();
RemoveAdorners();
}

Expand Down Expand Up @@ -468,7 +501,21 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
var isDropEnabled = dropControl.GetValue(DockProperties.IsDropEnabledProperty);
if (isDropEnabled)
{
Drop(_context.TargetPoint, dragAction, dropControl, _context.TargetDockControl);
var useGlobalOperation = _context.UseGlobalOperation;
var selectedOperation = _context.ResolvedOperation;
if (!_context.HasResolvedOperation)
{
var resolution = ResolveDockOperation(
_context.TargetPoint,
dragAction,
dropControl,
_context.TargetDockControl,
updateAdornerState: false);
useGlobalOperation = resolution.UseGlobalOperation;
selectedOperation = resolution.SelectedOperation;
}

Drop(_context.TargetPoint, dragAction, dropControl, _context.TargetDockControl, useGlobalOperation, selectedOperation);
executed = true;
LogDragState($"Drop executed on '{dropControl.GetType().Name}' with action '{dragAction}'.");
}
Expand Down Expand Up @@ -594,11 +641,12 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
var isDropEnabled = dropControl.GetValue(DockProperties.IsDropEnabledProperty);
if (isDropEnabled)
{
DockOperationResolution resolution;
if (DropControl == dropControl)
{
_context.TargetPoint = targetPoint;
_context.TargetDockControl = targetDockControl;
Over(targetPoint, dragAction, dropControl, targetDockControl);
resolution = Over(targetPoint, dragAction, dropControl, targetDockControl);
LogDragState($"Dragging over '{dropControl.GetType().Name}' at {targetPoint}.");
}
else
Expand All @@ -614,46 +662,22 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
_context.TargetPoint = targetPoint;
_context.TargetDockControl = targetDockControl;
Enter(targetPoint, dragAction, targetDockControl);
resolution = Over(targetPoint, dragAction, dropControl, targetDockControl);
LogDragState($"New drop control '{dropControl.GetType().Name}' at {targetPoint}.");
}

var globalOperation = GlobalAdornerHelper.Adorner is GlobalDockTarget globalDockTarget
? globalDockTarget.GetDockOperation(targetPoint, dropControl, targetDockControl, dragAction, ValidateGlobal, IsDockTargetVisible)
: DockOperation.None;

var hasLocalAdorner = false;
DockOperation localOperation;
if (LocalAdornerHelper.Adorner is DockTarget localDockTarget)
{
hasLocalAdorner = true;
localOperation = localDockTarget.GetDockOperation(
targetPoint, dropControl, targetDockControl, dragAction, ValidateLocal, IsDockTargetVisible);
}
else
{
localOperation = DockOperation.Fill;
}
var useGlobalOperation = GlobalDocking.ShouldUseGlobalOperation(
hasLocalAdorner,
localOperation,
globalOperation);

LogDragState($"Operations resolved: global={globalOperation}, local={localOperation}.");

if (useGlobalOperation)
{
var valid = ValidateGlobal(targetPoint, globalOperation, dragAction, targetDockControl);
preview = valid ? "Dock" : "None";
LogDragState($"Global validation {(valid ? "succeeded" : "failed")} for operation {globalOperation}.");
}
else
{
var valid = ValidateLocal(targetPoint, localOperation, dragAction, targetDockControl);
preview = valid
? localOperation == DockOperation.Window ? "Float" : "Dock"
: "None";
LogDragState($"Local validation {(valid ? "succeeded" : "failed")} for operation {localOperation}.");
}
LogDragState($"Operations resolved: global={resolution.GlobalOperation}, local={resolution.LocalOperation}, selected={resolution.SelectedOperation}, useGlobal={resolution.UseGlobalOperation}.");

preview = resolution.IsValid
? (resolution.UseGlobalOperation
? "Dock"
: resolution.SelectedOperation == DockOperation.Window
? "Float"
: "Dock")
: "None";
LogDragState(
$"{(resolution.UseGlobalOperation ? "Global" : "Local")} validation " +
$"{(resolution.IsValid ? "succeeded" : "failed")} for operation {resolution.SelectedOperation}.");
}
else
{
Expand All @@ -664,6 +688,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
DropControl = null;
_context.TargetPoint = default;
_context.TargetDockControl = null;
_context.ClearResolvedOperation();
LogDragState("Cleared drop control due to disabled target.");
}
}
Expand All @@ -674,6 +699,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
DropControl = null;
_context.TargetPoint = default;
_context.TargetDockControl = null;
_context.ClearResolvedOperation();
LogDragState($"No valid drop target at current position (local={point}, screen={screenPoint}); cleared drop context.");
var canFloat = _context.DragControl?.DataContext is IDockable sourceDockable && CanFloatDockable(sourceDockable);
preview = canFloat ? "Float" : "None";
Expand Down
Loading
Loading