feat(ui): replace OLE task-row drag with custom ghost drag
Task rows now drive a hand-built pointer-capture drag instead of DragDrop.DoDragDropAsync: armed on press, begins past a 4px threshold so a plain click still selects. The ghost follows the screen cursor across windows; on release the action is decided by what is under the cursor -- over the Mission Control window queues the task (geometric DragHitTest, no OLE drop), over another row in the same user list reorders, anywhere else cancels and restores the row. Drag starts from any list kind (drag-to-queue everywhere) but reorder-on-drop stays gated on CanReorder. Removes the obsolete OLE TaskRowFormat path from both the source and MissionControlView (pane PaneFormat reorder is untouched).
This commit is contained in:
@@ -95,13 +95,7 @@
|
|||||||
<Button Classes="flat" HorizontalAlignment="Stretch"
|
<Button Classes="flat" HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}">
|
||||||
PointerPressed="OnRowPointerPressed"
|
|
||||||
PointerMoved="OnRowPointerMoved"
|
|
||||||
PointerReleased="OnRowPointerReleased"
|
|
||||||
DragDrop.AllowDrop="True"
|
|
||||||
DragDrop.DragOver="OnRowDragOver"
|
|
||||||
DragDrop.Drop="OnRowDrop">
|
|
||||||
<islands:TaskRowView/>
|
<islands:TaskRowView/>
|
||||||
</Button>
|
</Button>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -120,13 +114,7 @@
|
|||||||
<Button Classes="flat" HorizontalAlignment="Stretch"
|
<Button Classes="flat" HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}">
|
||||||
PointerPressed="OnRowPointerPressed"
|
|
||||||
PointerMoved="OnRowPointerMoved"
|
|
||||||
PointerReleased="OnRowPointerReleased"
|
|
||||||
DragDrop.AllowDrop="True"
|
|
||||||
DragDrop.DragOver="OnRowDragOver"
|
|
||||||
DragDrop.Drop="OnRowDrop">
|
|
||||||
<islands:TaskRowView/>
|
<islands:TaskRowView/>
|
||||||
</Button>
|
</Button>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -159,13 +147,7 @@
|
|||||||
<Button Classes="flat" HorizontalAlignment="Stretch"
|
<Button Classes="flat" HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).SelectCommand}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}">
|
||||||
PointerPressed="OnRowPointerPressed"
|
|
||||||
PointerMoved="OnRowPointerMoved"
|
|
||||||
PointerReleased="OnRowPointerReleased"
|
|
||||||
DragDrop.AllowDrop="True"
|
|
||||||
DragDrop.DragOver="OnRowDragOver"
|
|
||||||
DragDrop.Drop="OnRowDrop">
|
|
||||||
<islands:TaskRowView/>
|
<islands:TaskRowView/>
|
||||||
</Button>
|
</Button>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -1,28 +1,43 @@
|
|||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
using ClaudeDo.Ui.ViewModels;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals;
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
|
using ClaudeDo.Ui.Views.Controls;
|
||||||
|
using ClaudeDo.Ui.Views.MissionControl;
|
||||||
using ClaudeDo.Ui.Views.Modals;
|
using ClaudeDo.Ui.Views.Modals;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.Islands;
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
public partial class TasksIslandView : UserControl
|
public partial class TasksIslandView : UserControl
|
||||||
{
|
{
|
||||||
// Public so the Mission Control window can accept the same drag payload (drop-to-queue).
|
private readonly TaskDragController _drag = new();
|
||||||
public static readonly DataFormat<string> TaskRowFormat =
|
|
||||||
DataFormat.CreateStringApplicationFormat("claudedo-task-row");
|
// Custom-drag gesture state. The drag is ARMED on press and BEGINS once the pointer moves
|
||||||
|
// past the threshold, so a plain click still selects the row.
|
||||||
|
private const double DragThreshold = 4;
|
||||||
|
private Point _pressPoint;
|
||||||
|
private TaskRowViewModel? _pressRow;
|
||||||
|
private Control? _pressControl;
|
||||||
|
private bool _dragArmed;
|
||||||
|
private bool _dragging;
|
||||||
|
|
||||||
public TasksIslandView()
|
public TasksIslandView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel);
|
AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel);
|
||||||
|
AddHandler(PointerMovedEvent, OnPointerMovedDrag, RoutingStrategies.Tunnel);
|
||||||
|
AddHandler(PointerReleasedEvent, OnPointerReleasedDrag, RoutingStrategies.Tunnel);
|
||||||
|
AddHandler(PointerCaptureLostEvent, OnPointerCaptureLost);
|
||||||
DataContextChanged += (_, _) =>
|
DataContextChanged += (_, _) =>
|
||||||
{
|
{
|
||||||
if (DataContext is TasksIslandViewModel vm)
|
if (DataContext is TasksIslandViewModel vm)
|
||||||
@@ -103,9 +118,15 @@ public partial class TasksIslandView : UserControl
|
|||||||
return await tcs.Task;
|
return await tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnTunnelPointerPressed(object? sender, PointerPressedEventArgs e)
|
// ── Custom ghost drag ────────────────────────────────────────────────────
|
||||||
|
// Replaces both the OLE DoDragDropAsync reorder and the OLE drop-to-queue path: a hand-built
|
||||||
|
// drag (pointer capture + a transparent topmost ghost window) is the only way to get a
|
||||||
|
// translucent follower that crosses from this window into the separate Mission Control window.
|
||||||
|
|
||||||
|
private void OnTunnelPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not TasksIslandViewModel vm) return;
|
ResetPressState();
|
||||||
|
if (DataContext is not TasksIslandViewModel) return;
|
||||||
if (e.Source is not Visual src) return;
|
if (e.Source is not Visual src) return;
|
||||||
|
|
||||||
var button = src as Button ?? src.FindAncestorOfType<Button>();
|
var button = src as Button ?? src.FindAncestorOfType<Button>();
|
||||||
@@ -113,8 +134,7 @@ public partial class TasksIslandView : UserControl
|
|||||||
if (!e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) return;
|
if (!e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) return;
|
||||||
|
|
||||||
// Select now so the details pane updates whether the gesture becomes a click or a drag.
|
// Select now so the details pane updates whether the gesture becomes a click or a drag.
|
||||||
// (Button.Click doesn't fire once DoDragDropAsync captures the pointer.)
|
if (DataContext is TasksIslandViewModel vm) vm.SelectedTask = row;
|
||||||
vm.SelectedTask = row;
|
|
||||||
|
|
||||||
// If the click landed on a nested Button (e.g. the done-toggle checkbox or star),
|
// If the click landed on a nested Button (e.g. the done-toggle checkbox or star),
|
||||||
// don't start a drag — that would capture the pointer and swallow the inner Click.
|
// don't start a drag — that would capture the pointer and swallow the inner Click.
|
||||||
@@ -122,79 +142,171 @@ public partial class TasksIslandView : UserControl
|
|||||||
&& parentVisual.FindAncestorOfType<Button>() is not null;
|
&& parentVisual.FindAncestorOfType<Button>() is not null;
|
||||||
if (nestedInsideButton) return;
|
if (nestedInsideButton) return;
|
||||||
|
|
||||||
if (!vm.CanReorder || row.IsRunning) return;
|
// Running tasks can be neither reordered nor re-queued.
|
||||||
|
if (row.IsRunning) return;
|
||||||
|
|
||||||
var data = new DataTransfer();
|
// Arm the drag for ANY list kind so drag-to-queue works everywhere; reorder-on-drop is
|
||||||
data.Add(DataTransferItem.Create(TaskRowFormat, row.Id));
|
// still gated on CanReorder (user lists only).
|
||||||
try
|
_pressPoint = e.GetPosition(this);
|
||||||
|
_pressRow = row;
|
||||||
|
_pressControl = button;
|
||||||
|
_dragArmed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerMovedDrag(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragArmed && !_dragging) return;
|
||||||
|
if (TopLevel.GetTopLevel(this) is not { } topLevel) return;
|
||||||
|
|
||||||
|
if (_dragArmed && !_dragging)
|
||||||
{
|
{
|
||||||
await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);
|
var p = e.GetPosition(this);
|
||||||
|
if (Math.Abs(p.X - _pressPoint.X) < DragThreshold && Math.Abs(p.Y - _pressPoint.Y) < DragThreshold)
|
||||||
|
return;
|
||||||
|
BeginDrag(e, topLevel);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (_dragging)
|
||||||
{
|
{
|
||||||
vm.ClearDropHints();
|
_drag.MoveTo(this.PointToScreen(e.GetPosition(this)));
|
||||||
|
UpdateReorderHint(e, topLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRowPointerPressed(object? sender, PointerPressedEventArgs e) { }
|
private void BeginDrag(PointerEventArgs e, TopLevel topLevel)
|
||||||
private void OnRowPointerMoved(object? sender, PointerEventArgs e) { }
|
|
||||||
private void OnRowPointerReleased(object? sender, PointerReleasedEventArgs e) { }
|
|
||||||
|
|
||||||
private void OnRowDragOver(object? sender, DragEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (DataContext is not TasksIslandViewModel vm) { e.DragEffects = DragDropEffects.None; return; }
|
if (_pressControl is null || _pressRow is null) return;
|
||||||
if (!e.DataTransfer?.Contains(TaskRowFormat) ?? true)
|
// Snapshot the row BEFORE applying the "grabbed" style so the ghost stays crisp.
|
||||||
|
_drag.Begin(_pressControl, e.GetPosition(_pressControl), topLevel.RenderScaling);
|
||||||
|
_pressRow.IsDragging = true;
|
||||||
|
_dragging = true;
|
||||||
|
e.Pointer.Capture(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnPointerReleasedDrag(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragArmed && !_dragging) return;
|
||||||
|
|
||||||
|
var wasDragging = _dragging;
|
||||||
|
var row = _pressRow;
|
||||||
|
var topLevel = TopLevel.GetTopLevel(this);
|
||||||
|
var screen = wasDragging && topLevel is not null
|
||||||
|
? this.PointToScreen(e.GetPosition(this))
|
||||||
|
: default;
|
||||||
|
|
||||||
|
EndDrag(e);
|
||||||
|
|
||||||
|
if (!wasDragging || row is null || topLevel is null) return;
|
||||||
|
|
||||||
|
// 1) Released over the Mission Control window → queue the task.
|
||||||
|
if (MissionControlUnder(screen) is { } mc)
|
||||||
{
|
{
|
||||||
e.DragEffects = DragDropEffects.None;
|
await mc.EnqueueTaskAsync(row.Id);
|
||||||
vm.ClearDropHints();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sender is not Button b || b.DataContext is not TaskRowViewModel target || target.IsRunning)
|
|
||||||
|
// 2) Released over another row in the same user list → reorder.
|
||||||
|
if (DataContext is TasksIslandViewModel vm && vm.CanReorder)
|
||||||
|
{
|
||||||
|
var targetButton = RowButtonAt(e, topLevel);
|
||||||
|
if (targetButton?.DataContext is TaskRowViewModel target
|
||||||
|
&& !ReferenceEquals(target, row) && !target.IsRunning)
|
||||||
|
{
|
||||||
|
var placeBelow = e.GetPosition(targetButton).Y > targetButton.Bounds.Height / 2;
|
||||||
|
await vm.ReorderAsync(row, target, placeBelow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Anywhere else → cancel; EndDrag already restored the source row.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
|
||||||
|
{
|
||||||
|
// We just took capture ourselves (stealing it from the row Button when the drag began) —
|
||||||
|
// that is not a real loss, so don't tear the drag down.
|
||||||
|
if (ReferenceEquals(e.Pointer.Captured, this)) return;
|
||||||
|
if (!_dragArmed && !_dragging) return;
|
||||||
|
if (_pressRow is not null) _pressRow.IsDragging = false;
|
||||||
|
if (DataContext is TasksIslandViewModel vm) vm.ClearDropHints();
|
||||||
|
_drag.End();
|
||||||
|
ResetPressState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndDrag(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (_pressRow is not null) _pressRow.IsDragging = false;
|
||||||
|
if (DataContext is TasksIslandViewModel vm) vm.ClearDropHints();
|
||||||
|
_drag.End();
|
||||||
|
if (_dragging) e.Pointer.Capture(null);
|
||||||
|
ResetPressState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPressState()
|
||||||
|
{
|
||||||
|
_dragArmed = false;
|
||||||
|
_dragging = false;
|
||||||
|
_pressRow = null;
|
||||||
|
_pressControl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live drop-hint while dragging over rows in the source (user) list.
|
||||||
|
private void UpdateReorderHint(PointerEventArgs e, TopLevel topLevel)
|
||||||
|
{
|
||||||
|
if (DataContext is not TasksIslandViewModel vm) return;
|
||||||
|
if (!vm.CanReorder) { vm.ClearDropHints(); return; }
|
||||||
|
|
||||||
|
var targetButton = RowButtonAt(e, topLevel);
|
||||||
|
if (targetButton?.DataContext is not TaskRowViewModel target
|
||||||
|
|| target.IsRunning || ReferenceEquals(target, _pressRow))
|
||||||
{
|
{
|
||||||
e.DragEffects = DragDropEffects.None;
|
|
||||||
vm.ClearDropHints();
|
vm.ClearDropHints();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceId = e.DataTransfer?.TryGetValue(TaskRowFormat);
|
var placeBelow = e.GetPosition(targetButton).Y > targetButton.Bounds.Height / 2;
|
||||||
if (string.IsNullOrEmpty(sourceId) || sourceId == target.Id)
|
|
||||||
{
|
|
||||||
e.DragEffects = DragDropEffects.None;
|
|
||||||
vm.ClearDropHints();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var placeBelow = e.GetPosition(b).Y > b.Bounds.Height / 2;
|
// Canonicalize: "drop below X" == "drop above X+1". Render the indicator above X+1 when
|
||||||
|
// there is one; only the last row in a section shows a below-line.
|
||||||
// Canonicalize: "drop below X" == "drop above X+1". Render the indicator
|
|
||||||
// above X+1 when there is one; only the last row in a section shows a below-line.
|
|
||||||
TaskRowViewModel hintRow = target;
|
TaskRowViewModel hintRow = target;
|
||||||
bool hintBelow = false;
|
bool hintBelow = false;
|
||||||
if (placeBelow)
|
if (placeBelow)
|
||||||
{
|
{
|
||||||
var next = FindNextInSameSection(vm, target);
|
var next = FindNextInSameSection(vm, target);
|
||||||
if (next is not null && !next.IsRunning)
|
if (next is not null && !next.IsRunning) { hintRow = next; hintBelow = false; }
|
||||||
{
|
else { hintRow = target; hintBelow = true; }
|
||||||
hintRow = next;
|
|
||||||
hintBelow = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hintRow = target;
|
|
||||||
hintBelow = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A hint that lands right where the dragged row already sits is a no-op.
|
// A hint that lands right where the dragged row already sits is a no-op.
|
||||||
if (hintRow.Id == sourceId)
|
if (_pressRow is not null && hintRow.Id == _pressRow.Id) { vm.ClearDropHints(); return; }
|
||||||
{
|
|
||||||
e.DragEffects = DragDropEffects.None;
|
|
||||||
vm.ClearDropHints();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.SetDropHint(hintRow, hintBelow);
|
vm.SetDropHint(hintRow, hintBelow);
|
||||||
e.DragEffects = DragDropEffects.Move;
|
}
|
||||||
|
|
||||||
|
// The row-level Button under the cursor, found by geometric hit-test on the source window
|
||||||
|
// (works while the pointer is captured to this control).
|
||||||
|
private static Button? RowButtonAt(PointerEventArgs e, TopLevel topLevel)
|
||||||
|
{
|
||||||
|
var pt = e.GetPosition((Visual)topLevel);
|
||||||
|
if (topLevel.InputHitTest(pt) is not Visual hit) return null;
|
||||||
|
var button = hit as Button ?? hit.FindAncestorOfType<Button>();
|
||||||
|
while (button is not null && button.DataContext is not TaskRowViewModel)
|
||||||
|
button = (button.Parent as Visual)?.FindAncestorOfType<Button>();
|
||||||
|
return button?.DataContext is TaskRowViewModel ? button : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Mission Control view model whose window contains the release point, if any.
|
||||||
|
private static MissionControlViewModel? MissionControlUnder(PixelPoint screen)
|
||||||
|
{
|
||||||
|
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
return null;
|
||||||
|
foreach (var w in desktop.Windows)
|
||||||
|
{
|
||||||
|
if (w is not MissionControlWindow mc || !mc.IsVisible) continue;
|
||||||
|
if (DragHitTest.WindowContains(mc.Position, mc.ClientSize, mc.RenderScaling, screen))
|
||||||
|
return mc.DataContext as MissionControlViewModel;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TaskRowViewModel? FindNextInSameSection(TasksIslandViewModel vm, TaskRowViewModel row)
|
private static TaskRowViewModel? FindNextInSameSection(TasksIslandViewModel vm, TaskRowViewModel row)
|
||||||
@@ -206,41 +318,4 @@ public partial class TasksIslandView : UserControl
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnRowDrop(object? sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is not TasksIslandViewModel vm) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (sender is not Button b || b.DataContext is not TaskRowViewModel target) return;
|
|
||||||
if (target.IsRunning) return;
|
|
||||||
|
|
||||||
var sourceId = e.DataTransfer?.TryGetValue(TaskRowFormat);
|
|
||||||
if (string.IsNullOrEmpty(sourceId) || sourceId == target.Id) return;
|
|
||||||
|
|
||||||
var source = FindRowById(vm, sourceId);
|
|
||||||
if (source is null || source.IsRunning) return;
|
|
||||||
|
|
||||||
var placeBelow = e.GetPosition(b).Y > b.Bounds.Height / 2;
|
|
||||||
|
|
||||||
// Clear the 6px drop-hint spacer BEFORE the move so the reorder animates
|
|
||||||
// into its truly-final layout in one step (otherwise the row lands in the
|
|
||||||
// gap, then the gap collapses and everything shifts up a second time).
|
|
||||||
vm.ClearDropHints();
|
|
||||||
|
|
||||||
await vm.ReorderAsync(source, target, placeBelow);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
vm.ClearDropHints();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TaskRowViewModel? FindRowById(TasksIslandViewModel vm, string id)
|
|
||||||
{
|
|
||||||
foreach (var r in vm.Items)
|
|
||||||
if (r.Id == id) return r;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using ClaudeDo.Ui.ViewModels;
|
using ClaudeDo.Ui.ViewModels;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
using ClaudeDo.Ui.Views.Islands;
|
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||||
|
|
||||||
@@ -25,9 +24,7 @@ public partial class MissionControlView : UserControl
|
|||||||
private void OnPaneDragOver(object? sender, DragEventArgs e)
|
private void OnPaneDragOver(object? sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
var dt = e.DataTransfer;
|
var dt = e.DataTransfer;
|
||||||
var accept = (dt?.Contains(PaneFormat) ?? false)
|
e.DragEffects = (dt?.Contains(PaneFormat) ?? false) ? DragDropEffects.Move : DragDropEffects.None;
|
||||||
|| (dt?.Contains(TasksIslandView.TaskRowFormat) ?? false);
|
|
||||||
e.DragEffects = accept ? DragDropEffects.Move : DragDropEffects.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPaneDrop(object? sender, DragEventArgs e)
|
private void OnPaneDrop(object? sender, DragEventArgs e)
|
||||||
@@ -36,15 +33,8 @@ public partial class MissionControlView : UserControl
|
|||||||
var dt = e.DataTransfer;
|
var dt = e.DataTransfer;
|
||||||
if (dt is null) return;
|
if (dt is null) return;
|
||||||
|
|
||||||
// A task dragged from the main app → queue it.
|
// A pane dragged within the grid → reorder. (Drag-to-queue from the main app now arrives
|
||||||
var taskId = dt.TryGetValue(TasksIslandView.TaskRowFormat);
|
// via the custom ghost drag's screen hit-test, not an OLE drop.)
|
||||||
if (!string.IsNullOrEmpty(taskId))
|
|
||||||
{
|
|
||||||
_ = vm.EnqueueTaskAsync(taskId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pane dragged within the grid → reorder.
|
|
||||||
var draggedId = dt.TryGetValue(PaneFormat);
|
var draggedId = dt.TryGetValue(PaneFormat);
|
||||||
if (string.IsNullOrEmpty(draggedId)) return;
|
if (string.IsNullOrEmpty(draggedId)) return;
|
||||||
if (e.Source is not Avalonia.Visual src) return;
|
if (e.Source is not Avalonia.Visual src) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user