gesture.go RawHistoryFolder
1package input
2
3import (
4 "context"
5 "sync"
6
7 "github.com/bnema/dumber/internal/logging"
8 "github.com/jwijenbergh/puregotk/v4/gtk"
9)
10
11const (
12 // Mouse button constants for X11/Wayland
13 mouseButtonBack = 8 // Side button - back
14 mouseButtonForward = 9 // Side button - forward
15)
16
17// GestureHandler handles mouse button gestures for navigation.
18// It recognizes mouse buttons 8 (back) and 9 (forward) on WebView widgets.
19type GestureHandler struct {
20 clickGesture *gtk.GestureClick
21
22 // Callback retention: must stay reachable by Go GC.
23 pressedCb func(gtk.GestureClick, int, float64, float64)
24
25 // Action handler callback
26 onAction ActionHandler
27
28 ctx context.Context
29 mu sync.RWMutex
30}
31
32// NewGestureHandler creates a new gesture handler.
33func NewGestureHandler(ctx context.Context) *GestureHandler {
34 log := logging.FromContext(ctx)
35 log.Debug().Msg("creating gesture handler")
36
37 return &GestureHandler{
38 ctx: ctx,
39 }
40}
41
42// SetOnAction sets the callback for when navigation actions are triggered.
43func (h *GestureHandler) SetOnAction(fn ActionHandler) {
44 h.mu.Lock()
45 defer h.mu.Unlock()
46 h.onAction = fn
47}
48
49// AttachTo attaches the gesture handler to a GTK widget.
50// Typically called with a WebView widget to enable mouse button navigation.
51func (h *GestureHandler) AttachTo(widget *gtk.Widget) {
52 log := logging.FromContext(h.ctx)
53
54 if widget == nil {
55 log.Error().Msg("cannot attach gesture handler to nil widget")
56 return
57 }
58
59 // Create click gesture that listens to all buttons
60 h.clickGesture = gtk.NewGestureClick()
61 if h.clickGesture == nil {
62 log.Error().Msg("failed to create gesture click")
63 return
64 }
65
66 // Listen to all mouse buttons (not just primary)
67 h.clickGesture.SetButton(0)
68
69 // Connect pressed handler (retain callback to prevent GC)
70 h.pressedCb = func(_ gtk.GestureClick, nPress int, _ float64, _ float64) {
71 h.handlePressed(nPress)
72 }
73 h.clickGesture.ConnectPressed(&h.pressedCb)
74
75 // Add controller to widget
76 widget.AddController(&h.clickGesture.EventController)
77
78 log.Debug().Msg("gesture handler attached to widget")
79}
80
81// handlePressed processes a mouse button press event.
82func (h *GestureHandler) handlePressed(nPress int) {
83 // Only handle single clicks for navigation
84 if nPress != 1 {
85 return
86 }
87
88 // Get the button that was pressed
89 button := h.clickGesture.GetCurrentButton()
90
91 var action Action
92 switch button {
93 case mouseButtonBack:
94 action = ActionGoBack
95 case mouseButtonForward:
96 action = ActionGoForward
97 default:
98 // Not a navigation button, ignore
99 return
100 }
101
102 // Dispatch action to handler
103 h.mu.RLock()
104 handler := h.onAction
105 h.mu.RUnlock()
106
107 if handler != nil {
108 if err := handler(h.ctx, action); err != nil {
109 log := logging.FromContext(h.ctx)
110 log.Error().
111 Err(err).
112 Str("action", string(action)).
113 Uint("button", button).
114 Msg("gesture action handler error")
115 }
116 }
117}
118
119// Detach removes the gesture handler.
120// Note: GTK handles cleanup when the widget is destroyed,
121// but we clear our reference here.
122func (h *GestureHandler) Detach() {
123 h.clickGesture = nil
124 h.pressedCb = nil
125}