using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.VisualTree; using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.ViewModels.Islands; using ClaudeDo.Ui.ViewModels.Modals; using ClaudeDo.Ui.Views.Modals; namespace ClaudeDo.Ui.Views.Islands; public partial class ListsIslandView : UserControl { private static readonly DataFormat ListRowFormat = DataFormat.CreateStringApplicationFormat("claudedo-list-row"); public ListsIslandView() { InitializeComponent(); AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel); DataContextChanged += (_, _) => { if (DataContext is ListsIslandViewModel vm) vm.FocusSearchRequested += (_, _) => SearchBox.Focus(); }; } private void OnItemTapped(object? sender, RoutedEventArgs e) { if (sender is Border { DataContext: ListNavItemViewModel item } && DataContext is ListsIslandViewModel vm) vm.SelectCommand.Execute(item); } private async void OnTunnelPointerPressed(object? sender, PointerPressedEventArgs e) { if (DataContext is not ListsIslandViewModel vm) return; if (e.Source is not Visual src) return; var border = FindListItemBorder(src); if (border?.DataContext is not ListNavItemViewModel row || row.Kind != ListKind.User) return; if (!e.GetCurrentPoint(border).Properties.IsLeftButtonPressed) return; // Double-click opens the list's settings instead of starting a drag. Handled here // because DoDragDropAsync captures the pointer and would swallow a DoubleTapped event. if (e.ClickCount == 2) { vm.OpenListSettingsCommand.Execute(row); return; } // Select now so the right pane updates whether the gesture becomes a click or a drag // (the Tapped handler doesn't fire once DoDragDropAsync captures the pointer). vm.SelectCommand.Execute(row); var data = new DataTransfer(); data.Add(DataTransferItem.Create(ListRowFormat, row.Id)); try { await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move); } finally { vm.ClearDropHints(); } } private void OnListDragOver(object? sender, DragEventArgs e) { if (DataContext is not ListsIslandViewModel vm) { e.DragEffects = DragDropEffects.None; return; } if (!e.DataTransfer?.Contains(ListRowFormat) ?? true) { e.DragEffects = DragDropEffects.None; vm.ClearDropHints(); return; } if (sender is not Border b || b.DataContext is not ListNavItemViewModel target || target.Kind != ListKind.User) { e.DragEffects = DragDropEffects.None; vm.ClearDropHints(); return; } var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat); 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". Only the last row shows a below-line. ListNavItemViewModel hintRow = target; bool hintBelow = false; if (placeBelow) { var next = FindNextUserList(vm, target); if (next is not null) { hintRow = next; hintBelow = false; } else { hintRow = target; hintBelow = true; } } if (hintRow.Id == sourceId) { e.DragEffects = DragDropEffects.None; vm.ClearDropHints(); return; } vm.SetDropHint(hintRow, hintBelow); e.DragEffects = DragDropEffects.Move; } private async void OnListDrop(object? sender, DragEventArgs e) { if (DataContext is not ListsIslandViewModel vm) return; try { if (sender is not Border b || b.DataContext is not ListNavItemViewModel target || target.Kind != ListKind.User) return; var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat); if (string.IsNullOrEmpty(sourceId) || sourceId == target.Id) return; var source = vm.UserLists.FirstOrDefault(r => r.Id == sourceId); if (source is null) return; var placeBelow = e.GetPosition(b).Y > b.Bounds.Height / 2; vm.ClearDropHints(); await vm.ReorderAsync(source, target, placeBelow); } catch { vm.ClearDropHints(); throw; } } private static Border? FindListItemBorder(Visual? v) { while (v is not null) { if (v is Border b && b.Classes.Contains("list-item")) return b; v = v.GetVisualParent(); } return null; } private static ListNavItemViewModel? FindNextUserList(ListsIslandViewModel vm, ListNavItemViewModel row) { var idx = vm.UserLists.IndexOf(row); if (idx < 0) return null; return idx + 1 < vm.UserLists.Count ? vm.UserLists[idx + 1] : null; } }