feat(ui): drag-reorder Mission Control panes by their header

This commit is contained in:
Mika Kuns
2026-06-25 16:36:09 +02:00
parent f63be285a2
commit f6ecfc995f
3 changed files with 60 additions and 2 deletions

View File

@@ -1,8 +1,46 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Islands;
namespace ClaudeDo.Ui.Views.MissionControl;
public partial class MissionControlView : UserControl
{
public MissionControlView() => InitializeComponent();
// Shared with MonitorPaneView (the drag source).
public static readonly DataFormat<string> PaneFormat =
DataFormat.CreateStringApplicationFormat("claudedo-monitor-pane");
public MissionControlView()
{
InitializeComponent();
AddHandler(DragDrop.DragOverEvent, OnPaneDragOver);
AddHandler(DragDrop.DropEvent, OnPaneDrop);
}
private void OnPaneDragOver(object? sender, DragEventArgs e)
{
e.DragEffects = (e.DataTransfer?.Contains(PaneFormat) ?? false)
? DragDropEffects.Move
: DragDropEffects.None;
}
private void OnPaneDrop(object? sender, DragEventArgs e)
{
if (DataContext is not MissionControlViewModel vm) return;
var draggedId = e.DataTransfer?.TryGetValue(PaneFormat);
if (string.IsNullOrEmpty(draggedId)) return;
if (e.Source is not Avalonia.Visual src) return;
var targetPane = src.FindAncestorOfType<MonitorPaneView>();
if (targetPane?.DataContext is not TaskMonitorViewModel target) return;
var dragged = vm.Monitors.FirstOrDefault(m => m.SubscribedTaskId == draggedId);
if (dragged is null) return;
vm.MoveMonitor(dragged, target);
}
}

View File

@@ -6,6 +6,7 @@
x:DataType="vm:TaskMonitorViewModel"
x:Class="ClaudeDo.Ui.Views.MissionControl.MonitorPaneView">
<Border Classes="monitor-pane"
DragDrop.AllowDrop="True"
Classes.mon-review="{Binding IsWaitingForReview}"
Classes.mon-done="{Binding IsDone}"
Classes.mon-roadblock="{Binding HasRoadblock}"
@@ -17,7 +18,8 @@
<Border DockPanel.Dock="Top"
Background="{DynamicResource Surface2Brush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,0,0,1" Padding="6,3">
BorderThickness="0,0,0,1" Padding="6,3"
PointerPressed="OnHeaderPressed">
<StackPanel Orientation="Horizontal" Spacing="2"
HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Classes="title-ctrl"

View File

@@ -1,8 +1,26 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.VisualTree;
using ClaudeDo.Ui.ViewModels.Islands;
namespace ClaudeDo.Ui.Views.MissionControl;
public partial class MonitorPaneView : UserControl
{
public MonitorPaneView() => InitializeComponent();
private async void OnHeaderPressed(object? sender, PointerPressedEventArgs e)
{
if (DataContext is not TaskMonitorViewModel m || m.SubscribedTaskId is not { } id) return;
if (e.Source is not Avalonia.Visual src) return;
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
// Don't start a drag when the press landed on a header action button.
var button = src as Button ?? src.FindAncestorOfType<Button>();
if (button is not null) return;
var data = new DataTransfer();
data.Add(DataTransferItem.Create(MissionControlView.PaneFormat, id));
await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);
}
}