feat(ui): drag-reorder Mission Control panes by their header
This commit is contained in:
@@ -1,8 +1,46 @@
|
|||||||
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
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;
|
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||||
|
|
||||||
public partial class MissionControlView : UserControl
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
x:DataType="vm:TaskMonitorViewModel"
|
x:DataType="vm:TaskMonitorViewModel"
|
||||||
x:Class="ClaudeDo.Ui.Views.MissionControl.MonitorPaneView">
|
x:Class="ClaudeDo.Ui.Views.MissionControl.MonitorPaneView">
|
||||||
<Border Classes="monitor-pane"
|
<Border Classes="monitor-pane"
|
||||||
|
DragDrop.AllowDrop="True"
|
||||||
Classes.mon-review="{Binding IsWaitingForReview}"
|
Classes.mon-review="{Binding IsWaitingForReview}"
|
||||||
Classes.mon-done="{Binding IsDone}"
|
Classes.mon-done="{Binding IsDone}"
|
||||||
Classes.mon-roadblock="{Binding HasRoadblock}"
|
Classes.mon-roadblock="{Binding HasRoadblock}"
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
<Border DockPanel.Dock="Top"
|
<Border DockPanel.Dock="Top"
|
||||||
Background="{DynamicResource Surface2Brush}"
|
Background="{DynamicResource Surface2Brush}"
|
||||||
BorderBrush="{DynamicResource LineBrush}"
|
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"
|
<StackPanel Orientation="Horizontal" Spacing="2"
|
||||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
<Button Classes="title-ctrl"
|
<Button Classes="title-ctrl"
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||||
|
|
||||||
public partial class MonitorPaneView : UserControl
|
public partial class MonitorPaneView : UserControl
|
||||||
{
|
{
|
||||||
public MonitorPaneView() => InitializeComponent();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user