using System; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; using Avalonia.VisualTree; using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.ViewModels.Islands; using ClaudeDo.Ui.ViewModels.Modals; using ClaudeDo.Ui.Views.Controls; using ClaudeDo.Ui.Views.MissionControl; using ClaudeDo.Ui.Views.Modals; namespace ClaudeDo.Ui.Views.Islands; public partial class TasksIslandView : UserControl { private readonly TaskDragController _drag = new(); // 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; // The list row (in the Lists island) currently highlighted as a drop target while dragging. private ListNavItemViewModel? _hintedList; public TasksIslandView() { InitializeComponent(); AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel); AddHandler(PointerMovedEvent, OnPointerMovedDrag, RoutingStrategies.Tunnel); AddHandler(PointerReleasedEvent, OnPointerReleasedDrag, RoutingStrategies.Tunnel); AddHandler(PointerCaptureLostEvent, OnPointerCaptureLost); DataContextChanged += (_, _) => { if (DataContext is TasksIslandViewModel vm) { vm.FocusAddTaskRequested += (_, _) => AddTaskBox.Focus(); vm.ShowUnfinishedPlanningModal = async (modalVm) => { var owner = TopLevel.GetTopLevel(this) as Window; if (owner is null) { modalVm.CancelCommand.Execute(null); return; } var modal = new UnfinishedPlanningModalView { DataContext = modalVm }; // Closing via the OS title-bar (if ever enabled) also resolves the TCS. modal.Closed += (_, _) => modalVm.CancelCommand.Execute(null); await modal.ShowDialog(owner); // ShowDialog completes once the window is closed (CloseAction or OS close). }; vm.SelectionChanged += (_, _) => ScrollSelectedIntoView(); } }; } // Bring the selected row into view — a programmatic select (e.g. Mission Control's "Open in // app") can target a row that isn't currently realized. RowsListBox.ScrollIntoView is // virtualization-aware (it realizes the container as needed); a visual-tree search for an // existing Button, as this used to do, only finds rows already on screen. private void ScrollSelectedIntoView() { if (DataContext is not TasksIslandViewModel vm || vm.SelectedTask is not { } target) return; Dispatcher.UIThread.Post(() => RowsListBox.ScrollIntoView(target), DispatcherPriority.Background); } // ── 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) { ResetPressState(); if (DataContext is not TasksIslandViewModel) return; if (e.Source is not Visual src) return; var button = src as Button ?? src.FindAncestorOfType