From 0221c04a4f164b9bef53154ae8c2087d34e6384b Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 10 Dec 2025 16:43:20 -0500 Subject: [PATCH] Add droppable merge empty state --- internal/ui/components.go | 76 +++++++++++++++++++++++++++++++++++---- main.go | 18 ++++++++-- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/internal/ui/components.go b/internal/ui/components.go index b915f58..3436c71 100644 --- a/internal/ui/components.go +++ b/internal/ui/components.go @@ -50,12 +50,12 @@ func (m *MonoTheme) Size(name fyne.ThemeSizeName) float32 { // ModuleTile is a clickable tile widget for module selection type ModuleTile struct { widget.BaseWidget - label string - color color.Color - enabled bool - onTapped func() - onDropped func([]fyne.URI) - flashing bool + label string + color color.Color + enabled bool + onTapped func() + onDropped func([]fyne.URI) + flashing bool draggedOver bool } @@ -263,6 +263,70 @@ func (r *tappableRenderer) Objects() []fyne.CanvasObject { return []fyne.CanvasObject{r.content} } +// Droppable wraps any canvas object and makes it a drop target (files/URIs) +type Droppable struct { + widget.BaseWidget + content fyne.CanvasObject + onDropped func([]fyne.URI) +} + +// NewDroppable creates a new droppable wrapper +func NewDroppable(content fyne.CanvasObject, onDropped func([]fyne.URI)) *Droppable { + d := &Droppable{ + content: content, + onDropped: onDropped, + } + d.ExtendBaseWidget(d) + return d +} + +// CreateRenderer creates the renderer for the droppable +func (d *Droppable) CreateRenderer() fyne.WidgetRenderer { + return &droppableRenderer{ + droppable: d, + content: d.content, + } +} + +// DraggedOver highlights when drag is over (optional) +func (d *Droppable) DraggedOver(pos fyne.Position) { + _ = pos +} + +// DraggedOut clears highlight (optional) +func (d *Droppable) DraggedOut() {} + +// Dropped handles drop events +func (d *Droppable) Dropped(pos fyne.Position, items []fyne.URI) { + _ = pos + if d.onDropped != nil { + d.onDropped(items) + } +} + +type droppableRenderer struct { + droppable *Droppable + content fyne.CanvasObject +} + +func (r *droppableRenderer) Layout(size fyne.Size) { + r.content.Resize(size) +} + +func (r *droppableRenderer) MinSize() fyne.Size { + return r.content.MinSize() +} + +func (r *droppableRenderer) Refresh() { + r.content.Refresh() +} + +func (r *droppableRenderer) Destroy() {} + +func (r *droppableRenderer) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{r.content} +} + // DraggableVScroll creates a vertical scroll container with draggable track type DraggableVScroll struct { widget.BaseWidget diff --git a/main.go b/main.go index 56d6aac..9f69adf 100644 --- a/main.go +++ b/main.go @@ -1574,9 +1574,21 @@ func (s *appState) showMergeView() { buildList = func() { listBox.Objects = nil if len(s.mergeClips) == 0 { - empty := widget.NewLabel("Add at least two clips to merge.") - empty.Alignment = fyne.TextAlignCenter - listBox.Add(container.NewCenter(empty)) + emptyLabel := widget.NewLabel("Add at least two clips to merge.") + emptyLabel.Alignment = fyne.TextAlignCenter + // Make empty state a drop target + emptyDrop := ui.NewDroppable(container.NewCenter(emptyLabel), func(items []fyne.URI) { + var paths []string + for _, uri := range items { + if uri.Scheme() == "file" { + paths = append(paths, uri.Path()) + } + } + if len(paths) > 0 { + addFiles(paths) + } + }) + listBox.Add(container.NewMax(emptyDrop)) } else { for i, c := range s.mergeClips { idx := i