diff --git a/EbayListingTool/Views/SingleItemView.xaml.cs b/EbayListingTool/Views/SingleItemView.xaml.cs index cd9ca89..ea333eb 100644 --- a/EbayListingTool/Views/SingleItemView.xaml.cs +++ b/EbayListingTool/Views/SingleItemView.xaml.cs @@ -20,6 +20,10 @@ public partial class SingleItemView : UserControl private System.Threading.CancellationTokenSource? _categoryCts; private string _suggestedPriceValue = ""; + // Photo drag-reorder + private Point _dragStartPoint; + private bool _isDragging; + public SingleItemView() { InitializeComponent(); @@ -329,25 +333,35 @@ public partial class SingleItemView : UserControl if (!imageExts.Contains(Path.GetExtension(path))) continue; if (_draft.PhotoPaths.Count >= 12) break; if (_draft.PhotoPaths.Contains(path)) continue; - _draft.PhotoPaths.Add(path); - AddPhotoThumbnail(path); } + RebuildPhotoThumbnails(); + } + + /// + /// Clears and recreates all photo thumbnails from . + /// Called after any add, remove, or reorder operation so the panel always matches the list. + /// + private void RebuildPhotoThumbnails() + { + PhotosPanel.Children.Clear(); + for (int i = 0; i < _draft.PhotoPaths.Count; i++) + AddPhotoThumbnail(_draft.PhotoPaths[i], i); UpdatePhotoPanel(); } - private void AddPhotoThumbnail(string path) + private void AddPhotoThumbnail(string path, int index) { try { var bmp = new BitmapImage(); bmp.BeginInit(); - bmp.UriSource = new Uri(path, UriKind.Absolute); // W1 + bmp.UriSource = new Uri(path, UriKind.Absolute); bmp.DecodePixelWidth = 128; bmp.CacheOption = BitmapCacheOption.OnLoad; bmp.EndInit(); - bmp.Freeze(); // M2 + bmp.Freeze(); var img = new System.Windows.Controls.Image { @@ -356,12 +370,9 @@ public partial class SingleItemView : UserControl Source = bmp, ToolTip = Path.GetFileName(path) }; + img.Clip = new System.Windows.Media.RectangleGeometry(new Rect(0, 0, 72, 72), 4, 4); - // Rounded clip on the image - img.Clip = new System.Windows.Media.RectangleGeometry( - new Rect(0, 0, 72, 72), 4, 4); - - // Remove button — shown on hover via opacity triggers + // Remove button var removeBtn = new Button { Width = 18, Height = 18, @@ -375,38 +386,116 @@ public partial class SingleItemView : UserControl System.Windows.Media.Color.FromArgb(200, 30, 30, 30)), Foreground = System.Windows.Media.Brushes.White, BorderThickness = new Thickness(0), - FontSize = 11, - FontWeight = FontWeights.Bold, + FontSize = 11, FontWeight = FontWeights.Bold, Content = "✕", Opacity = 0 }; + removeBtn.Click += (s, e) => + { + e.Handled = true; // don't bubble and trigger drag + _draft.PhotoPaths.Remove(path); + RebuildPhotoThumbnails(); + }; + + // "Cover" badge on the first photo — it becomes the eBay gallery hero image + Border? coverBadge = null; + if (index == 0) + { + coverBadge = new Border + { + CornerRadius = new CornerRadius(3), + Background = new System.Windows.Media.SolidColorBrush( + System.Windows.Media.Color.FromArgb(210, 60, 90, 200)), + Padding = new Thickness(3, 1, 3, 1), + Margin = new Thickness(2, 2, 0, 0), + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + IsHitTestVisible = false, // don't block drag + Child = new TextBlock + { + Text = "Cover", + FontSize = 8, + FontWeight = FontWeights.SemiBold, + Foreground = System.Windows.Media.Brushes.White + } + }; + } - // Container grid — shows remove button on mouse over var container = new Grid { Width = 72, Height = 72, Margin = new Thickness(4), - Cursor = Cursors.Hand + Cursor = Cursors.SizeAll, // signal draggability + AllowDrop = true, + Tag = path // stable identifier used by drop handler }; container.Children.Add(img); + if (coverBadge != null) container.Children.Add(coverBadge); container.Children.Add(removeBtn); + // Hover: reveal remove button container.MouseEnter += (s, e) => removeBtn.Opacity = 1; container.MouseLeave += (s, e) => removeBtn.Opacity = 0; - removeBtn.Click += (s, e) => RemovePhoto(path, container); + + // Drag initiation + container.MouseLeftButtonDown += (s, e) => + { + _dragStartPoint = e.GetPosition(null); + }; + container.MouseMove += (s, e) => + { + if (e.LeftButton != MouseButtonState.Pressed || _isDragging) return; + var pos = e.GetPosition(null); + if (Math.Abs(pos.X - _dragStartPoint.X) > SystemParameters.MinimumHorizontalDragDistance || + Math.Abs(pos.Y - _dragStartPoint.Y) > SystemParameters.MinimumVerticalDragDistance) + { + _isDragging = true; + DragDrop.DoDragDrop(container, path, DragDropEffects.Move); + _isDragging = false; + } + }; + + // Drop target + container.DragOver += (s, e) => + { + if (e.Data.GetDataPresent(typeof(string)) && + (string)e.Data.GetData(typeof(string)) != path) + { + e.Effects = DragDropEffects.Move; + container.Opacity = 0.45; // dim to signal insertion point + } + else + { + e.Effects = DragDropEffects.None; + } + e.Handled = true; + }; + container.DragLeave += (s, e) => container.Opacity = 1.0; + container.Drop += (s, e) => + { + container.Opacity = 1.0; + if (!e.Data.GetDataPresent(typeof(string))) return; + + var sourcePath = (string)e.Data.GetData(typeof(string)); + var targetPath = (string)container.Tag; + if (sourcePath == targetPath) return; + + var sourceIdx = _draft.PhotoPaths.IndexOf(sourcePath); + var targetIdx = _draft.PhotoPaths.IndexOf(targetPath); + if (sourceIdx < 0 || targetIdx < 0) return; + + _draft.PhotoPaths.RemoveAt(sourceIdx); + _draft.PhotoPaths.Insert(targetIdx, sourcePath); + + RebuildPhotoThumbnails(); + e.Handled = true; + }; PhotosPanel.Children.Add(container); } catch { /* skip unreadable files */ } } - private void RemovePhoto(string path, UIElement thumb) - { - _draft.PhotoPaths.Remove(path); - PhotosPanel.Children.Remove(thumb); - UpdatePhotoPanel(); - } - private void UpdatePhotoPanel() { var count = _draft.PhotoPaths.Count; @@ -421,8 +510,7 @@ public partial class SingleItemView : UserControl private void ClearPhotos_Click(object sender, RoutedEventArgs e) { _draft.PhotoPaths.Clear(); - PhotosPanel.Children.Clear(); - UpdatePhotoPanel(); + RebuildPhotoThumbnails(); } // ---- Post / Save ----