detached 1fe5ab8

feat(coordinator): wire gesture handler to webviews and keyboard dispatcher

commit
1fe5ab80fe620637710e1c065213480676e7322d
author
brice · · 2025-12-16 22:54 UTC
committer
brice · · 2025-12-16 20:10 UTC
parent
dc03f2e
signature
unsigned
2 files · +26 -1
M internal/ui/app.go +5 -0
@@ -320,6 +320,11 @@ func (a *App) initCoordinators(ctx context.Context) {
320320 )
321321 a.kbDispatcher.SetOnQuit(a.Quit)
322322
323+ // Wire gesture handler to dispatcher (for mouse button 8/9 navigation)
324+ a.contentCoord.SetGestureActionHandler(func(ctx context.Context, action input.Action) error {
325+ return a.kbDispatcher.Dispatch(ctx, action)
326+ })
327+
323328 log.Debug().Msg("coordinators initialized")
324329 }
325330
@@ -320,6 +320,11 @@ func (a *App) initCoordinators(ctx context.Context) {
320 )320 )
321 a.kbDispatcher.SetOnQuit(a.Quit)321 a.kbDispatcher.SetOnQuit(a.Quit)
322 322
323+ // Wire gesture handler to dispatcher (for mouse button 8/9 navigation)
324+ a.contentCoord.SetGestureActionHandler(func(ctx context.Context, action input.Action) error {
325+ return a.kbDispatcher.Dispatch(ctx, action)
326+ })
327+
323 log.Debug().Msg("coordinators initialized")328 log.Debug().Msg("coordinators initialized")
324 }329 }
325 330
M internal/ui/coordinator/content.go +21 -1
@@ -11,6 +11,7 @@ import (
1111 "github.com/bnema/dumber/internal/logging"
1212 "github.com/bnema/dumber/internal/ui/cache"
1313 "github.com/bnema/dumber/internal/ui/component"
14+ "github.com/bnema/dumber/internal/ui/input"
1415 "github.com/bnema/dumber/internal/ui/layout"
1516 "github.com/jwijenbergh/puregotk/v4/gdk"
1617 )
@@ -36,6 +37,9 @@ type ContentCoordinator struct {
3637
3738 // Callback when title changes (for history persistence)
3839 onTitleUpdated func(ctx context.Context, paneID entity.PaneID, url, title string)
40+
41+ // Gesture action handler for mouse button navigation
42+ gestureActionHandler input.ActionHandler
3943 }
4044
4145 // NewContentCoordinator creates a new ContentCoordinator.
@@ -67,6 +71,11 @@ func (c *ContentCoordinator) SetOnTitleUpdated(fn func(ctx context.Context, pane
6771 c.onTitleUpdated = fn
6872 }
6973
74+// SetGestureActionHandler sets the callback for mouse button navigation gestures.
75+func (c *ContentCoordinator) SetGestureActionHandler(handler input.ActionHandler) {
76+ c.gestureActionHandler = handler
77+}
78+
7079 // EnsureWebView acquires or reuses a WebView for the given pane.
7180 func (c *ContentCoordinator) EnsureWebView(ctx context.Context, paneID entity.PaneID) (*webkit.WebView, error) {
7281 log := logging.FromContext(ctx)
@@ -174,6 +183,7 @@ func (c *ContentCoordinator) AttachToWorkspace(ctx context.Context, ws *entity.W
174183 }
175184
176185 // WrapWidget converts a WebView to a layout.Widget for embedding.
186+// It also attaches gesture handlers for mouse button navigation.
177187 func (c *ContentCoordinator) WrapWidget(ctx context.Context, wv *webkit.WebView) layout.Widget {
178188 log := logging.FromContext(ctx)
179189
@@ -187,7 +197,17 @@ func (c *ContentCoordinator) WrapWidget(ctx context.Context, wv *webkit.WebView)
187197 return nil
188198 }
189199
190- return c.widgetFactory.WrapWidget(&gtkView.Widget)
200+ widget := c.widgetFactory.WrapWidget(&gtkView.Widget)
201+
202+ // Attach gesture handler for mouse button 8/9 navigation
203+ if widget != nil && c.gestureActionHandler != nil {
204+ gestureHandler := input.NewGestureHandler(ctx)
205+ gestureHandler.SetOnAction(c.gestureActionHandler)
206+ gestureHandler.AttachTo(widget.GtkWidget())
207+ log.Debug().Msg("gesture handler attached to webview")
208+ }
209+
210+ return widget
191211 }
192212
193213 // ActiveWebView returns the WebView for the active pane.
@@ -11,6 +11,7 @@ import (
11 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
12 "github.com/bnema/dumber/internal/ui/cache"12 "github.com/bnema/dumber/internal/ui/cache"
13 "github.com/bnema/dumber/internal/ui/component"13 "github.com/bnema/dumber/internal/ui/component"
14+ "github.com/bnema/dumber/internal/ui/input"
14 "github.com/bnema/dumber/internal/ui/layout"15 "github.com/bnema/dumber/internal/ui/layout"
15 "github.com/jwijenbergh/puregotk/v4/gdk"16 "github.com/jwijenbergh/puregotk/v4/gdk"
16 )17 )
@@ -36,6 +37,9 @@ type ContentCoordinator struct {
36 37
37 // Callback when title changes (for history persistence)38 // Callback when title changes (for history persistence)
38 onTitleUpdated func(ctx context.Context, paneID entity.PaneID, url, title string)39 onTitleUpdated func(ctx context.Context, paneID entity.PaneID, url, title string)
40+
41+ // Gesture action handler for mouse button navigation
42+ gestureActionHandler input.ActionHandler
39 }43 }
40 44
41 // NewContentCoordinator creates a new ContentCoordinator.45 // NewContentCoordinator creates a new ContentCoordinator.
@@ -67,6 +71,11 @@ func (c *ContentCoordinator) SetOnTitleUpdated(fn func(ctx context.Context, pane
67 c.onTitleUpdated = fn71 c.onTitleUpdated = fn
68 }72 }
69 73
74+// SetGestureActionHandler sets the callback for mouse button navigation gestures.
75+func (c *ContentCoordinator) SetGestureActionHandler(handler input.ActionHandler) {
76+ c.gestureActionHandler = handler
77+}
78+
70 // EnsureWebView acquires or reuses a WebView for the given pane.79 // EnsureWebView acquires or reuses a WebView for the given pane.
71 func (c *ContentCoordinator) EnsureWebView(ctx context.Context, paneID entity.PaneID) (*webkit.WebView, error) {80 func (c *ContentCoordinator) EnsureWebView(ctx context.Context, paneID entity.PaneID) (*webkit.WebView, error) {
72 log := logging.FromContext(ctx)81 log := logging.FromContext(ctx)
@@ -174,6 +183,7 @@ func (c *ContentCoordinator) AttachToWorkspace(ctx context.Context, ws *entity.W
174 }183 }
175 184
176 // WrapWidget converts a WebView to a layout.Widget for embedding.185 // WrapWidget converts a WebView to a layout.Widget for embedding.
186+// It also attaches gesture handlers for mouse button navigation.
177 func (c *ContentCoordinator) WrapWidget(ctx context.Context, wv *webkit.WebView) layout.Widget {187 func (c *ContentCoordinator) WrapWidget(ctx context.Context, wv *webkit.WebView) layout.Widget {
178 log := logging.FromContext(ctx)188 log := logging.FromContext(ctx)
179 189
@@ -187,7 +197,17 @@ func (c *ContentCoordinator) WrapWidget(ctx context.Context, wv *webkit.WebView)
187 return nil197 return nil
188 }198 }
189 199
190- return c.widgetFactory.WrapWidget(&gtkView.Widget)200+ widget := c.widgetFactory.WrapWidget(&gtkView.Widget)
201+
202+ // Attach gesture handler for mouse button 8/9 navigation
203+ if widget != nil && c.gestureActionHandler != nil {
204+ gestureHandler := input.NewGestureHandler(ctx)
205+ gestureHandler.SetOnAction(c.gestureActionHandler)
206+ gestureHandler.AttachTo(widget.GtkWidget())
207+ log.Debug().Msg("gesture handler attached to webview")
208+ }
209+
210+ return widget
191 }211 }
192 212
193 // ActiveWebView returns the WebView for the active pane.213 // ActiveWebView returns the WebView for the active pane.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close