fix: resolve stacked pane title updates and Zellij-style layout positioning
This commit fixes two critical issues with stacked pane title bar behavior: 1. **Title Update Timing**: Previously, panes transitioning from active to inactive would not show their current page titles in title bars. Fixed by updating title bars at the correct moments: - During stack navigation (NavigateStack) - During new pane creation (finalizeStackCreation) 2. **GTK Widget Positioning**: Fixed incorrect Zellij-style layout where title bars appeared in wrong positions. Corrected GTK widget insertion order: - addPaneToExistingStack(): Now uses BoxInsertChildAfter for correct positioning - updateTitleBarLabel(): Now maintains proper widget order when replacing title bars The layout now properly follows Zellij-style behavior: - 2 panes: Title bar appears above OR below active pane depending on which is focused - 3+ panes: Title bars appear above AND below active pane in correct order User-visible changes: - Collapsed panes now show current page titles immediately when switching - Title bars appear in correct positions (above/below active pane) - Proper visual layout maintained during pane creation and navigation Technical changes: - Enhanced NavigateStack() with title update before visibility changes - Enhanced finalizeStackCreation() with title update for previously active pane - Fixed addPaneToExistingStack() widget insertion using BoxInsertChildAfter() - Fixed updateTitleBarLabel() positioning logic for title bar replacement
- commit
- 2027e707392aa53ef53e95e9c35db8ea916459ce
- author
- bamen · · 2025-09-25 20:25 UTC
- committer
- bamen · · 2025-09-25 19:04 UTC
- parent
- 0a17c0a
- signature
- unsigned
2 files · +67 -6
M CHANGELOG.md +1 -0
| @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. | |||
| 21 | 21 | - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage | |
| 22 | 22 | - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings | |
| 23 | 23 | - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion | |
| 24 | + | - **Stacked pane title updates**: Fixed collapsed panes not showing current page titles when switching between stacked panes. Now follows Zellij-style layout where hidden panes display their up-to-date page titles in title bars with correct positioning above and below the active pane | |
| 24 | 25 | ||
| 25 | 26 | ## [0.10.0] - 2025-09-24 | |
| 26 | 27 | ||
| @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. | |||||
| 21 | - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage | 21 | - **WebView title updates**: Enhanced webview title change handling to properly update stacked pane title bars in addition to database storage | ||
| 22 | - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings | 22 | - **GTK4 widget lifecycle**: Improved widget parenting/unparenting operations with proper GTK4 validation and automatic cleanup to prevent critical warnings | ||
| 23 | - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion | 23 | - **Stacked pane close behavior**: Fixed remaining pane becoming non-interactable (gray background) when closing stacked panes via Ctrl+W or Ctrl+P+X. Added proper widget visibility restoration and focus management during stack-to-regular conversion | ||
| 24 | + | - **Stacked pane title updates**: Fixed collapsed panes not showing current page titles when switching between stacked panes. Now follows Zellij-style layout where hidden panes display their up-to-date page titles in title bars with correct positioning above and below the active pane | |||
| 24 | 25 | ||||
| 25 | ## [0.10.0] - 2025-09-24 | 26 | ## [0.10.0] - 2025-09-24 | ||
| 26 | 27 | ||||
M internal/app/browser/stacked_panes.go +66 -6
| @@ -134,9 +134,29 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode) | |||
| 134 | 134 | webkit.WidgetUnparent(newLeaf.container) | |
| 135 | 135 | } | |
| 136 | 136 | ||
| 137 | - | // Add the new widgets to the internal stack box (not the wrapper) | |
| 138 | - | webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar) | |
| 139 | - | webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container) | |
| 137 | + | // Insert widgets at correct position for Zellij-style layout | |
| 138 | + | // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2 | |
| 139 | + | ||
| 140 | + | // Find the widget to insert after (based on insertIndex) | |
| 141 | + | var insertAfterWidget uintptr = 0 | |
| 142 | + | if insertIndex > 0 { | |
| 143 | + | // Insert after the previous pane's container | |
| 144 | + | prevPane := stackNode.stackedPanes[insertIndex-1] | |
| 145 | + | insertAfterWidget = prevPane.container | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | if insertAfterWidget != 0 { | |
| 149 | + | // Insert titleBar after the previous pane's container | |
| 150 | + | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget) | |
| 151 | + | // Insert container after the newly inserted titleBar | |
| 152 | + | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar) | |
| 153 | + | log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget) | |
| 154 | + | } else { | |
| 155 | + | // Insert at the beginning (insertIndex = 0) | |
| 156 | + | webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container) | |
| 157 | + | webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar) | |
| 158 | + | log.Printf("[workspace] prepended widgets at position 0") | |
| 159 | + | } | |
| 140 | 160 | ||
| 141 | 161 | return stackNode, insertIndex, nil | |
| 142 | 162 | } | |
| @@ -286,6 +306,19 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod | |||
| 286 | 306 | // Mark stack operation timestamp to prevent focus conflicts | |
| 287 | 307 | spm.wm.lastStackOperation = time.Now() | |
| 288 | 308 | ||
| 309 | + | // This ensures Zellij-style layout where the previously active pane shows its current title | |
| 310 | + | currentActiveIndex := stackNode.activeStackIndex | |
| 311 | + | if currentActiveIndex >= 0 && currentActiveIndex < len(stackNode.stackedPanes) && currentActiveIndex != insertIndex { | |
| 312 | + | currentlyActivePane := stackNode.stackedPanes[currentActiveIndex] | |
| 313 | + | if currentlyActivePane.pane != nil && currentlyActivePane.pane.webView != nil && currentlyActivePane.titleBar != 0 { | |
| 314 | + | currentTitle := currentlyActivePane.pane.webView.GetTitle() | |
| 315 | + | if currentTitle != "" { | |
| 316 | + | spm.updateTitleBarLabel(currentlyActivePane, currentTitle) | |
| 317 | + | log.Printf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle) | |
| 318 | + | } | |
| 319 | + | } | |
| 320 | + | } | |
| 321 | + | ||
| 289 | 322 | // Immediately switch to the new pane (don't use deprecated IdleAdd) | |
| 290 | 323 | stackNode.activeStackIndex = insertIndex | |
| 291 | 324 | spm.UpdateStackVisibility(stackNode) | |
| @@ -400,6 +433,17 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool { | |||
| 400 | 433 | return false // No change | |
| 401 | 434 | } | |
| 402 | 435 | ||
| 436 | + | // Update title bar for the pane transitioning from ACTIVE to INACTIVE | |
| 437 | + | // This ensures the collapsed pane shows its current page title (Zellij-style layout) | |
| 438 | + | currentActivePane := stackNode.stackedPanes[currentIndex] | |
| 439 | + | if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 { | |
| 440 | + | currentTitle := currentActivePane.pane.webView.GetTitle() | |
| 441 | + | if currentTitle != "" { | |
| 442 | + | spm.updateTitleBarLabel(currentActivePane, currentTitle) | |
| 443 | + | log.Printf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle) | |
| 444 | + | } | |
| 445 | + | } | |
| 446 | + | ||
| 403 | 447 | // Update active stack index and visibility | |
| 404 | 448 | stackNode.activeStackIndex = newIndex | |
| 405 | 449 | spm.UpdateStackVisibility(stackNode) | |
| @@ -515,16 +559,32 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string) | |||
| 515 | 559 | ||
| 516 | 560 | // Replace the old title bar in the stack | |
| 517 | 561 | if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 { | |
| 562 | + | // Find the correct insertion position based on the pane's index in the stack | |
| 563 | + | paneIndex := -1 | |
| 564 | + | for i, stackedPane := range node.parent.stackedPanes { | |
| 565 | + | if stackedPane == node { | |
| 566 | + | paneIndex = i | |
| 567 | + | break | |
| 568 | + | } | |
| 569 | + | } | |
| 570 | + | ||
| 518 | 571 | // Remove old title bar | |
| 519 | 572 | webkit.WidgetUnparent(node.titleBar) | |
| 520 | 573 | ||
| 521 | 574 | // Update the node's title bar reference | |
| 522 | 575 | node.titleBar = newTitleBar | |
| 523 | 576 | ||
| 524 | - | // Add new title bar to the stack (need to find correct position) | |
| 525 | - | webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar) | |
| 577 | + | // Insert the new title bar at the correct position | |
| 578 | + | if paneIndex == 0 { | |
| 579 | + | // First pane - insert at the beginning | |
| 580 | + | webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar) | |
| 581 | + | } else { | |
| 582 | + | // Insert after the previous pane's container | |
| 583 | + | prevPane := node.parent.stackedPanes[paneIndex-1] | |
| 584 | + | webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container) | |
| 585 | + | } | |
| 526 | 586 | ||
| 527 | - | log.Printf("[workspace] replaced title bar for pane: %p", node) | |
| 587 | + | log.Printf("[workspace] replaced title bar for pane %d: %p", paneIndex, node) | |
| 528 | 588 | } | |
| 529 | 589 | } | |
| 530 | 590 | ||
| @@ -134,9 +134,29 @@ func (spm *StackedPaneManager) addPaneToExistingStack(target, newLeaf *paneNode) | |||||
| 134 | webkit.WidgetUnparent(newLeaf.container) | 134 | webkit.WidgetUnparent(newLeaf.container) | ||
| 135 | } | 135 | } | ||
| 136 | 136 | ||||
| 137 | - | // Add the new widgets to the internal stack box (not the wrapper) | 137 | + | // Insert widgets at correct position for Zellij-style layout |
| 138 | - | webkit.BoxAppend(stackNode.stackWrapper, newLeaf.titleBar) | 138 | + | // Each pane has 2 widgets (titleBar, container), so position = insertIndex * 2 |
| 139 | - | webkit.BoxAppend(stackNode.stackWrapper, newLeaf.container) | 139 | + | |
| 140 | + | // Find the widget to insert after (based on insertIndex) | |||
| 141 | + | var insertAfterWidget uintptr = 0 | |||
| 142 | + | if insertIndex > 0 { | |||
| 143 | + | // Insert after the previous pane's container | |||
| 144 | + | prevPane := stackNode.stackedPanes[insertIndex-1] | |||
| 145 | + | insertAfterWidget = prevPane.container | |||
| 146 | + | } | |||
| 147 | + | ||||
| 148 | + | if insertAfterWidget != 0 { | |||
| 149 | + | // Insert titleBar after the previous pane's container | |||
| 150 | + | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.titleBar, insertAfterWidget) | |||
| 151 | + | // Insert container after the newly inserted titleBar | |||
| 152 | + | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newLeaf.container, newLeaf.titleBar) | |||
| 153 | + | log.Printf("[workspace] inserted widgets at position %d (after widget %p)", insertIndex, insertAfterWidget) | |||
| 154 | + | } else { | |||
| 155 | + | // Insert at the beginning (insertIndex = 0) | |||
| 156 | + | webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.container) | |||
| 157 | + | webkit.BoxPrepend(stackNode.stackWrapper, newLeaf.titleBar) | |||
| 158 | + | log.Printf("[workspace] prepended widgets at position 0") | |||
| 159 | + | } | |||
| 140 | 160 | ||||
| 141 | return stackNode, insertIndex, nil | 161 | return stackNode, insertIndex, nil | ||
| 142 | } | 162 | } | ||
| @@ -286,6 +306,19 @@ func (spm *StackedPaneManager) finalizeStackCreation(stackNode, newLeaf *paneNod | |||||
| 286 | // Mark stack operation timestamp to prevent focus conflicts | 306 | // Mark stack operation timestamp to prevent focus conflicts | ||
| 287 | spm.wm.lastStackOperation = time.Now() | 307 | spm.wm.lastStackOperation = time.Now() | ||
| 288 | 308 | ||||
| 309 | + | // This ensures Zellij-style layout where the previously active pane shows its current title | |||
| 310 | + | currentActiveIndex := stackNode.activeStackIndex | |||
| 311 | + | if currentActiveIndex >= 0 && currentActiveIndex < len(stackNode.stackedPanes) && currentActiveIndex != insertIndex { | |||
| 312 | + | currentlyActivePane := stackNode.stackedPanes[currentActiveIndex] | |||
| 313 | + | if currentlyActivePane.pane != nil && currentlyActivePane.pane.webView != nil && currentlyActivePane.titleBar != 0 { | |||
| 314 | + | currentTitle := currentlyActivePane.pane.webView.GetTitle() | |||
| 315 | + | if currentTitle != "" { | |||
| 316 | + | spm.updateTitleBarLabel(currentlyActivePane, currentTitle) | |||
| 317 | + | log.Printf("[workspace] updated title bar for pane becoming inactive during stack creation: %s", currentTitle) | |||
| 318 | + | } | |||
| 319 | + | } | |||
| 320 | + | } | |||
| 321 | + | ||||
| 289 | // Immediately switch to the new pane (don't use deprecated IdleAdd) | 322 | // Immediately switch to the new pane (don't use deprecated IdleAdd) | ||
| 290 | stackNode.activeStackIndex = insertIndex | 323 | stackNode.activeStackIndex = insertIndex | ||
| 291 | spm.UpdateStackVisibility(stackNode) | 324 | spm.UpdateStackVisibility(stackNode) | ||
| @@ -400,6 +433,17 @@ func (spm *StackedPaneManager) NavigateStack(direction string) bool { | |||||
| 400 | return false // No change | 433 | return false // No change | ||
| 401 | } | 434 | } | ||
| 402 | 435 | ||||
| 436 | + | // Update title bar for the pane transitioning from ACTIVE to INACTIVE | |||
| 437 | + | // This ensures the collapsed pane shows its current page title (Zellij-style layout) | |||
| 438 | + | currentActivePane := stackNode.stackedPanes[currentIndex] | |||
| 439 | + | if currentActivePane.pane != nil && currentActivePane.pane.webView != nil && currentActivePane.titleBar != 0 { | |||
| 440 | + | currentTitle := currentActivePane.pane.webView.GetTitle() | |||
| 441 | + | if currentTitle != "" { | |||
| 442 | + | spm.updateTitleBarLabel(currentActivePane, currentTitle) | |||
| 443 | + | log.Printf("[workspace] updated title bar for pane transitioning to INACTIVE: %s", currentTitle) | |||
| 444 | + | } | |||
| 445 | + | } | |||
| 446 | + | ||||
| 403 | // Update active stack index and visibility | 447 | // Update active stack index and visibility | ||
| 404 | stackNode.activeStackIndex = newIndex | 448 | stackNode.activeStackIndex = newIndex | ||
| 405 | spm.UpdateStackVisibility(stackNode) | 449 | spm.UpdateStackVisibility(stackNode) | ||
| @@ -515,16 +559,32 @@ func (spm *StackedPaneManager) updateTitleBarLabel(node *paneNode, title string) | |||||
| 515 | 559 | ||||
| 516 | // Replace the old title bar in the stack | 560 | // Replace the old title bar in the stack | ||
| 517 | if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 { | 561 | if node.parent != nil && node.parent.isStacked && node.parent.stackWrapper != 0 { | ||
| 562 | + | // Find the correct insertion position based on the pane's index in the stack | |||
| 563 | + | paneIndex := -1 | |||
| 564 | + | for i, stackedPane := range node.parent.stackedPanes { | |||
| 565 | + | if stackedPane == node { | |||
| 566 | + | paneIndex = i | |||
| 567 | + | break | |||
| 568 | + | } | |||
| 569 | + | } | |||
| 570 | + | ||||
| 518 | // Remove old title bar | 571 | // Remove old title bar | ||
| 519 | webkit.WidgetUnparent(node.titleBar) | 572 | webkit.WidgetUnparent(node.titleBar) | ||
| 520 | 573 | ||||
| 521 | // Update the node's title bar reference | 574 | // Update the node's title bar reference | ||
| 522 | node.titleBar = newTitleBar | 575 | node.titleBar = newTitleBar | ||
| 523 | 576 | ||||
| 524 | - | // Add new title bar to the stack (need to find correct position) | 577 | + | // Insert the new title bar at the correct position |
| 525 | - | webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar) | 578 | + | if paneIndex == 0 { |
| 579 | + | // First pane - insert at the beginning | |||
| 580 | + | webkit.BoxPrepend(node.parent.stackWrapper, newTitleBar) | |||
| 581 | + | } else { | |||
| 582 | + | // Insert after the previous pane's container | |||
| 583 | + | prevPane := node.parent.stackedPanes[paneIndex-1] | |||
| 584 | + | webkit.BoxInsertChildAfter(node.parent.stackWrapper, newTitleBar, prevPane.container) | |||
| 585 | + | } | |||
| 526 | 586 | ||||
| 527 | - | log.Printf("[workspace] replaced title bar for pane: %p", node) | 587 | + | log.Printf("[workspace] replaced title bar for pane %d: %p", paneIndex, node) |
| 528 | } | 588 | } | ||
| 529 | } | 589 | } | ||
| 530 | 590 | ||||