From e25f0c428456f6fcfdfe9f64a9004d53f6fbf7f8 Mon Sep 17 00:00:00 2001 From: Stu Date: Tue, 9 Dec 2025 11:23:26 -0500 Subject: [PATCH] Add video interaction: double-click fullscreen and right-click play/pause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements intuitive video player interactions for enhanced user experience. Video Interaction Features: - Double-click video area to toggle fullscreen - Right-click video area to play/pause - Single-click does nothing (reserved for future use) - Transparent tappable overlay on video canvas Implementation: - Created TappableOverlay widget in internal/ui/tappable.go - Invisible widget captures tap, double-tap, and secondary-tap events - Extends widget.BaseWidget for Fyne compatibility - Added overlay to stage container after video image User Experience: - Double-click anywhere on video → instant fullscreen - Right-click anywhere on video → quick play/pause - Works alongside existing keyboard shortcuts (F11, Space, ESC) - Play button icon updates when using right-click Technical Details: - TappableOverlay has no visual representation - Implements Tapped(), DoubleTapped(), TappedSecondary() - Callbacks are configurable per instance - Positioned as top layer in container.NewMax() stack Usage: 1. Load a video 2. Double-click video to enter fullscreen 3. Right-click to pause/play 4. ESC or F11 to exit fullscreen Next Steps: - Consider adding single-click functionality - Add visual feedback for interactions - Implement mouse cursor auto-hide in fullscreen 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/ui/tappable.go | 64 +++++++++++++++++++++++++++++++++++++++++ main.go | 28 ++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 internal/ui/tappable.go diff --git a/internal/ui/tappable.go b/internal/ui/tappable.go new file mode 100644 index 0000000..8839e30 --- /dev/null +++ b/internal/ui/tappable.go @@ -0,0 +1,64 @@ +package ui + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// TappableOverlay is an invisible widget that captures tap and double-tap events +type TappableOverlay struct { + widget.BaseWidget + OnTapped func() + OnDoubleTapped func() + OnSecondaryTapped func() +} + +// NewTappableOverlay creates a new tappable overlay +func NewTappableOverlay(onTapped, onDoubleTapped, onSecondaryTapped func()) *TappableOverlay { + t := &TappableOverlay{ + OnTapped: onTapped, + OnDoubleTapped: onDoubleTapped, + OnSecondaryTapped: onSecondaryTapped, + } + t.ExtendBaseWidget(t) + return t +} + +// Tapped handles single tap events +func (t *TappableOverlay) Tapped(*fyne.PointEvent) { + if t.OnTapped != nil { + t.OnTapped() + } +} + +// TappedSecondary handles right-click events +func (t *TappableOverlay) TappedSecondary(*fyne.PointEvent) { + if t.OnSecondaryTapped != nil { + t.OnSecondaryTapped() + } +} + +// DoubleTapped handles double-tap events +func (t *TappableOverlay) DoubleTapped(*fyne.PointEvent) { + if t.OnDoubleTapped != nil { + t.OnDoubleTapped() + } +} + +// CreateRenderer implements fyne.Widget +func (t *TappableOverlay) CreateRenderer() fyne.WidgetRenderer { + return &tappableRenderer{} +} + +// MinSize returns minimum size (should fill parent) +func (t *TappableOverlay) MinSize() fyne.Size { + return fyne.NewSize(1, 1) +} + +type tappableRenderer struct{} + +func (r *tappableRenderer) Layout(size fyne.Size) {} +func (r *tappableRenderer) MinSize() fyne.Size { return fyne.NewSize(1, 1) } +func (r *tappableRenderer) Refresh() {} +func (r *tappableRenderer) Objects() []fyne.CanvasObject { return nil } +func (r *tappableRenderer) Destroy() {} diff --git a/main.go b/main.go index 55c14bb..65e7137 100644 --- a/main.go +++ b/main.go @@ -1068,6 +1068,34 @@ func (s *appState) showPlayerView() { }) } + // Add tappable overlay to stage for video interactions + // Double-click toggles fullscreen, right-click plays/pauses + videoTapper := ui.NewTappableOverlay( + nil, // Single tap does nothing for now + func() { + // Double-tap: toggle fullscreen + s.toggleFullscreen() + }, + func() { + // Right-click: toggle play/pause + if !ensureSession() { + return + } + if s.playerPaused { + s.playSess.Play() + s.playerPaused = false + playBtn.SetText(ui.IconPause) + } else { + s.playSess.Pause() + s.playerPaused = true + playBtn.SetText(ui.IconPlayArrow) + } + }, + ) + + // Add tapper to stage + stage.Objects = append(stage.Objects, videoTapper) + playerArea = container.NewBorder( nil, container.NewVBox(container.NewPadded(progressBar), container.NewPadded(controlRow)),