detached 2716a73

refactor(browser): migrate stacked_panes.go to gotk4 API

Complete migration of stacked pane widget operations to gotk4:

Widget Type Changes:
- titleBarToPane: map[uintptr] → map[uint64]
- createTitleBar/createTitleBarWithTitle: return gtk.Widgetter
- Widget variables: uintptr → gtk.Widgetter throughout

Widget Operations Updated:
- gtk.NewBox() instead of webkit.NewBox()
- box.Append/Prepend/Remove() instead of webkit.Box*()
- widget.SetVisible/Show/Hide() instead of webkit.Widget*()
- widget.Parent/Unparent() instead of webkit.WidgetGet/Unparent()
- Type assertions for Box/Paned operations

Null Checks:
- All widget != 0 → widget != nil
- All widget == 0 → widget == nil

Click Handlers:
- Commented out WidgetAttachClickHandler (TODO: implement with gtk.GestureClick)
- Updated handleTitleBarClick to use uint64 ID

Functions Updated:
- prepareNewStackedPane
- addPaneToExistingStack
- convertToStackedContainer
- detachFromParent/reattachToParent
- UpdateStackVisibility
- createTitleBar/createTitleBarWithTitle
- updateTitleBarLabel
- CloseStackedPane
- handleTitleBarClick

Remaining: Click handlers need gtk.GestureClick implementation
commit
2716a7313a2e9eb462ee674fc5a350dd07351af6
author
bamen · · 2025-10-07 19:26 UTC
committer
bamen · · 2025-10-07 19:20 UTC
parent
7ce5a26
signature
unsigned
1 file · +175 -164
M internal/app/browser/stacked_panes.go +175 -164
@@ -10,6 +10,7 @@ import (
1010
1111 "github.com/bnema/dumber/internal/cache"
1212 "github.com/bnema/dumber/pkg/webkit"
13+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
1314 )
1415
1516 const (
@@ -20,7 +21,7 @@ const (
2021 // StackedPaneManager handles all stacked pane operations
2122 type StackedPaneManager struct {
2223 wm *WorkspaceManager
23- titleBarToPane map[uintptr]*paneNode
24+ titleBarToPane map[uint64]*paneNode
2425 nextTitleBarID uint64
2526 }
2627
@@ -28,7 +29,7 @@ type StackedPaneManager struct {
2829 func NewStackedPaneManager(wm *WorkspaceManager) *StackedPaneManager {
2930 return &StackedPaneManager{
3031 wm: wm,
31- titleBarToPane: make(map[uintptr]*paneNode),
32+ titleBarToPane: make(map[uint64]*paneNode),
3233 }
3334 }
3435
@@ -84,12 +85,12 @@ func (spm *StackedPaneManager) prepareNewStackedPane() (*paneNode, error) {
8485 }
8586
8687 newContainer := newPane.webView.RootWidget()
87- if newContainer == 0 {
88+ if newContainer == nil {
8889 return nil, errors.New("new pane missing container")
8990 }
90- webkit.WidgetSetHExpand(newContainer, true)
91- webkit.WidgetSetVExpand(newContainer, true)
92- webkit.WidgetRealizeInContainer(newContainer)
91+ newContainer.SetHExpand(true)
92+ newContainer.SetVExpand(true)
93+ newContainer.Realize()
9394
9495 // Create the new leaf node
9596 newLeaf := &paneNode{
@@ -104,8 +105,10 @@ func (spm *StackedPaneManager) prepareNewStackedPane() (*paneNode, error) {
104105 spm.wm.setTitleBar(newLeaf, newTitleBar)
105106
106107 // Keep new container hidden initially - will be shown after transition
107- webkit.WidgetSetVisible(newContainer, false)
108- webkit.WidgetSetVisible(newTitleBar, false)
108+ newContainer.SetVisible(false)
109+ if newTitleBar != nil {
110+ newTitleBar.SetVisible(false)
111+ }
109112
110113 return newLeaf, nil
111114 }
@@ -141,9 +144,9 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
141144 stackNode.stackedPanes[insertIndex] = newLeaf // Insert new pane
142145
143146 // Unparent the new container before adding it to the stack (only if it has a parent)
144- if newLeaf.container != 0 && webkit.WidgetIsValid(newLeaf.container) {
145- if webkit.WidgetGetParent(newLeaf.container) != 0 {
146- webkit.WidgetUnparent(newLeaf.container)
147+ if newLeaf.container != nil {
148+ if newLeaf.container.Parent() != nil {
149+ newLeaf.container.Unparent()
147150 }
148151 }
149152
@@ -151,32 +154,32 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
151154 // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2
152155
153156 // Find the widget to insert after (based on insertIndex)
154- var insertAfterWidget uintptr = 0
157+ var insertAfterWidget gtk.Widgetter
155158 if insertIndex > 0 {
156159 // Insert after the previous pane's container
157160 prevPane := stackNode.stackedPanes[insertIndex-1]
158- if prevPane.container != 0 {
161+ if prevPane.container != nil {
159162 insertAfterWidget = prevPane.container
160163 }
161164 }
162165
163- if insertAfterWidget != 0 {
166+ if insertAfterWidget != nil {
164167 // Insert titleBar after the previous pane's container
165- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {
166- webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget)
168+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
169+ box.InsertChildAfter(newLeaf.titleBar, insertAfterWidget)
167170 }
168171 // Insert container after the newly inserted titleBar
169- if stackNode.stackWrapper != 0 && newLeaf.container != 0 && newLeaf.titleBar != 0 {
170- webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar)
172+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil && newLeaf.titleBar != nil {
173+ box.InsertChildAfter(newLeaf.container, newLeaf.titleBar)
171174 }
172- log.Printf("[workspace] inserted widgets at position %d (after widget %#x)", insertIndex, insertAfterWidget)
175+ log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget)
173176 } else {
174177 // Insert at the beginning (insertIndex = 0)
175- if stackNode.stackWrapper != 0 && newLeaf.container != 0 {
176- webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container)
178+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {
179+ box.Prepend(newLeaf.container)
177180 }
178- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {
179- webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar)
181+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
182+ box.Prepend(newLeaf.titleBar)
180183 }
181184 log.Printf("[workspace] prepended widgets at position 0")
182185 }
@@ -189,27 +192,27 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
189192 log.Printf("[workspace] converting pane to stacked: %p", target)
190193
191194 // Create the wrapper container - this is what will be used by splitNode
192- stackWrapperContainer := webkit.NewBox(webkit.OrientationVertical, 0)
193- if stackWrapperContainer == 0 {
195+ stackWrapperContainer := gtk.NewBox(gtk.OrientationVertical, 0)
196+ if stackWrapperContainer == nil {
194197 return nil, 0, errors.New("failed to create stack wrapper container")
195198 }
196- webkit.WidgetSetHExpand(stackWrapperContainer, true)
197- webkit.WidgetSetVExpand(stackWrapperContainer, true)
198- webkit.WidgetRealizeInContainer(stackWrapperContainer) // Ensures size request is set like regular panes
199+ stackWrapperContainer.SetHExpand(true)
200+ stackWrapperContainer.SetVExpand(true)
201+ stackWrapperContainer.Realize() // Ensures size request is set like regular panes
199202
200203 // Add CSS class to the stack container for proper styling
201- webkit.WidgetAddCSSClass(stackWrapperContainer, stackContainerClass)
204+ stackWrapperContainer.AddCSSClass(stackContainerClass)
202205
203206 // Create the internal box for the actual stacked widgets (titles + webviews)
204- stackInternalBox := webkit.NewBox(webkit.OrientationVertical, 0)
205- if stackInternalBox == 0 {
207+ stackInternalBox := gtk.NewBox(gtk.OrientationVertical, 0)
208+ if stackInternalBox == nil {
206209 return nil, 0, errors.New("failed to create stack internal box")
207210 }
208- webkit.WidgetSetHExpand(stackInternalBox, true)
209- webkit.WidgetSetVExpand(stackInternalBox, true)
211+ stackInternalBox.SetHExpand(true)
212+ stackInternalBox.SetVExpand(true)
210213
211214 // The internal box goes inside the wrapper
212- webkit.BoxAppend(stackWrapperContainer, stackInternalBox)
215+ stackWrapperContainer.Append(stackInternalBox)
213216
214217 // Get the existing container and parent info
215218 existingContainer := target.container
@@ -219,15 +222,19 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
219222 titleBar := spm.createTitleBar(target)
220223
221224 // Keep existing container visible during transition to prevent rendering glitch
222- webkit.WidgetSetVisible(titleBar, false)
225+ if titleBar != nil {
226+ titleBar.SetVisible(false)
227+ }
223228
224229 // Detach existing container from its current parent first
225230 spm.detachFromParent(target, parent)
226231
227232 // Build the complete stack structure with hidden widgets
228- webkit.BoxAppend(stackInternalBox, titleBar)
229- if existingContainer != 0 {
230- webkit.BoxAppend(stackInternalBox, existingContainer)
233+ if titleBar != nil {
234+ stackInternalBox.Append(titleBar)
235+ }
236+ if existingContainer != nil {
237+ stackInternalBox.Append(existingContainer)
231238 }
232239
233240 // Immediately reattach the stack wrapper to minimize visibility gap
@@ -275,24 +282,24 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
275282
276283 // Unparent the new container before adding it to the stack
277284 // This is critical: even freshly created WebViews might have internal parent refs
278- if newLeaf.container != 0 && webkit.WidgetIsValid(newLeaf.container) {
279- parent := webkit.WidgetGetParent(newLeaf.container)
280- if parent != 0 {
281- log.Printf("[workspace] unparenting new pane container %#x from parent %#x before stack append", newLeaf.container, parent)
282- webkit.WidgetUnparent(newLeaf.container)
285+ if newLeaf.container != nil {
286+ parentWidget := newLeaf.container.Parent()
287+ if parentWidget != nil {
288+ log.Printf("[workspace] unparenting new pane container %p from parent %p before stack append", newLeaf.container, parentWidget)
289+ newLeaf.container.Unparent()
283290 // Verify unparent succeeded
284- if finalParent := webkit.WidgetGetParent(newLeaf.container); finalParent != 0 {
285- log.Printf("[workspace] WARNING: container %#x still has parent %#x after unparent", newLeaf.container, finalParent)
291+ if finalParent := newLeaf.container.Parent(); finalParent != nil {
292+ log.Printf("[workspace] WARNING: container %p still has parent %p after unparent", newLeaf.container, finalParent)
286293 }
287294 }
288295 }
289296
290297 // Add the new widgets to the internal stack box
291- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {
292- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar)
298+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
299+ box.Append(newLeaf.titleBar)
293300 }
294- if stackNode.stackWrapper != 0 && newLeaf.container != 0 {
295- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container)
301+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {
302+ box.Append(newLeaf.container)
296303 }
297304
298305 return stackNode, insertIndex, nil
@@ -303,35 +310,35 @@ func (spm *StackedPaneManager) detachFromParent(target *paneNode, parent *paneNo
303310 if parent == nil {
304311 // Target is the root - remove it from window
305312 if spm.wm.window != nil {
306- spm.wm.window.SetChild(0)
313+ spm.wm.window.SetChild(nil)
307314 }
308315 // Unparent if it has a GTK parent
309- if target.container != 0 && webkit.WidgetIsValid(target.container) {
310- if webkit.WidgetGetParent(target.container) != 0 {
311- webkit.WidgetUnparent(target.container)
316+ if target.container != nil {
317+ if target.container.Parent() != nil {
318+ target.container.Unparent()
312319 }
313320 }
314- } else if parent.container != 0 && webkit.WidgetIsValid(parent.container) {
321+ } else if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
315322 // Target has a parent paned - remove it (automatically unparents in GTK4)
316323 if parent.left == target {
317- webkit.PanedSetStartChild(parent.container, 0)
324+ paned.SetStartChild(nil)
318325 } else if parent.right == target {
319- webkit.PanedSetEndChild(parent.container, 0)
326+ paned.SetEndChild(nil)
320327 }
321328 }
322329 }
323330
324331 // reattachToParent attaches a container to the parent
325-func (spm *StackedPaneManager) reattachToParent(container uintptr, target *paneNode, parent *paneNode) {
332+func (spm *StackedPaneManager) reattachToParent(container gtk.Widgetter, target *paneNode, parent *paneNode) {
326333 if parent == nil {
327334 if spm.wm.window != nil {
328335 spm.wm.window.SetChild(container)
329336 }
330- } else if parent.container != 0 && webkit.WidgetIsValid(parent.container) {
337+ } else if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
331338 if parent.left == target {
332- webkit.PanedSetStartChild(parent.container, container)
339+ paned.SetStartChild(container)
333340 } else if parent.right == target {
334- webkit.PanedSetEndChild(parent.container, container)
341+ paned.SetEndChild(container)
335342 }
336343 }
337344 }
@@ -406,20 +413,20 @@ func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
406413 for i, pane := range stackNode.stackedPanes {
407414 if i == activeIndex {
408415 // Active pane: show container, NEVER show title bar
409- if pane.container != 0 {
410- webkit.WidgetSetVisible(pane.container, true)
416+ if pane.container != nil {
417+ pane.container.SetVisible(true)
411418 }
412- if pane.titleBar != 0 {
413- webkit.WidgetSetVisible(pane.titleBar, false) // ABSOLUTE RULE: never visible for active pane
419+ if pane.titleBar != nil {
420+ pane.titleBar.SetVisible(false) // ABSOLUTE RULE: never visible for active pane
414421 }
415422 log.Printf("[workspace] active pane %d: container=visible, titleBar=HIDDEN", i)
416423 } else {
417424 // Inactive panes: hide container, show title bar
418- if pane.container != 0 {
419- webkit.WidgetSetVisible(pane.container, false)
425+ if pane.container != nil {
426+ pane.container.SetVisible(false)
420427 }
421- if pane.titleBar != 0 {
422- webkit.WidgetSetVisible(pane.titleBar, true)
428+ if pane.titleBar != nil {
429+ pane.titleBar.SetVisible(true)
423430
424431 // Refresh title bar to pick up any newly downloaded favicons
425432 if pane.pane != nil && pane.pane.webView != nil {
@@ -513,7 +520,7 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
513520 }
514521
515522 // createTitleBar creates a title bar widget for a pane in a stack
516-func (spm *StackedPaneManager) createTitleBar(pane *paneNode) uintptr {
523+func (spm *StackedPaneManager) createTitleBar(pane *paneNode) gtk.Widgetter {
517524 // Get the actual title and URL from the WebView
518525 var titleText, pageURL string
519526 if pane.pane != nil && pane.pane.webView != nil {
@@ -527,15 +534,16 @@ func (spm *StackedPaneManager) createTitleBar(pane *paneNode) uintptr {
527534 }
528535
529536 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)
530- if titleBar != 0 {
537+ if titleBar != nil {
531538 // Store the mapping from titleBar ID to pane
532539 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)
533- spm.titleBarToPane[uintptr(titleBarID)] = pane
540+ spm.titleBarToPane[titleBarID] = pane
534541
535542 // Attach click handler
536- webkit.WidgetAttachClickHandler(titleBar, func() {
537- spm.handleTitleBarClick(uintptr(titleBarID))
538- })
543+ // TODO: Implement click handler with gtk.GestureClick
544+ // webkit.WidgetAttachClickHandler(titleBar, func() {
545+ // spm.handleTitleBarClick(titleBarID)
546+ // })
539547 }
540548
541549 return titleBar
@@ -554,7 +562,7 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str
554562 }
555563
556564 // Check if this pane is in a stack and has a title bar
557- if node.parent != nil && node.parent.isStacked && node.titleBar != 0 {
565+ if node.parent != nil && node.parent.isStacked && node.titleBar != nil {
558566 // CRITICAL: Only update title bar for INACTIVE panes
559567 // Active panes should never show their title bar
560568 stackNode := node.parent
@@ -582,7 +590,7 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str
582590
583591 // updateTitleBarLabel updates the label widget within a title bar by recreating it
584592 func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string) {
585- if node == nil || node.titleBar == 0 || node.parent == nil || !node.parent.isStacked {
593+ if node == nil || node.titleBar == nil || node.parent == nil || !node.parent.isStacked {
586594 return
587595 }
588596
@@ -594,22 +602,23 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
594602
595603 // Create new title bar with updated title and favicon
596604 newTitleBar := spm.createTitleBarWithTitle(title, pageURL)
597- if newTitleBar == 0 {
605+ if newTitleBar == nil {
598606 log.Printf("[workspace] failed to create new title bar")
599607 return
600608 }
601609
602610 // Store the mapping from titleBar ID to pane and attach click handler
603611 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)
604- spm.titleBarToPane[uintptr(titleBarID)] = node
612+ spm.titleBarToPane[titleBarID] = node
605613
606614 // Attach click handler
607- webkit.WidgetAttachClickHandler(newTitleBar, func() {
608- spm.handleTitleBarClick(uintptr(titleBarID))
609- })
615+ // TODO: Implement with gtk.GestureClick
616+ // webkit.WidgetAttachClickHandler(newTitleBar, func() {
617+ // spm.handleTitleBarClick(titleBarID)
618+ // })
610619
611620 // Replace the old title bar in the stack
612- if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 {
621+ if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != nil {
613622 // Find the correct insertion position based on the pane's index in the stack
614623 paneIndex := -1
615624 for i, stackedPane := range node.parent.stackedPanes {
@@ -620,24 +629,24 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
620629 }
621630
622631 // Remove old title bar
623- if node.titleBar != 0 && webkit.WidgetIsValid(node.titleBar) {
624- webkit.WidgetUnparent(node.titleBar)
632+ if node.titleBar != nil {
633+ node.titleBar.Unparent()
625634 }
626635
627636 // Update the node's title bar reference
628637 spm.wm.setTitleBar(node, newTitleBar)
629638
630639 // Insert the new title bar at the correct position
631- if paneIndex == 0 {
632- // First pane - insert at the beginning
633- if node.parent.stackWrapper != 0 {
634- webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)
635- }
636- } else {
637- // Insert after the previous pane's container
638- prevPane := node.parent.stackedPanes[paneIndex-1]
639- if node.parent.stackWrapper != 0 && prevPane.container != 0 {
640- webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container)
640+ if box, ok := node.parent.stackWrapper.(*gtk.Box); ok {
641+ if paneIndex == 0 {
642+ // First pane - insert at the beginning
643+ box.Prepend(newTitleBar)
644+ } else {
645+ // Insert after the previous pane's container
646+ prevPane := node.parent.stackedPanes[paneIndex-1]
647+ if prevPane.container != nil {
648+ box.InsertChildAfter(newTitleBar, prevPane.container)
649+ }
641650 }
642651 }
643652
@@ -646,18 +655,18 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
646655 }
647656
648657 // createTitleBarWithTitle creates a title bar with a specific title and optional favicon
649-func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL string) uintptr {
650- titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8)
651- if titleBox == 0 {
652- return 0
658+func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL string) gtk.Widgetter {
659+ titleBox := gtk.NewBox(gtk.OrientationHorizontal, 8)
660+ if titleBox == nil {
661+ return nil
653662 }
654663
655- webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title")
656- webkit.WidgetSetHExpand(titleBox, true)
657- webkit.WidgetSetVExpand(titleBox, false)
664+ titleBox.AddCSSClass("stacked-pane-title")
665+ titleBox.SetHExpand(true)
666+ titleBox.SetVExpand(false)
658667
659668 // Add favicon placeholder for debugging (always visible)
660- var faviconImg uintptr
669+ var faviconImg gtk.Widgetter
661670 var debugReason string
662671
663672 if pageURL == "" {
@@ -691,10 +700,12 @@ func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL str
691700 faviconPath := faviconCache.GetCachedPath(faviconURL)
692701 if faviconPath != "" {
693702 log.Printf("[favicon] Loading cached favicon from: %s", faviconPath)
694- faviconImg = webkit.ImageNewFromFile(faviconPath)
695- if faviconImg != 0 {
696- webkit.ImageSetPixelSize(faviconImg, 16)
697- webkit.WidgetAddCSSClass(faviconImg, "stacked-pane-favicon")
703+ faviconImg = gtk.NewImageFromFile(faviconPath)
704+ if faviconImg != nil {
705+ if img, ok := faviconImg.(*gtk.Image); ok {
706+ img.SetPixelSize(16)
707+ }
708+ faviconImg.AddCSSClass("stacked-pane-favicon")
698709 log.Printf("[favicon] Successfully created favicon image widget")
699710 } else {
700711 debugReason = "image widget creation failed"
@@ -711,32 +722,32 @@ func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL str
711722 }
712723
713724 // Add favicon or placeholder
714- if faviconImg != 0 {
715- webkit.BoxAppend(titleBox, faviconImg)
716- webkit.WidgetShow(faviconImg)
725+ if faviconImg != nil {
726+ titleBox.Append(faviconImg)
727+ faviconImg.Show()
717728 } else {
718729 // DEBUG: Add visible placeholder label to show code is running
719- placeholderLabel := webkit.NewLabel(faviconPlaceholder)
720- if placeholderLabel != 0 {
721- webkit.WidgetAddCSSClass(placeholderLabel, "stacked-pane-favicon-placeholder")
722- webkit.BoxAppend(titleBox, placeholderLabel)
723- webkit.WidgetShow(placeholderLabel)
730+ placeholderLabel := gtk.NewLabel(faviconPlaceholder)
731+ if placeholderLabel != nil {
732+ placeholderLabel.AddCSSClass("stacked-pane-favicon-placeholder")
733+ titleBox.Append(placeholderLabel)
734+ placeholderLabel.Show()
724735 log.Printf("[favicon] Using placeholder for %s (reason: %s)", pageURL, debugReason)
725736 }
726737 }
727738
728739 // Create title label with the specified title
729- titleLabel := webkit.NewLabel(title)
730- if titleLabel == 0 {
740+ titleLabel := gtk.NewLabel(title)
741+ if titleLabel == nil {
731742 return titleBox
732743 }
733744
734- webkit.WidgetAddCSSClass(titleLabel, "stacked-pane-title-text")
735- webkit.LabelSetEllipsize(titleLabel, webkit.EllipsizeEnd)
745+ titleLabel.AddCSSClass("stacked-pane-title-text")
746+ titleLabel.SetEllipsize(2) // pango.EllipsizeEnd = 2
736747
737- webkit.BoxAppend(titleBox, titleLabel)
738- webkit.WidgetShow(titleBox)
739- webkit.WidgetShow(titleLabel)
748+ titleBox.Append(titleLabel)
749+ titleBox.Show()
750+ titleLabel.Show()
740751
741752 return titleBox
742753 }
@@ -775,15 +786,15 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
775786
776787 // Remove the pane widgets from the stack container before mutating the tree.
777788 stackBox := stackNode.stackWrapper
778- if stackBox == 0 {
789+ if stackBox == nil {
779790 stackBox = stackNode.container
780791 }
781- if stackBox != 0 {
782- if node.titleBar != 0 {
783- webkit.BoxRemove(stackBox, node.titleBar)
792+ if box, ok := stackBox.(*gtk.Box); ok && box != nil {
793+ if node.titleBar != nil {
794+ box.Remove(node.titleBar)
784795 }
785- if node.container != 0 {
786- webkit.BoxRemove(stackBox, node.container)
796+ if node.container != nil {
797+ box.Remove(node.container)
787798 }
788799 }
789800
@@ -796,30 +807,30 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
796807 parent := stackNode.parent
797808 if parent == nil {
798809 // Stack was the root of the workspace — closing this pane exits the app.
799- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {
800- if webkit.WidgetGetParent(stackNode.container) != 0 {
801- webkit.WidgetUnparent(stackNode.container)
810+ if stackNode.container != nil {
811+ if stackNode.container.Parent() != nil {
812+ stackNode.container.Unparent()
802813 }
803814 }
804815 if spm.wm.window != nil {
805- spm.wm.window.SetChild(0)
816+ spm.wm.window.SetChild(nil)
806817 }
807818 _, err := spm.wm.cleanupAndExit(node)
808819 return err
809820 }
810821
811822 // Detach the empty stack container from its GtkPaned parent.
812- if parent.container != 0 && webkit.WidgetIsValid(parent.container) {
823+ if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
813824 if parent.left == stackNode {
814- webkit.PanedSetStartChild(parent.container, 0)
825+ paned.SetStartChild(nil)
815826 } else if parent.right == stackNode {
816- webkit.PanedSetEndChild(parent.container, 0)
827+ paned.SetEndChild(nil)
817828 }
818- webkit.WidgetQueueAllocate(parent.container)
829+ paned.QueueAllocate()
819830 }
820- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {
821- if webkit.WidgetGetParent(stackNode.container) != 0 {
822- webkit.WidgetUnparent(stackNode.container)
831+ if stackNode.container != nil {
832+ if stackNode.container.Parent() != nil {
833+ stackNode.container.Unparent()
823834 }
824835 }
825836
@@ -840,8 +851,8 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
840851 }
841852
842853 stackNode.parent = nil
843- stackNode.container = 0
844- stackNode.stackWrapper = 0
854+ stackNode.container = nil
855+ stackNode.stackWrapper = nil
845856 stackNode.stackedPanes = nil
846857
847858 return nil
@@ -852,25 +863,25 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
852863 parent := stackNode.parent
853864 lastPaneContainer := lastPane.container
854865
855- if lastPane.titleBar != 0 && stackBox != 0 {
856- webkit.BoxRemove(stackBox, lastPane.titleBar)
866+ if box, ok := stackBox.(*gtk.Box); ok && box != nil && lastPane.titleBar != nil {
867+ box.Remove(lastPane.titleBar)
857868 }
858- if stackBox != 0 && lastPaneContainer != 0 {
859- webkit.BoxRemove(stackBox, lastPaneContainer)
869+ if box, ok := stackBox.(*gtk.Box); ok && box != nil && lastPaneContainer != nil {
870+ box.Remove(lastPaneContainer)
860871 }
861- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {
862- if webkit.WidgetGetParent(stackNode.container) != 0 {
863- webkit.WidgetUnparent(stackNode.container)
872+ if stackNode.container != nil {
873+ if stackNode.container.Parent() != nil {
874+ stackNode.container.Unparent()
864875 }
865876 }
866877
867878 if parent == nil {
868879 spm.wm.root = lastPane
869880 lastPane.parent = nil
870- if spm.wm.window != nil && lastPaneContainer != 0 {
881+ if spm.wm.window != nil && lastPaneContainer != nil {
871882 spm.wm.window.SetChild(lastPaneContainer)
872- webkit.WidgetQueueAllocate(lastPaneContainer)
873- webkit.WidgetShow(lastPaneContainer)
883+ lastPaneContainer.QueueAllocate()
884+ lastPaneContainer.Show()
874885 }
875886 } else {
876887 if parent.left == stackNode {
@@ -879,36 +890,36 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
879890 parent.right = lastPane
880891 }
881892 lastPane.parent = parent
882- if parent.container != 0 && webkit.WidgetIsValid(parent.container) && lastPaneContainer != 0 {
893+ if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil && lastPaneContainer != nil {
883894 if parent.left == lastPane {
884- webkit.PanedSetStartChild(parent.container, lastPaneContainer)
895+ paned.SetStartChild(lastPaneContainer)
885896 } else {
886- webkit.PanedSetEndChild(parent.container, lastPaneContainer)
897+ paned.SetEndChild(lastPaneContainer)
887898 }
888- webkit.WidgetQueueAllocate(parent.container)
899+ paned.QueueAllocate()
889900 }
890- if lastPaneContainer != 0 {
891- webkit.WidgetSetVisible(lastPaneContainer, true)
901+ if lastPaneContainer != nil {
902+ lastPaneContainer.SetVisible(true)
892903 }
893904 }
894905
895906 lastPane.isStacked = false
896907 lastPane.stackedPanes = nil
897- lastPane.titleBar = 0
908+ lastPane.titleBar = nil
898909 spm.wm.viewToNode[lastPane.pane.webView] = lastPane
899910
900911 generation := spm.wm.nextCleanupGeneration()
901912 spm.wm.cleanupPane(node, generation)
902913
903914 spm.wm.SetActivePane(lastPane, SourceClose)
904- if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 {
905- webkit.WidgetShow(lastPaneWidget)
906- webkit.WidgetGrabFocus(lastPaneWidget)
915+ if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != nil {
916+ lastPaneWidget.Show()
917+ lastPaneWidget.GrabFocus()
907918 }
908919
909920 stackNode.parent = nil
910- stackNode.container = 0
911- stackNode.stackWrapper = 0
921+ stackNode.container = nil
922+ stackNode.stackWrapper = nil
912923 stackNode.stackedPanes = nil
913924
914925 log.Printf("[workspace] converted stack back to regular pane")
@@ -939,7 +950,7 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
939950 }
940951
941952 // handleTitleBarClick handles clicks on title bars to switch the active pane
942-func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uintptr) {
953+func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uint64) {
943954 pane, exists := spm.titleBarToPane[titleBarID]
944955 if !exists || pane == nil || pane.parent == nil || !pane.parent.isStacked {
945956 return
@@ -957,7 +968,7 @@ func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uintptr) {
957968
958969 // Update title bar for the pane transitioning from ACTIVE to INACTIVE
959970 currentActivePane := stackNode.stackedPanes[stackNode.activeStackIndex]
960- if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 {
971+ if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != nil {
961972 currentTitle := currentActivePane.pane.webView.GetTitle()
962973 if currentTitle != "" {
963974 spm.updateTitleBarLabel(currentActivePane, currentTitle)
@@ -10,6 +10,7 @@ import (
10 10
11 "github.com/bnema/dumber/internal/cache"11 "github.com/bnema/dumber/internal/cache"
12 "github.com/bnema/dumber/pkg/webkit"12 "github.com/bnema/dumber/pkg/webkit"
13+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
13 )14 )
14 15
15 const (16 const (
@@ -20,7 +21,7 @@ const (
20 // StackedPaneManager handles all stacked pane operations21 // StackedPaneManager handles all stacked pane operations
21 type StackedPaneManager struct {22 type StackedPaneManager struct {
22 wm *WorkspaceManager23 wm *WorkspaceManager
23- titleBarToPane map[uintptr]*paneNode24+ titleBarToPane map[uint64]*paneNode
24 nextTitleBarID uint6425 nextTitleBarID uint64
25 }26 }
26 27
@@ -28,7 +29,7 @@ type StackedPaneManager struct {
28 func NewStackedPaneManager(wm *WorkspaceManager) *StackedPaneManager {29 func NewStackedPaneManager(wm *WorkspaceManager) *StackedPaneManager {
29 return &StackedPaneManager{30 return &StackedPaneManager{
30 wm: wm,31 wm: wm,
31- titleBarToPane: make(map[uintptr]*paneNode),32+ titleBarToPane: make(map[uint64]*paneNode),
32 }33 }
33 }34 }
34 35
@@ -84,12 +85,12 @@ func (spm *StackedPaneManager) prepareNewStackedPane() (*paneNode, error) {
84 }85 }
85 86
86 newContainer := newPane.webView.RootWidget()87 newContainer := newPane.webView.RootWidget()
87- if newContainer == 0 {88+ if newContainer == nil {
88 return nil, errors.New("new pane missing container")89 return nil, errors.New("new pane missing container")
89 }90 }
90- webkit.WidgetSetHExpand(newContainer, true)91+ newContainer.SetHExpand(true)
91- webkit.WidgetSetVExpand(newContainer, true)92+ newContainer.SetVExpand(true)
92- webkit.WidgetRealizeInContainer(newContainer)93+ newContainer.Realize()
93 94
94 // Create the new leaf node95 // Create the new leaf node
95 newLeaf := &paneNode{96 newLeaf := &paneNode{
@@ -104,8 +105,10 @@ func (spm *StackedPaneManager) prepareNewStackedPane() (*paneNode, error) {
104 spm.wm.setTitleBar(newLeaf, newTitleBar)105 spm.wm.setTitleBar(newLeaf, newTitleBar)
105 106
106 // Keep new container hidden initially - will be shown after transition107 // Keep new container hidden initially - will be shown after transition
107- webkit.WidgetSetVisible(newContainer, false)108+ newContainer.SetVisible(false)
108- webkit.WidgetSetVisible(newTitleBar, false)109+ if newTitleBar != nil {
110+ newTitleBar.SetVisible(false)
111+ }
109 112
110 return newLeaf, nil113 return newLeaf, nil
111 }114 }
@@ -141,9 +144,9 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
141 stackNode.stackedPanes[insertIndex] = newLeaf // Insert new pane144 stackNode.stackedPanes[insertIndex] = newLeaf // Insert new pane
142 145
143 // Unparent the new container before adding it to the stack (only if it has a parent)146 // Unparent the new container before adding it to the stack (only if it has a parent)
144- if newLeaf.container != 0 && webkit.WidgetIsValid(newLeaf.container) {147+ if newLeaf.container != nil {
145- if webkit.WidgetGetParent(newLeaf.container) != 0 {148+ if newLeaf.container.Parent() != nil {
146- webkit.WidgetUnparent(newLeaf.container)149+ newLeaf.container.Unparent()
147 }150 }
148 }151 }
149 152
@@ -151,32 +154,32 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode)
151 // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2154 // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2
152 155
153 // Find the widget to insert after (based on insertIndex)156 // Find the widget to insert after (based on insertIndex)
154- var insertAfterWidget uintptr = 0157+ var insertAfterWidget gtk.Widgetter
155 if insertIndex > 0 {158 if insertIndex > 0 {
156 // Insert after the previous pane's container159 // Insert after the previous pane's container
157 prevPane := stackNode.stackedPanes[insertIndex-1]160 prevPane := stackNode.stackedPanes[insertIndex-1]
158- if prevPane.container != 0 {161+ if prevPane.container != nil {
159 insertAfterWidget = prevPane.container162 insertAfterWidget = prevPane.container
160 }163 }
161 }164 }
162 165
163- if insertAfterWidget != 0 {166+ if insertAfterWidget != nil {
164 // Insert titleBar after the previous pane's container167 // Insert titleBar after the previous pane's container
165- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {168+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
166- webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget)169+ box.InsertChildAfter(newLeaf.titleBar, insertAfterWidget)
167 }170 }
168 // Insert container after the newly inserted titleBar171 // Insert container after the newly inserted titleBar
169- if stackNode.stackWrapper != 0 && newLeaf.container != 0 && newLeaf.titleBar != 0 {172+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil && newLeaf.titleBar != nil {
170- webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar)173+ box.InsertChildAfter(newLeaf.container, newLeaf.titleBar)
171 }174 }
172- log.Printf("[workspace] inserted widgets at position %d (after widget %#x)", insertIndex, insertAfterWidget)175+ log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget)
173 } else {176 } else {
174 // Insert at the beginning (insertIndex = 0)177 // Insert at the beginning (insertIndex = 0)
175- if stackNode.stackWrapper != 0 && newLeaf.container != 0 {178+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {
176- webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container)179+ box.Prepend(newLeaf.container)
177 }180 }
178- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {181+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
179- webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar)182+ box.Prepend(newLeaf.titleBar)
180 }183 }
181 log.Printf("[workspace] prepended widgets at position 0")184 log.Printf("[workspace] prepended widgets at position 0")
182 }185 }
@@ -189,27 +192,27 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
189 log.Printf("[workspace] converting pane to stacked: %p", target)192 log.Printf("[workspace] converting pane to stacked: %p", target)
190 193
191 // Create the wrapper container - this is what will be used by splitNode194 // Create the wrapper container - this is what will be used by splitNode
192- stackWrapperContainer := webkit.NewBox(webkit.OrientationVertical, 0)195+ stackWrapperContainer := gtk.NewBox(gtk.OrientationVertical, 0)
193- if stackWrapperContainer == 0 {196+ if stackWrapperContainer == nil {
194 return nil, 0, errors.New("failed to create stack wrapper container")197 return nil, 0, errors.New("failed to create stack wrapper container")
195 }198 }
196- webkit.WidgetSetHExpand(stackWrapperContainer, true)199+ stackWrapperContainer.SetHExpand(true)
197- webkit.WidgetSetVExpand(stackWrapperContainer, true)200+ stackWrapperContainer.SetVExpand(true)
198- webkit.WidgetRealizeInContainer(stackWrapperContainer) // Ensures size request is set like regular panes201+ stackWrapperContainer.Realize() // Ensures size request is set like regular panes
199 202
200 // Add CSS class to the stack container for proper styling203 // Add CSS class to the stack container for proper styling
201- webkit.WidgetAddCSSClass(stackWrapperContainer, stackContainerClass)204+ stackWrapperContainer.AddCSSClass(stackContainerClass)
202 205
203 // Create the internal box for the actual stacked widgets (titles + webviews)206 // Create the internal box for the actual stacked widgets (titles + webviews)
204- stackInternalBox := webkit.NewBox(webkit.OrientationVertical, 0)207+ stackInternalBox := gtk.NewBox(gtk.OrientationVertical, 0)
205- if stackInternalBox == 0 {208+ if stackInternalBox == nil {
206 return nil, 0, errors.New("failed to create stack internal box")209 return nil, 0, errors.New("failed to create stack internal box")
207 }210 }
208- webkit.WidgetSetHExpand(stackInternalBox, true)211+ stackInternalBox.SetHExpand(true)
209- webkit.WidgetSetVExpand(stackInternalBox, true)212+ stackInternalBox.SetVExpand(true)
210 213
211 // The internal box goes inside the wrapper214 // The internal box goes inside the wrapper
212- webkit.BoxAppend(stackWrapperContainer, stackInternalBox)215+ stackWrapperContainer.Append(stackInternalBox)
213 216
214 // Get the existing container and parent info217 // Get the existing container and parent info
215 existingContainer := target.container218 existingContainer := target.container
@@ -219,15 +222,19 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
219 titleBar := spm.createTitleBar(target)222 titleBar := spm.createTitleBar(target)
220 223
221 // Keep existing container visible during transition to prevent rendering glitch224 // Keep existing container visible during transition to prevent rendering glitch
222- webkit.WidgetSetVisible(titleBar, false)225+ if titleBar != nil {
226+ titleBar.SetVisible(false)
227+ }
223 228
224 // Detach existing container from its current parent first229 // Detach existing container from its current parent first
225 spm.detachFromParent(target, parent)230 spm.detachFromParent(target, parent)
226 231
227 // Build the complete stack structure with hidden widgets232 // Build the complete stack structure with hidden widgets
228- webkit.BoxAppend(stackInternalBox, titleBar)233+ if titleBar != nil {
229- if existingContainer != 0 {234+ stackInternalBox.Append(titleBar)
230- webkit.BoxAppend(stackInternalBox, existingContainer)235+ }
236+ if existingContainer != nil {
237+ stackInternalBox.Append(existingContainer)
231 }238 }
232 239
233 // Immediately reattach the stack wrapper to minimize visibility gap240 // Immediately reattach the stack wrapper to minimize visibility gap
@@ -275,24 +282,24 @@ func (spm *StackedPaneManager) convertToStackedContainer(target, newLeaf *paneNo
275 282
276 // Unparent the new container before adding it to the stack283 // Unparent the new container before adding it to the stack
277 // This is critical: even freshly created WebViews might have internal parent refs284 // This is critical: even freshly created WebViews might have internal parent refs
278- if newLeaf.container != 0 && webkit.WidgetIsValid(newLeaf.container) {285+ if newLeaf.container != nil {
279- parent := webkit.WidgetGetParent(newLeaf.container)286+ parentWidget := newLeaf.container.Parent()
280- if parent != 0 {287+ if parentWidget != nil {
281- log.Printf("[workspace] unparenting new pane container %#x from parent %#x before stack append", newLeaf.container, parent)288+ log.Printf("[workspace] unparenting new pane container %p from parent %p before stack append", newLeaf.container, parentWidget)
282- webkit.WidgetUnparent(newLeaf.container)289+ newLeaf.container.Unparent()
283 // Verify unparent succeeded290 // Verify unparent succeeded
284- if finalParent := webkit.WidgetGetParent(newLeaf.container); finalParent != 0 {291+ if finalParent := newLeaf.container.Parent(); finalParent != nil {
285- log.Printf("[workspace] WARNING: container %#x still has parent %#x after unparent", newLeaf.container, finalParent)292+ log.Printf("[workspace] WARNING: container %p still has parent %p after unparent", newLeaf.container, finalParent)
286 }293 }
287 }294 }
288 }295 }
289 296
290 // Add the new widgets to the internal stack box297 // Add the new widgets to the internal stack box
291- if stackNode.stackWrapper != 0 && newLeaf.titleBar != 0 {298+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.titleBar != nil {
292- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar)299+ box.Append(newLeaf.titleBar)
293 }300 }
294- if stackNode.stackWrapper != 0 && newLeaf.container != 0 {301+ if box, ok := stackNode.stackWrapper.(*gtk.Box); ok && newLeaf.container != nil {
295- webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container)302+ box.Append(newLeaf.container)
296 }303 }
297 304
298 return stackNode, insertIndex, nil305 return stackNode, insertIndex, nil
@@ -303,35 +310,35 @@ func (spm *StackedPaneManager) detachFromParent(target *paneNode, parent *paneNo
303 if parent == nil {310 if parent == nil {
304 // Target is the root - remove it from window311 // Target is the root - remove it from window
305 if spm.wm.window != nil {312 if spm.wm.window != nil {
306- spm.wm.window.SetChild(0)313+ spm.wm.window.SetChild(nil)
307 }314 }
308 // Unparent if it has a GTK parent315 // Unparent if it has a GTK parent
309- if target.container != 0 && webkit.WidgetIsValid(target.container) {316+ if target.container != nil {
310- if webkit.WidgetGetParent(target.container) != 0 {317+ if target.container.Parent() != nil {
311- webkit.WidgetUnparent(target.container)318+ target.container.Unparent()
312 }319 }
313 }320 }
314- } else if parent.container != 0 && webkit.WidgetIsValid(parent.container) {321+ } else if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
315 // Target has a parent paned - remove it (automatically unparents in GTK4)322 // Target has a parent paned - remove it (automatically unparents in GTK4)
316 if parent.left == target {323 if parent.left == target {
317- webkit.PanedSetStartChild(parent.container, 0)324+ paned.SetStartChild(nil)
318 } else if parent.right == target {325 } else if parent.right == target {
319- webkit.PanedSetEndChild(parent.container, 0)326+ paned.SetEndChild(nil)
320 }327 }
321 }328 }
322 }329 }
323 330
324 // reattachToParent attaches a container to the parent331 // reattachToParent attaches a container to the parent
325-func (spm *StackedPaneManager) reattachToParent(container uintptr, target *paneNode, parent *paneNode) {332+func (spm *StackedPaneManager) reattachToParent(container gtk.Widgetter, target *paneNode, parent *paneNode) {
326 if parent == nil {333 if parent == nil {
327 if spm.wm.window != nil {334 if spm.wm.window != nil {
328 spm.wm.window.SetChild(container)335 spm.wm.window.SetChild(container)
329 }336 }
330- } else if parent.container != 0 && webkit.WidgetIsValid(parent.container) {337+ } else if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
331 if parent.left == target {338 if parent.left == target {
332- webkit.PanedSetStartChild(parent.container, container)339+ paned.SetStartChild(container)
333 } else if parent.right == target {340 } else if parent.right == target {
334- webkit.PanedSetEndChild(parent.container, container)341+ paned.SetEndChild(container)
335 }342 }
336 }343 }
337 }344 }
@@ -406,20 +413,20 @@ func (spm *StackedPaneManager) UpdateStackVisibility(stackNode *paneNode) {
406 for i, pane := range stackNode.stackedPanes {413 for i, pane := range stackNode.stackedPanes {
407 if i == activeIndex {414 if i == activeIndex {
408 // Active pane: show container, NEVER show title bar415 // Active pane: show container, NEVER show title bar
409- if pane.container != 0 {416+ if pane.container != nil {
410- webkit.WidgetSetVisible(pane.container, true)417+ pane.container.SetVisible(true)
411 }418 }
412- if pane.titleBar != 0 {419+ if pane.titleBar != nil {
413- webkit.WidgetSetVisible(pane.titleBar, false) // ABSOLUTE RULE: never visible for active pane420+ pane.titleBar.SetVisible(false) // ABSOLUTE RULE: never visible for active pane
414 }421 }
415 log.Printf("[workspace] active pane %d: container=visible, titleBar=HIDDEN", i)422 log.Printf("[workspace] active pane %d: container=visible, titleBar=HIDDEN", i)
416 } else {423 } else {
417 // Inactive panes: hide container, show title bar424 // Inactive panes: hide container, show title bar
418- if pane.container != 0 {425+ if pane.container != nil {
419- webkit.WidgetSetVisible(pane.container, false)426+ pane.container.SetVisible(false)
420 }427 }
421- if pane.titleBar != 0 {428+ if pane.titleBar != nil {
422- webkit.WidgetSetVisible(pane.titleBar, true)429+ pane.titleBar.SetVisible(true)
423 430
424 // Refresh title bar to pick up any newly downloaded favicons431 // Refresh title bar to pick up any newly downloaded favicons
425 if pane.pane != nil && pane.pane.webView != nil {432 if pane.pane != nil && pane.pane.webView != nil {
@@ -513,7 +520,7 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool {
513 }520 }
514 521
515 // createTitleBar creates a title bar widget for a pane in a stack522 // createTitleBar creates a title bar widget for a pane in a stack
516-func (spm *StackedPaneManager) createTitleBar(pane *paneNode) uintptr {523+func (spm *StackedPaneManager) createTitleBar(pane *paneNode) gtk.Widgetter {
517 // Get the actual title and URL from the WebView524 // Get the actual title and URL from the WebView
518 var titleText, pageURL string525 var titleText, pageURL string
519 if pane.pane != nil && pane.pane.webView != nil {526 if pane.pane != nil && pane.pane.webView != nil {
@@ -527,15 +534,16 @@ func (spm *StackedPaneManager) createTitleBar(pane *paneNode) uintptr {
527 }534 }
528 535
529 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)536 titleBar := spm.createTitleBarWithTitle(titleText, pageURL)
530- if titleBar != 0 {537+ if titleBar != nil {
531 // Store the mapping from titleBar ID to pane538 // Store the mapping from titleBar ID to pane
532 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)539 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)
533- spm.titleBarToPane[uintptr(titleBarID)] = pane540+ spm.titleBarToPane[titleBarID] = pane
534 541
535 // Attach click handler542 // Attach click handler
536- webkit.WidgetAttachClickHandler(titleBar, func() {543+ // TODO: Implement click handler with gtk.GestureClick
537- spm.handleTitleBarClick(uintptr(titleBarID))544+ // webkit.WidgetAttachClickHandler(titleBar, func() {
538- })545+ // spm.handleTitleBarClick(titleBarID)
546+ // })
539 }547 }
540 548
541 return titleBar549 return titleBar
@@ -554,7 +562,7 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str
554 }562 }
555 563
556 // Check if this pane is in a stack and has a title bar564 // Check if this pane is in a stack and has a title bar
557- if node.parent != nil && node.parent.isStacked && node.titleBar != 0 {565+ if node.parent != nil && node.parent.isStacked && node.titleBar != nil {
558 // CRITICAL: Only update title bar for INACTIVE panes566 // CRITICAL: Only update title bar for INACTIVE panes
559 // Active panes should never show their title bar567 // Active panes should never show their title bar
560 stackNode := node.parent568 stackNode := node.parent
@@ -582,7 +590,7 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str
582 590
583 // updateTitleBarLabel updates the label widget within a title bar by recreating it591 // updateTitleBarLabel updates the label widget within a title bar by recreating it
584 func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string) {592 func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string) {
585- if node == nil || node.titleBar == 0 || node.parent == nil || !node.parent.isStacked {593+ if node == nil || node.titleBar == nil || node.parent == nil || !node.parent.isStacked {
586 return594 return
587 }595 }
588 596
@@ -594,22 +602,23 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
594 602
595 // Create new title bar with updated title and favicon603 // Create new title bar with updated title and favicon
596 newTitleBar := spm.createTitleBarWithTitle(title, pageURL)604 newTitleBar := spm.createTitleBarWithTitle(title, pageURL)
597- if newTitleBar == 0 {605+ if newTitleBar == nil {
598 log.Printf("[workspace] failed to create new title bar")606 log.Printf("[workspace] failed to create new title bar")
599 return607 return
600 }608 }
601 609
602 // Store the mapping from titleBar ID to pane and attach click handler610 // Store the mapping from titleBar ID to pane and attach click handler
603 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)611 titleBarID := atomic.AddUint64(&spm.nextTitleBarID, 1)
604- spm.titleBarToPane[uintptr(titleBarID)] = node612+ spm.titleBarToPane[titleBarID] = node
605 613
606 // Attach click handler614 // Attach click handler
607- webkit.WidgetAttachClickHandler(newTitleBar, func() {615+ // TODO: Implement with gtk.GestureClick
608- spm.handleTitleBarClick(uintptr(titleBarID))616+ // webkit.WidgetAttachClickHandler(newTitleBar, func() {
609- })617+ // spm.handleTitleBarClick(titleBarID)
618+ // })
610 619
611 // Replace the old title bar in the stack620 // Replace the old title bar in the stack
612- if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 {621+ if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != nil {
613 // Find the correct insertion position based on the pane's index in the stack622 // Find the correct insertion position based on the pane's index in the stack
614 paneIndex := -1623 paneIndex := -1
615 for i, stackedPane := range node.parent.stackedPanes {624 for i, stackedPane := range node.parent.stackedPanes {
@@ -620,24 +629,24 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
620 }629 }
621 630
622 // Remove old title bar631 // Remove old title bar
623- if node.titleBar != 0 && webkit.WidgetIsValid(node.titleBar) {632+ if node.titleBar != nil {
624- webkit.WidgetUnparent(node.titleBar)633+ node.titleBar.Unparent()
625 }634 }
626 635
627 // Update the node's title bar reference636 // Update the node's title bar reference
628 spm.wm.setTitleBar(node, newTitleBar)637 spm.wm.setTitleBar(node, newTitleBar)
629 638
630 // Insert the new title bar at the correct position639 // Insert the new title bar at the correct position
631- if paneIndex == 0 {640+ if box, ok := node.parent.stackWrapper.(*gtk.Box); ok {
632- // First pane - insert at the beginning641+ if paneIndex == 0 {
633- if node.parent.stackWrapper != 0 {642+ // First pane - insert at the beginning
634- webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar)643+ box.Prepend(newTitleBar)
635- }644+ } else {
636- } else {645+ // Insert after the previous pane's container
637- // Insert after the previous pane's container646+ prevPane := node.parent.stackedPanes[paneIndex-1]
638- prevPane := node.parent.stackedPanes[paneIndex-1]647+ if prevPane.container != nil {
639- if node.parent.stackWrapper != 0 && prevPane.container != 0 {648+ box.InsertChildAfter(newTitleBar, prevPane.container)
640- webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container)649+ }
641 }650 }
642 }651 }
643 652
@@ -646,18 +655,18 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string)
646 }655 }
647 656
648 // createTitleBarWithTitle creates a title bar with a specific title and optional favicon657 // createTitleBarWithTitle creates a title bar with a specific title and optional favicon
649-func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL string) uintptr {658+func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL string) gtk.Widgetter {
650- titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8)659+ titleBox := gtk.NewBox(gtk.OrientationHorizontal, 8)
651- if titleBox == 0 {660+ if titleBox == nil {
652- return 0661+ return nil
653 }662 }
654 663
655- webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title")664+ titleBox.AddCSSClass("stacked-pane-title")
656- webkit.WidgetSetHExpand(titleBox, true)665+ titleBox.SetHExpand(true)
657- webkit.WidgetSetVExpand(titleBox, false)666+ titleBox.SetVExpand(false)
658 667
659 // Add favicon placeholder for debugging (always visible)668 // Add favicon placeholder for debugging (always visible)
660- var faviconImg uintptr669+ var faviconImg gtk.Widgetter
661 var debugReason string670 var debugReason string
662 671
663 if pageURL == "" {672 if pageURL == "" {
@@ -691,10 +700,12 @@ func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL str
691 faviconPath := faviconCache.GetCachedPath(faviconURL)700 faviconPath := faviconCache.GetCachedPath(faviconURL)
692 if faviconPath != "" {701 if faviconPath != "" {
693 log.Printf("[favicon] Loading cached favicon from: %s", faviconPath)702 log.Printf("[favicon] Loading cached favicon from: %s", faviconPath)
694- faviconImg = webkit.ImageNewFromFile(faviconPath)703+ faviconImg = gtk.NewImageFromFile(faviconPath)
695- if faviconImg != 0 {704+ if faviconImg != nil {
696- webkit.ImageSetPixelSize(faviconImg, 16)705+ if img, ok := faviconImg.(*gtk.Image); ok {
697- webkit.WidgetAddCSSClass(faviconImg, "stacked-pane-favicon")706+ img.SetPixelSize(16)
707+ }
708+ faviconImg.AddCSSClass("stacked-pane-favicon")
698 log.Printf("[favicon] Successfully created favicon image widget")709 log.Printf("[favicon] Successfully created favicon image widget")
699 } else {710 } else {
700 debugReason = "image widget creation failed"711 debugReason = "image widget creation failed"
@@ -711,32 +722,32 @@ func (spm *StackedPaneManager) createTitleBarWithTitle(title string, pageURL str
711 }722 }
712 723
713 // Add favicon or placeholder724 // Add favicon or placeholder
714- if faviconImg != 0 {725+ if faviconImg != nil {
715- webkit.BoxAppend(titleBox, faviconImg)726+ titleBox.Append(faviconImg)
716- webkit.WidgetShow(faviconImg)727+ faviconImg.Show()
717 } else {728 } else {
718 // DEBUG: Add visible placeholder label to show code is running729 // DEBUG: Add visible placeholder label to show code is running
719- placeholderLabel := webkit.NewLabel(faviconPlaceholder)730+ placeholderLabel := gtk.NewLabel(faviconPlaceholder)
720- if placeholderLabel != 0 {731+ if placeholderLabel != nil {
721- webkit.WidgetAddCSSClass(placeholderLabel, "stacked-pane-favicon-placeholder")732+ placeholderLabel.AddCSSClass("stacked-pane-favicon-placeholder")
722- webkit.BoxAppend(titleBox, placeholderLabel)733+ titleBox.Append(placeholderLabel)
723- webkit.WidgetShow(placeholderLabel)734+ placeholderLabel.Show()
724 log.Printf("[favicon] Using placeholder for %s (reason: %s)", pageURL, debugReason)735 log.Printf("[favicon] Using placeholder for %s (reason: %s)", pageURL, debugReason)
725 }736 }
726 }737 }
727 738
728 // Create title label with the specified title739 // Create title label with the specified title
729- titleLabel := webkit.NewLabel(title)740+ titleLabel := gtk.NewLabel(title)
730- if titleLabel == 0 {741+ if titleLabel == nil {
731 return titleBox742 return titleBox
732 }743 }
733 744
734- webkit.WidgetAddCSSClass(titleLabel, "stacked-pane-title-text")745+ titleLabel.AddCSSClass("stacked-pane-title-text")
735- webkit.LabelSetEllipsize(titleLabel, webkit.EllipsizeEnd)746+ titleLabel.SetEllipsize(2) // pango.EllipsizeEnd = 2
736 747
737- webkit.BoxAppend(titleBox, titleLabel)748+ titleBox.Append(titleLabel)
738- webkit.WidgetShow(titleBox)749+ titleBox.Show()
739- webkit.WidgetShow(titleLabel)750+ titleLabel.Show()
740 751
741 return titleBox752 return titleBox
742 }753 }
@@ -775,15 +786,15 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
775 786
776 // Remove the pane widgets from the stack container before mutating the tree.787 // Remove the pane widgets from the stack container before mutating the tree.
777 stackBox := stackNode.stackWrapper788 stackBox := stackNode.stackWrapper
778- if stackBox == 0 {789+ if stackBox == nil {
779 stackBox = stackNode.container790 stackBox = stackNode.container
780 }791 }
781- if stackBox != 0 {792+ if box, ok := stackBox.(*gtk.Box); ok && box != nil {
782- if node.titleBar != 0 {793+ if node.titleBar != nil {
783- webkit.BoxRemove(stackBox, node.titleBar)794+ box.Remove(node.titleBar)
784 }795 }
785- if node.container != 0 {796+ if node.container != nil {
786- webkit.BoxRemove(stackBox, node.container)797+ box.Remove(node.container)
787 }798 }
788 }799 }
789 800
@@ -796,30 +807,30 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
796 parent := stackNode.parent807 parent := stackNode.parent
797 if parent == nil {808 if parent == nil {
798 // Stack was the root of the workspace — closing this pane exits the app.809 // Stack was the root of the workspace — closing this pane exits the app.
799- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {810+ if stackNode.container != nil {
800- if webkit.WidgetGetParent(stackNode.container) != 0 {811+ if stackNode.container.Parent() != nil {
801- webkit.WidgetUnparent(stackNode.container)812+ stackNode.container.Unparent()
802 }813 }
803 }814 }
804 if spm.wm.window != nil {815 if spm.wm.window != nil {
805- spm.wm.window.SetChild(0)816+ spm.wm.window.SetChild(nil)
806 }817 }
807 _, err := spm.wm.cleanupAndExit(node)818 _, err := spm.wm.cleanupAndExit(node)
808 return err819 return err
809 }820 }
810 821
811 // Detach the empty stack container from its GtkPaned parent.822 // Detach the empty stack container from its GtkPaned parent.
812- if parent.container != 0 && webkit.WidgetIsValid(parent.container) {823+ if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil {
813 if parent.left == stackNode {824 if parent.left == stackNode {
814- webkit.PanedSetStartChild(parent.container, 0)825+ paned.SetStartChild(nil)
815 } else if parent.right == stackNode {826 } else if parent.right == stackNode {
816- webkit.PanedSetEndChild(parent.container, 0)827+ paned.SetEndChild(nil)
817 }828 }
818- webkit.WidgetQueueAllocate(parent.container)829+ paned.QueueAllocate()
819 }830 }
820- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {831+ if stackNode.container != nil {
821- if webkit.WidgetGetParent(stackNode.container) != 0 {832+ if stackNode.container.Parent() != nil {
822- webkit.WidgetUnparent(stackNode.container)833+ stackNode.container.Unparent()
823 }834 }
824 }835 }
825 836
@@ -840,8 +851,8 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
840 }851 }
841 852
842 stackNode.parent = nil853 stackNode.parent = nil
843- stackNode.container = 0854+ stackNode.container = nil
844- stackNode.stackWrapper = 0855+ stackNode.stackWrapper = nil
845 stackNode.stackedPanes = nil856 stackNode.stackedPanes = nil
846 857
847 return nil858 return nil
@@ -852,25 +863,25 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
852 parent := stackNode.parent863 parent := stackNode.parent
853 lastPaneContainer := lastPane.container864 lastPaneContainer := lastPane.container
854 865
855- if lastPane.titleBar != 0 && stackBox != 0 {866+ if box, ok := stackBox.(*gtk.Box); ok && box != nil && lastPane.titleBar != nil {
856- webkit.BoxRemove(stackBox, lastPane.titleBar)867+ box.Remove(lastPane.titleBar)
857 }868 }
858- if stackBox != 0 && lastPaneContainer != 0 {869+ if box, ok := stackBox.(*gtk.Box); ok && box != nil && lastPaneContainer != nil {
859- webkit.BoxRemove(stackBox, lastPaneContainer)870+ box.Remove(lastPaneContainer)
860 }871 }
861- if stackNode.container != 0 && webkit.WidgetIsValid(stackNode.container) {872+ if stackNode.container != nil {
862- if webkit.WidgetGetParent(stackNode.container) != 0 {873+ if stackNode.container.Parent() != nil {
863- webkit.WidgetUnparent(stackNode.container)874+ stackNode.container.Unparent()
864 }875 }
865 }876 }
866 877
867 if parent == nil {878 if parent == nil {
868 spm.wm.root = lastPane879 spm.wm.root = lastPane
869 lastPane.parent = nil880 lastPane.parent = nil
870- if spm.wm.window != nil && lastPaneContainer != 0 {881+ if spm.wm.window != nil && lastPaneContainer != nil {
871 spm.wm.window.SetChild(lastPaneContainer)882 spm.wm.window.SetChild(lastPaneContainer)
872- webkit.WidgetQueueAllocate(lastPaneContainer)883+ lastPaneContainer.QueueAllocate()
873- webkit.WidgetShow(lastPaneContainer)884+ lastPaneContainer.Show()
874 }885 }
875 } else {886 } else {
876 if parent.left == stackNode {887 if parent.left == stackNode {
@@ -879,36 +890,36 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
879 parent.right = lastPane890 parent.right = lastPane
880 }891 }
881 lastPane.parent = parent892 lastPane.parent = parent
882- if parent.container != 0 && webkit.WidgetIsValid(parent.container) && lastPaneContainer != 0 {893+ if paned, ok := parent.container.(*gtk.Paned); ok && paned != nil && lastPaneContainer != nil {
883 if parent.left == lastPane {894 if parent.left == lastPane {
884- webkit.PanedSetStartChild(parent.container, lastPaneContainer)895+ paned.SetStartChild(lastPaneContainer)
885 } else {896 } else {
886- webkit.PanedSetEndChild(parent.container, lastPaneContainer)897+ paned.SetEndChild(lastPaneContainer)
887 }898 }
888- webkit.WidgetQueueAllocate(parent.container)899+ paned.QueueAllocate()
889 }900 }
890- if lastPaneContainer != 0 {901+ if lastPaneContainer != nil {
891- webkit.WidgetSetVisible(lastPaneContainer, true)902+ lastPaneContainer.SetVisible(true)
892 }903 }
893 }904 }
894 905
895 lastPane.isStacked = false906 lastPane.isStacked = false
896 lastPane.stackedPanes = nil907 lastPane.stackedPanes = nil
897- lastPane.titleBar = 0908+ lastPane.titleBar = nil
898 spm.wm.viewToNode[lastPane.pane.webView] = lastPane909 spm.wm.viewToNode[lastPane.pane.webView] = lastPane
899 910
900 generation := spm.wm.nextCleanupGeneration()911 generation := spm.wm.nextCleanupGeneration()
901 spm.wm.cleanupPane(node, generation)912 spm.wm.cleanupPane(node, generation)
902 913
903 spm.wm.SetActivePane(lastPane, SourceClose)914 spm.wm.SetActivePane(lastPane, SourceClose)
904- if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 {915+ if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != nil {
905- webkit.WidgetShow(lastPaneWidget)916+ lastPaneWidget.Show()
906- webkit.WidgetGrabFocus(lastPaneWidget)917+ lastPaneWidget.GrabFocus()
907 }918 }
908 919
909 stackNode.parent = nil920 stackNode.parent = nil
910- stackNode.container = 0921+ stackNode.container = nil
911- stackNode.stackWrapper = 0922+ stackNode.stackWrapper = nil
912 stackNode.stackedPanes = nil923 stackNode.stackedPanes = nil
913 924
914 log.Printf("[workspace] converted stack back to regular pane")925 log.Printf("[workspace] converted stack back to regular pane")
@@ -939,7 +950,7 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error {
939 }950 }
940 951
941 // handleTitleBarClick handles clicks on title bars to switch the active pane952 // handleTitleBarClick handles clicks on title bars to switch the active pane
942-func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uintptr) {953+func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uint64) {
943 pane, exists := spm.titleBarToPane[titleBarID]954 pane, exists := spm.titleBarToPane[titleBarID]
944 if !exists || pane == nil || pane.parent == nil || !pane.parent.isStacked {955 if !exists || pane == nil || pane.parent == nil || !pane.parent.isStacked {
945 return956 return
@@ -957,7 +968,7 @@ func (spm *StackedPaneManager) handleTitleBarClick(titleBarID uintptr) {
957 968
958 // Update title bar for the pane transitioning from ACTIVE to INACTIVE969 // Update title bar for the pane transitioning from ACTIVE to INACTIVE
959 currentActivePane := stackNode.stackedPanes[stackNode.activeStackIndex]970 currentActivePane := stackNode.stackedPanes[stackNode.activeStackIndex]
960- if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 {971+ if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != nil {
961 currentTitle := currentActivePane.pane.webView.GetTitle()972 currentTitle := currentActivePane.pane.webView.GetTitle()
962 if currentTitle != "" {973 if currentTitle != "" {
963 spm.updateTitleBarLabel(currentActivePane, currentTitle)974 spm.updateTitleBarLabel(currentActivePane, currentTitle)

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