fix: resolve stacked pane interactivity after close operations
When closing a pane in a 2-pane stack via Ctrl+W or Ctrl+P+X, the remaining pane became non-interactable with a gray background after automatic conversion back to regular pane mode. Root cause: The complex widget reparenting during stack-to-regular conversion wasn't properly restoring widget visibility and focus state. Changes: - Add WebView.SetActive() calls in focus manager for proper active state sync - Add comprehensive focus detection logic for stack-to-regular conversion - Add explicit widget visibility restoration after reparenting - Add diagnostic logging for widget states during focus operations - Add proper focus management mirroring the working stack creation logic Both Ctrl+W (native) and Ctrl+P+X (JS workspace) shortcuts now properly maintain pane interactivity after closing stacked panes. Fixes focus management issues in stacked_panes.go:CloseStackedPane method.
- commit
- 0a17c0a05354891f2368c8de7505fa6e9c871756
- author
- bamen · · 2025-09-25 17:54 UTC
- committer
- bamen · · 2025-09-25 20:26 UTC
- parent
- d148d83
- signature
- unsigned
5 files · +99 -357
M CHANGELOG.md +2 -1
| @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. | |||
| 6 | 6 | ||
| 7 | 7 | ### Added | |
| 8 | 8 | - **Pin/favorite sites**: Pin websites from history or quick access to prioritize them in "Jump back in" section. Uses localStorage persistence with star icons for pin/unpin actions | |
| 9 | - | - **Zellij-style stacked panes**: Added Ctrl+P → 's' to stack panes instead of splitting, with Alt+Up/Down navigation between stacked panes. Features collapsed title bars showing "Page Title - domain" for inactive panes and full WebView interaction for the active pane. Includes GTK4 CSS styling for proper visual feedback | |
| 9 | + | - **Zellij-style stacked panes**: Added Ctrl+P → 's' to stack panes instead of splitting, with Alt+Up/Down navigation between stacked panes. Features collapsed page title bars showing for inactive panes and full WebView interaction for the active pane. Includes GTK4 CSS styling for proper visual feedback | |
| 10 | 10 | ||
| 11 | 11 | ### Changed | |
| 12 | 12 | - **History item layout**: Removed "• domain" from titles, now highlight domain within the full URL using same color as title text | |
| @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. | |||
| 20 | 20 | - **Stacked pane focus conflicts**: Fixed focus stealing issues in stacked panes by implementing focus-aware navigation that preserves active pane when spawning siblings | |
| 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 | + | - **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 | 24 | ||
| 24 | 25 | ## [0.10.0] - 2025-09-24 | |
| 25 | 26 | ||
| @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. | |||||
| 6 | 6 | ||||
| 7 | ### Added | 7 | ### Added | ||
| 8 | - **Pin/favorite sites**: Pin websites from history or quick access to prioritize them in "Jump back in" section. Uses localStorage persistence with star icons for pin/unpin actions | 8 | - **Pin/favorite sites**: Pin websites from history or quick access to prioritize them in "Jump back in" section. Uses localStorage persistence with star icons for pin/unpin actions | ||
| 9 | - | - **Zellij-style stacked panes**: Added Ctrl+P → 's' to stack panes instead of splitting, with Alt+Up/Down navigation between stacked panes. Features collapsed title bars showing "Page Title - domain" for inactive panes and full WebView interaction for the active pane. Includes GTK4 CSS styling for proper visual feedback | 9 | + | - **Zellij-style stacked panes**: Added Ctrl+P → 's' to stack panes instead of splitting, with Alt+Up/Down navigation between stacked panes. Features collapsed page title bars showing for inactive panes and full WebView interaction for the active pane. Includes GTK4 CSS styling for proper visual feedback |
| 10 | 10 | ||||
| 11 | ### Changed | 11 | ### Changed | ||
| 12 | - **History item layout**: Removed "• domain" from titles, now highlight domain within the full URL using same color as title text | 12 | - **History item layout**: Removed "• domain" from titles, now highlight domain within the full URL using same color as title text | ||
| @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. | |||||
| 20 | - **Stacked pane focus conflicts**: Fixed focus stealing issues in stacked panes by implementing focus-aware navigation that preserves active pane when spawning siblings | 20 | - **Stacked pane focus conflicts**: Fixed focus stealing issues in stacked panes by implementing focus-aware navigation that preserves active pane when spawning siblings | ||
| 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 | 24 | ||||
| 24 | ## [0.10.0] - 2025-09-24 | 25 | ## [0.10.0] - 2025-09-24 | ||
| 25 | 26 | ||||
M internal/app/browser/focus_manager.go +6 -0
| @@ -180,6 +180,9 @@ func (fm *FocusManager) updateStackVisibility(stackNode *paneNode) { | |||
| 180 | 180 | func (fm *FocusManager) notifyJavaScript(oldPane, newPane *paneNode) { | |
| 181 | 181 | // Notify old pane it lost focus | |
| 182 | 182 | if oldPane != nil && oldPane.pane != nil && oldPane.pane.webView != nil { | |
| 183 | + | // Update WebView internal active state | |
| 184 | + | oldPane.pane.webView.SetActive(false) | |
| 185 | + | ||
| 183 | 186 | oldDetail := map[string]any{ | |
| 184 | 187 | "active": false, | |
| 185 | 188 | "paneId": fm.getPaneID(oldPane), | |
| @@ -191,6 +194,9 @@ func (fm *FocusManager) notifyJavaScript(oldPane, newPane *paneNode) { | |||
| 191 | 194 | ||
| 192 | 195 | // Notify new pane it gained focus | |
| 193 | 196 | if newPane != nil && newPane.pane != nil && newPane.pane.webView != nil { | |
| 197 | + | // Update WebView internal active state | |
| 198 | + | newPane.pane.webView.SetActive(true) | |
| 199 | + | ||
| 194 | 200 | newDetail := map[string]any{ | |
| 195 | 201 | "active": true, | |
| 196 | 202 | "paneId": fm.getPaneID(newPane), | |
| @@ -180,6 +180,9 @@ func (fm *FocusManager) updateStackVisibility(stackNode *paneNode) { | |||||
| 180 | func (fm *FocusManager) notifyJavaScript(oldPane, newPane *paneNode) { | 180 | func (fm *FocusManager) notifyJavaScript(oldPane, newPane *paneNode) { | ||
| 181 | // Notify old pane it lost focus | 181 | // Notify old pane it lost focus | ||
| 182 | if oldPane != nil && oldPane.pane != nil && oldPane.pane.webView != nil { | 182 | if oldPane != nil && oldPane.pane != nil && oldPane.pane.webView != nil { | ||
| 183 | + | // Update WebView internal active state | |||
| 184 | + | oldPane.pane.webView.SetActive(false) | |||
| 185 | + | ||||
| 183 | oldDetail := map[string]any{ | 186 | oldDetail := map[string]any{ | ||
| 184 | "active": false, | 187 | "active": false, | ||
| 185 | "paneId": fm.getPaneID(oldPane), | 188 | "paneId": fm.getPaneID(oldPane), | ||
| @@ -191,6 +194,9 @@ func (fm *FocusManager) notifyJavaScript(oldPane, newPane *paneNode) { | |||||
| 191 | 194 | ||||
| 192 | // Notify new pane it gained focus | 195 | // Notify new pane it gained focus | ||
| 193 | if newPane != nil && newPane.pane != nil && newPane.pane.webView != nil { | 196 | if newPane != nil && newPane.pane != nil && newPane.pane.webView != nil { | ||
| 197 | + | // Update WebView internal active state | |||
| 198 | + | newPane.pane.webView.SetActive(true) | |||
| 199 | + | ||||
| 194 | newDetail := map[string]any{ | 200 | newDetail := map[string]any{ | ||
| 195 | "active": true, | 201 | "active": true, | ||
| 196 | "paneId": fm.getPaneID(newPane), | 202 | "paneId": fm.getPaneID(newPane), | ||
M internal/app/browser/stacked_panes.go +78 -6
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "fmt" | |
| 6 | 6 | "log" | |
| 7 | 7 | "time" | |
| 8 | + | "unsafe" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/dumber/pkg/webkit" | |
| 10 | 11 | ) | |
| @@ -468,9 +469,28 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str | |||
| 468 | 469 | ||
| 469 | 470 | // Check if this pane is in a stack and has a title bar | |
| 470 | 471 | if node.parent != nil && node.parent.isStacked && node.titleBar != 0 { | |
| 471 | - | // Update the title bar label with the new title | |
| 472 | - | spm.updateTitleBarLabel(node, title) | |
| 473 | - | log.Printf("[workspace] updated title bar for WebView %s: %s", webView.ID(), title) | |
| 472 | + | // CRITICAL: Only update title bar for INACTIVE panes | |
| 473 | + | // Active panes should never show their title bar | |
| 474 | + | stackNode := node.parent | |
| 475 | + | activeIndex := stackNode.activeStackIndex | |
| 476 | + | ||
| 477 | + | // Find this pane's index in the stack | |
| 478 | + | paneIndex := -1 | |
| 479 | + | for i, pane := range stackNode.stackedPanes { | |
| 480 | + | if pane == node { | |
| 481 | + | paneIndex = i | |
| 482 | + | break | |
| 483 | + | } | |
| 484 | + | } | |
| 485 | + | ||
| 486 | + | if paneIndex != -1 && paneIndex != activeIndex { | |
| 487 | + | // This is an INACTIVE pane - safe to update title bar | |
| 488 | + | spm.updateTitleBarLabel(node, title) | |
| 489 | + | log.Printf("[workspace] updated title bar for INACTIVE WebView %s: %s", webView.ID(), title) | |
| 490 | + | } else { | |
| 491 | + | // This is the ACTIVE pane - title bar should remain hidden | |
| 492 | + | log.Printf("[workspace] skipped title bar update for ACTIVE WebView %s: %s", webView.ID(), title) | |
| 493 | + | } | |
| 474 | 494 | } | |
| 475 | 495 | } | |
| 476 | 496 | ||
| @@ -632,13 +652,65 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error { | |||
| 632 | 652 | lastPane.stackedPanes = nil | |
| 633 | 653 | lastPane.titleBar = 0 | |
| 634 | 654 | ||
| 655 | + | // CRITICAL: Ensure the container is visible after reparenting | |
| 656 | + | // The container may have been hidden during stack operations | |
| 657 | + | webkit.WidgetSetVisible(lastPaneContainer, true) | |
| 658 | + | ||
| 635 | 659 | // Update the viewToNode mapping to point to the lastPane, not stackNode | |
| 636 | 660 | spm.wm.viewToNode[lastPane.pane.webView] = lastPane | |
| 637 | 661 | ||
| 638 | - | // Focus the remaining pane if it was the active one being closed | |
| 639 | - | if spm.wm.currentlyFocused == node { | |
| 640 | - | spm.wm.currentlyFocused = lastPane | |
| 662 | + | // FIXED: Focus the remaining pane if any pane from this stack was currently focused | |
| 663 | + | // Check if currentlyFocused is part of this stack (including the pane being closed) | |
| 664 | + | shouldFocus := false | |
| 665 | + | if spm.wm.currentlyFocused != nil { | |
| 666 | + | log.Printf("[workspace] DEBUG: currentlyFocused=%p, node being closed=%p, lastPane=%p", | |
| 667 | + | spm.wm.currentlyFocused, node, lastPane) | |
| 668 | + | // Check if currentlyFocused is the pane being closed | |
| 669 | + | if spm.wm.currentlyFocused == node { | |
| 670 | + | shouldFocus = true | |
| 671 | + | log.Printf("[workspace] DEBUG: shouldFocus=true (closing focused pane)") | |
| 672 | + | } else if spm.wm.currentlyFocused.parent == stackNode { | |
| 673 | + | // Check if currentlyFocused is another pane in this stack | |
| 674 | + | shouldFocus = true | |
| 675 | + | log.Printf("[workspace] DEBUG: shouldFocus=true (focused pane in same stack)") | |
| 676 | + | } | |
| 677 | + | } | |
| 678 | + | ||
| 679 | + | if shouldFocus { | |
| 680 | + | log.Printf("[workspace] focusing remaining pane after stack conversion") | |
| 681 | + | ||
| 682 | + | // Log widget state before focus operations | |
| 683 | + | if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 { | |
| 684 | + | visible := webkit.WidgetGetVisible(lastPaneWidget) | |
| 685 | + | log.Printf("[workspace] Widget state before focus: widget=%p visible=%v", | |
| 686 | + | unsafe.Pointer(lastPaneWidget), visible) | |
| 687 | + | } | |
| 688 | + | ||
| 689 | + | // Update CSS classes (mirror finalizeStackCreation) | |
| 690 | + | spm.wm.ensurePaneBaseClasses() | |
| 691 | + | ||
| 692 | + | // Mirror the exact logic from finalizeStackCreation that works perfectly: | |
| 693 | + | // 1. First call SetActivePane (this does all the focus work) | |
| 694 | + | // 2. Then set currentlyFocused | |
| 641 | 695 | spm.wm.focusManager.SetActivePane(lastPane) | |
| 696 | + | spm.wm.currentlyFocused = lastPane | |
| 697 | + | ||
| 698 | + | // CRITICAL: Ensure widget visibility and focus after reparenting | |
| 699 | + | // The widget may need explicit show/focus calls after being reparented | |
| 700 | + | if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 { | |
| 701 | + | log.Printf("[workspace] Ensuring widget visibility after reparenting") | |
| 702 | + | webkit.WidgetShow(lastPaneWidget) | |
| 703 | + | webkit.WidgetGrabFocus(lastPaneWidget) | |
| 704 | + | ||
| 705 | + | // Log widget state after focus operations | |
| 706 | + | visible := webkit.WidgetGetVisible(lastPaneWidget) | |
| 707 | + | log.Printf("[workspace] Widget state after focus: widget=%p visible=%v", | |
| 708 | + | unsafe.Pointer(lastPaneWidget), visible) | |
| 709 | + | } | |
| 710 | + | ||
| 711 | + | log.Printf("[workspace] DEBUG: applied finalizeStackCreation focus logic in reverse") | |
| 712 | + | } else { | |
| 713 | + | log.Printf("[workspace] DEBUG: not focusing remaining pane (shouldFocus=false)") | |
| 642 | 714 | } | |
| 643 | 715 | ||
| 644 | 716 | log.Printf("[workspace] converted single-pane stack back to regular pane") | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "log" | 6 | "log" | ||
| 7 | "time" | 7 | "time" | ||
| 8 | + | "unsafe" | |||
| 8 | 9 | ||||
| 9 | "github.com/bnema/dumber/pkg/webkit" | 10 | "github.com/bnema/dumber/pkg/webkit" | ||
| 10 | ) | 11 | ) | ||
| @@ -468,9 +469,28 @@ func (spm *StackedPaneManager) UpdateTitleBar(webView *webkit.WebView, title str | |||||
| 468 | 469 | ||||
| 469 | // Check if this pane is in a stack and has a title bar | 470 | // Check if this pane is in a stack and has a title bar | ||
| 470 | if node.parent != nil && node.parent.isStacked && node.titleBar != 0 { | 471 | if node.parent != nil && node.parent.isStacked && node.titleBar != 0 { | ||
| 471 | - | // Update the title bar label with the new title | 472 | + | // CRITICAL: Only update title bar for INACTIVE panes |
| 472 | - | spm.updateTitleBarLabel(node, title) | 473 | + | // Active panes should never show their title bar |
| 473 | - | log.Printf("[workspace] updated title bar for WebView %s: %s", webView.ID(), title) | 474 | + | stackNode := node.parent |
| 475 | + | activeIndex := stackNode.activeStackIndex | |||
| 476 | + | ||||
| 477 | + | // Find this pane's index in the stack | |||
| 478 | + | paneIndex := -1 | |||
| 479 | + | for i, pane := range stackNode.stackedPanes { | |||
| 480 | + | if pane == node { | |||
| 481 | + | paneIndex = i | |||
| 482 | + | break | |||
| 483 | + | } | |||
| 484 | + | } | |||
| 485 | + | ||||
| 486 | + | if paneIndex != -1 && paneIndex != activeIndex { | |||
| 487 | + | // This is an INACTIVE pane - safe to update title bar | |||
| 488 | + | spm.updateTitleBarLabel(node, title) | |||
| 489 | + | log.Printf("[workspace] updated title bar for INACTIVE WebView %s: %s", webView.ID(), title) | |||
| 490 | + | } else { | |||
| 491 | + | // This is the ACTIVE pane - title bar should remain hidden | |||
| 492 | + | log.Printf("[workspace] skipped title bar update for ACTIVE WebView %s: %s", webView.ID(), title) | |||
| 493 | + | } | |||
| 474 | } | 494 | } | ||
| 475 | } | 495 | } | ||
| 476 | 496 | ||||
| @@ -632,13 +652,65 @@ func (spm *StackedPaneManager) CloseStackedPane(node *paneNode) error { | |||||
| 632 | lastPane.stackedPanes = nil | 652 | lastPane.stackedPanes = nil | ||
| 633 | lastPane.titleBar = 0 | 653 | lastPane.titleBar = 0 | ||
| 634 | 654 | ||||
| 655 | + | // CRITICAL: Ensure the container is visible after reparenting | |||
| 656 | + | // The container may have been hidden during stack operations | |||
| 657 | + | webkit.WidgetSetVisible(lastPaneContainer, true) | |||
| 658 | + | ||||
| 635 | // Update the viewToNode mapping to point to the lastPane, not stackNode | 659 | // Update the viewToNode mapping to point to the lastPane, not stackNode | ||
| 636 | spm.wm.viewToNode[lastPane.pane.webView] = lastPane | 660 | spm.wm.viewToNode[lastPane.pane.webView] = lastPane | ||
| 637 | 661 | ||||
| 638 | - | // Focus the remaining pane if it was the active one being closed | 662 | + | // FIXED: Focus the remaining pane if any pane from this stack was currently focused |
| 639 | - | if spm.wm.currentlyFocused == node { | 663 | + | // Check if currentlyFocused is part of this stack (including the pane being closed) |
| 640 | - | spm.wm.currentlyFocused = lastPane | 664 | + | shouldFocus := false |
| 665 | + | if spm.wm.currentlyFocused != nil { | |||
| 666 | + | log.Printf("[workspace] DEBUG: currentlyFocused=%p, node being closed=%p, lastPane=%p", | |||
| 667 | + | spm.wm.currentlyFocused, node, lastPane) | |||
| 668 | + | // Check if currentlyFocused is the pane being closed | |||
| 669 | + | if spm.wm.currentlyFocused == node { | |||
| 670 | + | shouldFocus = true | |||
| 671 | + | log.Printf("[workspace] DEBUG: shouldFocus=true (closing focused pane)") | |||
| 672 | + | } else if spm.wm.currentlyFocused.parent == stackNode { | |||
| 673 | + | // Check if currentlyFocused is another pane in this stack | |||
| 674 | + | shouldFocus = true | |||
| 675 | + | log.Printf("[workspace] DEBUG: shouldFocus=true (focused pane in same stack)") | |||
| 676 | + | } | |||
| 677 | + | } | |||
| 678 | + | ||||
| 679 | + | if shouldFocus { | |||
| 680 | + | log.Printf("[workspace] focusing remaining pane after stack conversion") | |||
| 681 | + | ||||
| 682 | + | // Log widget state before focus operations | |||
| 683 | + | if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 { | |||
| 684 | + | visible := webkit.WidgetGetVisible(lastPaneWidget) | |||
| 685 | + | log.Printf("[workspace] Widget state before focus: widget=%p visible=%v", | |||
| 686 | + | unsafe.Pointer(lastPaneWidget), visible) | |||
| 687 | + | } | |||
| 688 | + | ||||
| 689 | + | // Update CSS classes (mirror finalizeStackCreation) | |||
| 690 | + | spm.wm.ensurePaneBaseClasses() | |||
| 691 | + | ||||
| 692 | + | // Mirror the exact logic from finalizeStackCreation that works perfectly: | |||
| 693 | + | // 1. First call SetActivePane (this does all the focus work) | |||
| 694 | + | // 2. Then set currentlyFocused | |||
| 641 | spm.wm.focusManager.SetActivePane(lastPane) | 695 | spm.wm.focusManager.SetActivePane(lastPane) | ||
| 696 | + | spm.wm.currentlyFocused = lastPane | |||
| 697 | + | ||||
| 698 | + | // CRITICAL: Ensure widget visibility and focus after reparenting | |||
| 699 | + | // The widget may need explicit show/focus calls after being reparented | |||
| 700 | + | if lastPaneWidget := lastPane.pane.webView.Widget(); lastPaneWidget != 0 { | |||
| 701 | + | log.Printf("[workspace] Ensuring widget visibility after reparenting") | |||
| 702 | + | webkit.WidgetShow(lastPaneWidget) | |||
| 703 | + | webkit.WidgetGrabFocus(lastPaneWidget) | |||
| 704 | + | ||||
| 705 | + | // Log widget state after focus operations | |||
| 706 | + | visible := webkit.WidgetGetVisible(lastPaneWidget) | |||
| 707 | + | log.Printf("[workspace] Widget state after focus: widget=%p visible=%v", | |||
| 708 | + | unsafe.Pointer(lastPaneWidget), visible) | |||
| 709 | + | } | |||
| 710 | + | ||||
| 711 | + | log.Printf("[workspace] DEBUG: applied finalizeStackCreation focus logic in reverse") | |||
| 712 | + | } else { | |||
| 713 | + | log.Printf("[workspace] DEBUG: not focusing remaining pane (shouldFocus=false)") | |||
| 642 | } | 714 | } | ||
| 643 | 715 | ||||
| 644 | log.Printf("[workspace] converted single-pane stack back to regular pane") | 716 | log.Printf("[workspace] converted single-pane stack back to regular pane") | ||
M internal/app/browser/workspace_manager.go +3 -340
| @@ -1211,349 +1211,12 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane | |||
| 1211 | 1211 | return newLeaf, nil | |
| 1212 | 1212 | } | |
| 1213 | 1213 | ||
| 1214 | - | // stackPane creates a new pane stacked on top of the target pane. | |
| 1215 | - | // Similar to splitNode but creates a stack container instead of a paned widget. | |
| 1216 | - | func (wm *WorkspaceManager) stackPane(target *paneNode) (*paneNode, error) { | |
| 1217 | - | if target == nil || !target.isLeaf || target.pane == nil { | |
| 1218 | - | return nil, errors.New("stack target must be a leaf pane") | |
| 1219 | - | } | |
| 1220 | - | ||
| 1221 | - | newView, err := wm.createWebView() | |
| 1222 | - | if err != nil { | |
| 1223 | - | return nil, err | |
| 1224 | - | } | |
| 1225 | - | ||
| 1226 | - | newPane, err := wm.createPane(newView) | |
| 1227 | - | if err != nil { | |
| 1228 | - | return nil, err | |
| 1229 | - | } | |
| 1230 | - | ||
| 1231 | - | if handler := newPane.MessageHandler(); handler != nil { | |
| 1232 | - | handler.SetWorkspaceObserver(wm) | |
| 1233 | - | } | |
| 1234 | - | ||
| 1235 | - | newContainer := newPane.webView.RootWidget() | |
| 1236 | - | if newContainer == 0 { | |
| 1237 | - | return nil, errors.New("new pane missing container") | |
| 1238 | - | } | |
| 1239 | - | webkit.WidgetSetHExpand(newContainer, true) | |
| 1240 | - | webkit.WidgetSetVExpand(newContainer, true) | |
| 1241 | - | webkit.WidgetRealizeInContainer(newContainer) | |
| 1242 | - | ||
| 1243 | - | var stackNode *paneNode | |
| 1244 | - | var insertIndex int | |
| 1245 | - | ||
| 1246 | - | // Check if target is already in a stack | |
| 1247 | - | if target.parent != nil && target.parent.isStacked { | |
| 1248 | - | // Target is already in a stack - find the stack container and insertion point | |
| 1249 | - | stackNode = target.parent | |
| 1250 | - | ||
| 1251 | - | // Find the current position of the target pane in the stack | |
| 1252 | - | currentIndex := -1 | |
| 1253 | - | for i, pane := range stackNode.stackedPanes { | |
| 1254 | - | if pane == target { | |
| 1255 | - | currentIndex = i | |
| 1256 | - | break | |
| 1257 | - | } | |
| 1258 | - | } | |
| 1259 | - | ||
| 1260 | - | if currentIndex == -1 { | |
| 1261 | - | return nil, errors.New("target pane not found in stack") | |
| 1262 | - | } | |
| 1263 | - | ||
| 1264 | - | // Insert the new pane right after the current pane | |
| 1265 | - | insertIndex = currentIndex + 1 | |
| 1266 | - | log.Printf("[workspace] adding to existing stack: currentIndex=%d insertIndex=%d stackSize=%d", | |
| 1267 | - | currentIndex, insertIndex, len(stackNode.stackedPanes)) | |
| 1268 | - | } else if target.isStacked { | |
| 1269 | - | // This shouldn't happen with current structure, but handle it | |
| 1270 | - | return nil, errors.New("target pane has inconsistent stacking state") | |
| 1271 | - | } else { | |
| 1272 | - | // Target is not stacked - create initial stack | |
| 1273 | - | log.Printf("[workspace] converting pane to stacked: %p", target) | |
| 1274 | - | ||
| 1275 | - | // Create the wrapper container - this is what will be used by splitNode | |
| 1276 | - | stackWrapperContainer := webkit.NewBox(webkit.OrientationVertical, 0) | |
| 1277 | - | if stackWrapperContainer == 0 { | |
| 1278 | - | return nil, errors.New("failed to create stack wrapper container") | |
| 1279 | - | } | |
| 1280 | - | webkit.WidgetSetHExpand(stackWrapperContainer, true) | |
| 1281 | - | webkit.WidgetSetVExpand(stackWrapperContainer, true) | |
| 1282 | - | ||
| 1283 | - | // Create the internal box for the actual stacked widgets (titles + webviews) | |
| 1284 | - | stackInternalBox := webkit.NewBox(webkit.OrientationVertical, 0) | |
| 1285 | - | if stackInternalBox == 0 { | |
| 1286 | - | return nil, errors.New("failed to create stack internal box") | |
| 1287 | - | } | |
| 1288 | - | webkit.WidgetSetHExpand(stackInternalBox, true) | |
| 1289 | - | webkit.WidgetSetVExpand(stackInternalBox, true) | |
| 1290 | - | ||
| 1291 | - | // The internal box goes inside the wrapper | |
| 1292 | - | webkit.BoxAppend(stackWrapperContainer, stackInternalBox) | |
| 1293 | - | ||
| 1294 | - | // Get the existing container and parent info | |
| 1295 | - | existingContainer := target.container | |
| 1296 | - | parent := target.parent | |
| 1297 | - | ||
| 1298 | - | // Create title bar for the existing pane | |
| 1299 | - | titleBar := wm.createTitleBar(target) | |
| 1300 | - | ||
| 1301 | - | // Keep existing container visible during transition to prevent rendering glitch | |
| 1302 | - | // webkit.WidgetSetVisible(existingContainer, false) // REMOVED - causes rendering block | |
| 1303 | - | webkit.WidgetSetVisible(titleBar, false) | |
| 1304 | - | ||
| 1305 | - | // Detach existing container from its current parent first | |
| 1306 | - | // This is necessary before we can add it to the stack | |
| 1307 | - | if parent == nil { | |
| 1308 | - | // Target is the root - remove it from window | |
| 1309 | - | if wm.window != nil { | |
| 1310 | - | wm.window.SetChild(0) | |
| 1311 | - | } | |
| 1312 | - | // Unparent if it has a GTK parent | |
| 1313 | - | if webkit.WidgetGetParent(existingContainer) != 0 { | |
| 1314 | - | webkit.WidgetUnparent(existingContainer) | |
| 1315 | - | } | |
| 1316 | - | } else if parent.container != 0 { | |
| 1317 | - | // Target has a parent paned - remove it (automatically unparents in GTK4) | |
| 1318 | - | if parent.left == target { | |
| 1319 | - | webkit.PanedSetStartChild(parent.container, 0) | |
| 1320 | - | } else if parent.right == target { | |
| 1321 | - | webkit.PanedSetEndChild(parent.container, 0) | |
| 1322 | - | } | |
| 1323 | - | } | |
| 1324 | - | ||
| 1325 | - | // Build the complete stack structure with hidden widgets | |
| 1326 | - | webkit.BoxAppend(stackInternalBox, titleBar) | |
| 1327 | - | webkit.BoxAppend(stackInternalBox, existingContainer) | |
| 1328 | - | ||
| 1329 | - | // Immediately reattach the stack wrapper to minimize visibility gap | |
| 1330 | - | if parent == nil { | |
| 1331 | - | if wm.window != nil { | |
| 1332 | - | wm.window.SetChild(stackWrapperContainer) | |
| 1333 | - | } | |
| 1334 | - | } else if parent.container != 0 { | |
| 1335 | - | if parent.left == target { | |
| 1336 | - | webkit.PanedSetStartChild(parent.container, stackWrapperContainer) | |
| 1337 | - | } else if parent.right == target { | |
| 1338 | - | webkit.PanedSetEndChild(parent.container, stackWrapperContainer) | |
| 1339 | - | } | |
| 1340 | - | } | |
| 1341 | - | ||
| 1342 | - | // Convert target to a stacked leaf node | |
| 1343 | - | target.isStacked = true | |
| 1344 | - | target.isLeaf = true | |
| 1345 | - | target.container = existingContainer | |
| 1346 | - | target.titleBar = titleBar | |
| 1347 | - | ||
| 1348 | - | // Create the stack container node - container points to wrapper, stackWrapper points to internal box | |
| 1349 | - | stackNode = &paneNode{ | |
| 1350 | - | isStacked: true, | |
| 1351 | - | isLeaf: false, | |
| 1352 | - | container: stackWrapperContainer, // Wrapper for GTK operations (splits, etc.) | |
| 1353 | - | stackWrapper: stackInternalBox, // Internal box for stack operations | |
| 1354 | - | stackedPanes: []*paneNode{target}, | |
| 1355 | - | activeStackIndex: 0, // KEEP CURRENT PANE ACTIVE during transition (index 0) | |
| 1356 | - | parent: parent, | |
| 1357 | - | } | |
| 1358 | - | ||
| 1359 | - | // Update target's parent to be the stack node | |
| 1360 | - | target.parent = stackNode | |
| 1361 | - | ||
| 1362 | - | // Update parent references (GTK operations already done above) | |
| 1363 | - | if parent == nil { | |
| 1364 | - | wm.root = stackNode | |
| 1365 | - | } else { | |
| 1366 | - | if parent.left == target { | |
| 1367 | - | parent.left = stackNode | |
| 1368 | - | } else if parent.right == target { | |
| 1369 | - | parent.right = stackNode | |
| 1370 | - | } | |
| 1371 | - | } | |
| 1372 | - | ||
| 1373 | - | // New pane will be inserted at index 1 (after the original pane) | |
| 1374 | - | insertIndex = 1 | |
| 1375 | - | } | |
| 1376 | - | ||
| 1377 | - | // Create new leaf node for the new pane | |
| 1378 | - | newLeaf := &paneNode{ | |
| 1379 | - | pane: newPane, | |
| 1380 | - | parent: stackNode, | |
| 1381 | - | container: newContainer, | |
| 1382 | - | isLeaf: true, | |
| 1383 | - | } | |
| 1384 | - | ||
| 1385 | - | // Create title bar for the new pane | |
| 1386 | - | newTitleBar := wm.createTitleBar(newLeaf) | |
| 1387 | - | newLeaf.titleBar = newTitleBar | |
| 1388 | - | ||
| 1389 | - | // Keep new container hidden initially - will be shown after transition | |
| 1390 | - | webkit.WidgetSetVisible(newContainer, false) | |
| 1391 | - | webkit.WidgetSetVisible(newTitleBar, false) | |
| 1392 | - | ||
| 1393 | - | // Insert the new pane at the correct position in the slice | |
| 1394 | - | stackNode.stackedPanes = append(stackNode.stackedPanes, nil) // Expand slice | |
| 1395 | - | copy(stackNode.stackedPanes[insertIndex+1:], stackNode.stackedPanes[insertIndex:]) // Shift elements | |
| 1396 | - | stackNode.stackedPanes[insertIndex] = newLeaf // Insert new pane | |
| 1397 | - | ||
| 1398 | - | // Unparent the new container before adding it to the stack (only if it has a parent) | |
| 1399 | - | if webkit.WidgetGetParent(newContainer) != 0 { | |
| 1400 | - | webkit.WidgetUnparent(newContainer) | |
| 1401 | - | } | |
| 1402 | - | ||
| 1403 | - | // Add the new widgets to the internal stack box (not the wrapper) | |
| 1404 | - | // GTK will handle the visual order correctly as long as we manage visibility properly | |
| 1405 | - | webkit.BoxAppend(stackNode.stackWrapper, newTitleBar) | |
| 1406 | - | webkit.BoxAppend(stackNode.stackWrapper, newContainer) | |
| 1407 | - | ||
| 1408 | - | // Update workspace state first | |
| 1409 | - | wm.viewToNode[newPane.webView] = newLeaf | |
| 1410 | - | wm.ensureHover(newLeaf) | |
| 1411 | - | wm.app.panes = append(wm.app.panes, newPane) | |
| 1412 | - | if newPane.zoomController != nil { | |
| 1413 | - | newPane.zoomController.ApplyInitialZoom() | |
| 1414 | - | } | |
| 1415 | - | ||
| 1416 | - | // Update CSS classes | |
| 1417 | - | wm.ensurePaneBaseClasses() | |
| 1418 | - | ||
| 1419 | - | // Mark stack operation timestamp to prevent focus conflicts | |
| 1420 | - | wm.lastStackOperation = time.Now() | |
| 1421 | - | ||
| 1422 | - | // Update stack visibility to show current state (existing pane still active) | |
| 1423 | - | wm.stackedPaneManager.UpdateStackVisibility(stackNode) | |
| 1424 | - | ||
| 1425 | - | // Transition to the new pane after a brief delay to avoid rendering conflicts | |
| 1426 | - | webkit.IdleAdd(func() bool { | |
| 1427 | - | // Now switch to the new pane | |
| 1428 | - | stackNode.activeStackIndex = insertIndex | |
| 1429 | - | wm.stackedPaneManager.UpdateStackVisibility(stackNode) | |
| 1430 | - | wm.currentlyFocused = newLeaf | |
| 1431 | - | wm.focusManager.SetActivePane(newLeaf) | |
| 1432 | - | return false // Remove idle callback | |
| 1433 | - | }) | |
| 1434 | - | ||
| 1435 | - | log.Printf("[workspace] stacked new pane: stackNode=%p newLeaf=%p stackSize=%d activeIndex=%d insertIndex=%d", | |
| 1436 | - | stackNode, newLeaf, len(stackNode.stackedPanes), stackNode.activeStackIndex, insertIndex) | |
| 1437 | - | return newLeaf, nil | |
| 1438 | - | } | |
| 1439 | - | ||
| 1440 | 1214 | // UpdateTitleBar updates the title bar label for a WebView in stacked panes | |
| 1441 | 1215 | func (wm *WorkspaceManager) UpdateTitleBar(webView *webkit.WebView, title string) { | |
| 1442 | - | if wm == nil || webView == nil || title == "" { | |
| 1443 | - | return | |
| 1444 | - | } | |
| 1445 | - | ||
| 1446 | - | // Find the pane node for this WebView | |
| 1447 | - | node, exists := wm.viewToNode[webView] | |
| 1448 | - | if !exists || node == nil || !node.isLeaf { | |
| 1449 | - | return | |
| 1216 | + | // Delegate to StackedPaneManager which has the correct logic | |
| 1217 | + | if wm.stackedPaneManager != nil { | |
| 1218 | + | wm.stackedPaneManager.UpdateTitleBar(webView, title) | |
| 1450 | 1219 | } | |
| 1451 | - | ||
| 1452 | - | // Check if this pane is in a stack and has a title bar | |
| 1453 | - | if node.parent != nil && node.parent.isStacked && node.titleBar != 0 { | |
| 1454 | - | // Update the title bar label with the new title | |
| 1455 | - | wm.updateTitleBarLabel(node, title) | |
| 1456 | - | log.Printf("[workspace] updated title bar for WebView %s: %s", webView.ID(), title) | |
| 1457 | - | } | |
| 1458 | - | } | |
| 1459 | - | ||
| 1460 | - | // updateTitleBarLabel updates the label widget within a title bar by recreating it | |
| 1461 | - | func (wm *WorkspaceManager) updateTitleBarLabel(node *paneNode, title string) { | |
| 1462 | - | if node == nil || node.titleBar == 0 || node.parent == nil || !node.parent.isStacked { | |
| 1463 | - | return | |
| 1464 | - | } | |
| 1465 | - | ||
| 1466 | - | // Format the title for display | |
| 1467 | - | displayTitle := title | |
| 1468 | - | if len(displayTitle) > 50 { | |
| 1469 | - | displayTitle = displayTitle[:47] + "..." | |
| 1470 | - | } | |
| 1471 | - | ||
| 1472 | - | // Recreate the title bar with the new title | |
| 1473 | - | stackNode := node.parent | |
| 1474 | - | oldTitleBar := node.titleBar | |
| 1475 | - | ||
| 1476 | - | // Create new title bar with updated title | |
| 1477 | - | newTitleBar := wm.createTitleBarWithTitle(node, displayTitle) | |
| 1478 | - | if newTitleBar == 0 { | |
| 1479 | - | log.Printf("[workspace] failed to create new title bar") | |
| 1480 | - | return | |
| 1481 | - | } | |
| 1482 | - | ||
| 1483 | - | // Replace the old title bar in the internal stack box (not the wrapper) | |
| 1484 | - | // Insert the new one after the old one, then remove the old one | |
| 1485 | - | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newTitleBar, oldTitleBar) | |
| 1486 | - | webkit.BoxRemove(stackNode.stackWrapper, oldTitleBar) | |
| 1487 | - | ||
| 1488 | - | // Update the node reference | |
| 1489 | - | node.titleBar = newTitleBar | |
| 1490 | - | ||
| 1491 | - | log.Printf("[workspace] updated title bar label: %s", displayTitle) | |
| 1492 | - | } | |
| 1493 | - | ||
| 1494 | - | // createTitleBarWithTitle creates a title bar with a specific title | |
| 1495 | - | func (wm *WorkspaceManager) createTitleBarWithTitle(pane *paneNode, title string) uintptr { | |
| 1496 | - | titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8) | |
| 1497 | - | if titleBox == 0 { | |
| 1498 | - | log.Printf("[workspace] failed to create title bar box") | |
| 1499 | - | return 0 | |
| 1500 | - | } | |
| 1501 | - | webkit.WidgetSetHExpand(titleBox, true) | |
| 1502 | - | webkit.WidgetSetVExpand(titleBox, false) | |
| 1503 | - | ||
| 1504 | - | // Use the provided title | |
| 1505 | - | displayTitle := title | |
| 1506 | - | if displayTitle == "" { | |
| 1507 | - | displayTitle = "New Tab" | |
| 1508 | - | } | |
| 1509 | - | ||
| 1510 | - | label := webkit.NewLabel(displayTitle) | |
| 1511 | - | if label != 0 { | |
| 1512 | - | webkit.LabelSetEllipsize(label, webkit.EllipsizeEnd) | |
| 1513 | - | webkit.WidgetSetHExpand(label, true) | |
| 1514 | - | webkit.BoxAppend(titleBox, label) | |
| 1515 | - | } | |
| 1516 | - | ||
| 1517 | - | // Add CSS classes | |
| 1518 | - | webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title") | |
| 1519 | - | webkit.WidgetAddCSSClass(label, "stacked-pane-title-text") | |
| 1520 | - | ||
| 1521 | - | log.Printf("[workspace] created title bar with custom title: box=%#x label=%#x title=%s", titleBox, label, displayTitle) | |
| 1522 | - | return titleBox | |
| 1523 | - | } | |
| 1524 | - | ||
| 1525 | - | // createTitleBar creates a title bar widget for a pane in a stack. | |
| 1526 | - | func (wm *WorkspaceManager) createTitleBar(pane *paneNode) uintptr { | |
| 1527 | - | titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8) | |
| 1528 | - | if titleBox == 0 { | |
| 1529 | - | log.Printf("[workspace] failed to create title bar box") | |
| 1530 | - | return 0 | |
| 1531 | - | } | |
| 1532 | - | ||
| 1533 | - | webkit.WidgetSetHExpand(titleBox, true) | |
| 1534 | - | webkit.WidgetSetVExpand(titleBox, false) | |
| 1535 | - | ||
| 1536 | - | // Create label for page title and domain | |
| 1537 | - | title := "New Tab" | |
| 1538 | - | if pane.pane != nil && pane.pane.webView != nil { | |
| 1539 | - | if pageTitle := pane.pane.webView.GetTitle(); pageTitle != "" { | |
| 1540 | - | title = pageTitle | |
| 1541 | - | } | |
| 1542 | - | } | |
| 1543 | - | ||
| 1544 | - | label := webkit.NewLabel(title) | |
| 1545 | - | if label != 0 { | |
| 1546 | - | webkit.LabelSetEllipsize(label, webkit.EllipsizeEnd) | |
| 1547 | - | webkit.WidgetSetHExpand(label, true) | |
| 1548 | - | webkit.BoxAppend(titleBox, label) | |
| 1549 | - | } | |
| 1550 | - | ||
| 1551 | - | // Add CSS classes | |
| 1552 | - | webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title") | |
| 1553 | - | webkit.WidgetAddCSSClass(label, "stacked-pane-title-text") | |
| 1554 | - | ||
| 1555 | - | log.Printf("[workspace] created title bar: box=%#x label=%#x title=%s", titleBox, label, title) | |
| 1556 | - | return titleBox | |
| 1557 | 1220 | } | |
| 1558 | 1221 | ||
| 1559 | 1222 | // updateStackVisibility updates the visibility of panes in a stack. | |
| @@ -1211,349 +1211,12 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane | |||||
| 1211 | return newLeaf, nil | 1211 | return newLeaf, nil | ||
| 1212 | } | 1212 | } | ||
| 1213 | 1213 | ||||
| 1214 | - | // stackPane creates a new pane stacked on top of the target pane. | |||
| 1215 | - | // Similar to splitNode but creates a stack container instead of a paned widget. | |||
| 1216 | - | func (wm *WorkspaceManager) stackPane(target *paneNode) (*paneNode, error) { | |||
| 1217 | - | if target == nil || !target.isLeaf || target.pane == nil { | |||
| 1218 | - | return nil, errors.New("stack target must be a leaf pane") | |||
| 1219 | - | } | |||
| 1220 | - | ||||
| 1221 | - | newView, err := wm.createWebView() | |||
| 1222 | - | if err != nil { | |||
| 1223 | - | return nil, err | |||
| 1224 | - | } | |||
| 1225 | - | ||||
| 1226 | - | newPane, err := wm.createPane(newView) | |||
| 1227 | - | if err != nil { | |||
| 1228 | - | return nil, err | |||
| 1229 | - | } | |||
| 1230 | - | ||||
| 1231 | - | if handler := newPane.MessageHandler(); handler != nil { | |||
| 1232 | - | handler.SetWorkspaceObserver(wm) | |||
| 1233 | - | } | |||
| 1234 | - | ||||
| 1235 | - | newContainer := newPane.webView.RootWidget() | |||
| 1236 | - | if newContainer == 0 { | |||
| 1237 | - | return nil, errors.New("new pane missing container") | |||
| 1238 | - | } | |||
| 1239 | - | webkit.WidgetSetHExpand(newContainer, true) | |||
| 1240 | - | webkit.WidgetSetVExpand(newContainer, true) | |||
| 1241 | - | webkit.WidgetRealizeInContainer(newContainer) | |||
| 1242 | - | ||||
| 1243 | - | var stackNode *paneNode | |||
| 1244 | - | var insertIndex int | |||
| 1245 | - | ||||
| 1246 | - | // Check if target is already in a stack | |||
| 1247 | - | if target.parent != nil && target.parent.isStacked { | |||
| 1248 | - | // Target is already in a stack - find the stack container and insertion point | |||
| 1249 | - | stackNode = target.parent | |||
| 1250 | - | ||||
| 1251 | - | // Find the current position of the target pane in the stack | |||
| 1252 | - | currentIndex := -1 | |||
| 1253 | - | for i, pane := range stackNode.stackedPanes { | |||
| 1254 | - | if pane == target { | |||
| 1255 | - | currentIndex = i | |||
| 1256 | - | break | |||
| 1257 | - | } | |||
| 1258 | - | } | |||
| 1259 | - | ||||
| 1260 | - | if currentIndex == -1 { | |||
| 1261 | - | return nil, errors.New("target pane not found in stack") | |||
| 1262 | - | } | |||
| 1263 | - | ||||
| 1264 | - | // Insert the new pane right after the current pane | |||
| 1265 | - | insertIndex = currentIndex + 1 | |||
| 1266 | - | log.Printf("[workspace] adding to existing stack: currentIndex=%d insertIndex=%d stackSize=%d", | |||
| 1267 | - | currentIndex, insertIndex, len(stackNode.stackedPanes)) | |||
| 1268 | - | } else if target.isStacked { | |||
| 1269 | - | // This shouldn't happen with current structure, but handle it | |||
| 1270 | - | return nil, errors.New("target pane has inconsistent stacking state") | |||
| 1271 | - | } else { | |||
| 1272 | - | // Target is not stacked - create initial stack | |||
| 1273 | - | log.Printf("[workspace] converting pane to stacked: %p", target) | |||
| 1274 | - | ||||
| 1275 | - | // Create the wrapper container - this is what will be used by splitNode | |||
| 1276 | - | stackWrapperContainer := webkit.NewBox(webkit.OrientationVertical, 0) | |||
| 1277 | - | if stackWrapperContainer == 0 { | |||
| 1278 | - | return nil, errors.New("failed to create stack wrapper container") | |||
| 1279 | - | } | |||
| 1280 | - | webkit.WidgetSetHExpand(stackWrapperContainer, true) | |||
| 1281 | - | webkit.WidgetSetVExpand(stackWrapperContainer, true) | |||
| 1282 | - | ||||
| 1283 | - | // Create the internal box for the actual stacked widgets (titles + webviews) | |||
| 1284 | - | stackInternalBox := webkit.NewBox(webkit.OrientationVertical, 0) | |||
| 1285 | - | if stackInternalBox == 0 { | |||
| 1286 | - | return nil, errors.New("failed to create stack internal box") | |||
| 1287 | - | } | |||
| 1288 | - | webkit.WidgetSetHExpand(stackInternalBox, true) | |||
| 1289 | - | webkit.WidgetSetVExpand(stackInternalBox, true) | |||
| 1290 | - | ||||
| 1291 | - | // The internal box goes inside the wrapper | |||
| 1292 | - | webkit.BoxAppend(stackWrapperContainer, stackInternalBox) | |||
| 1293 | - | ||||
| 1294 | - | // Get the existing container and parent info | |||
| 1295 | - | existingContainer := target.container | |||
| 1296 | - | parent := target.parent | |||
| 1297 | - | ||||
| 1298 | - | // Create title bar for the existing pane | |||
| 1299 | - | titleBar := wm.createTitleBar(target) | |||
| 1300 | - | ||||
| 1301 | - | // Keep existing container visible during transition to prevent rendering glitch | |||
| 1302 | - | // webkit.WidgetSetVisible(existingContainer, false) // REMOVED - causes rendering block | |||
| 1303 | - | webkit.WidgetSetVisible(titleBar, false) | |||
| 1304 | - | ||||
| 1305 | - | // Detach existing container from its current parent first | |||
| 1306 | - | // This is necessary before we can add it to the stack | |||
| 1307 | - | if parent == nil { | |||
| 1308 | - | // Target is the root - remove it from window | |||
| 1309 | - | if wm.window != nil { | |||
| 1310 | - | wm.window.SetChild(0) | |||
| 1311 | - | } | |||
| 1312 | - | // Unparent if it has a GTK parent | |||
| 1313 | - | if webkit.WidgetGetParent(existingContainer) != 0 { | |||
| 1314 | - | webkit.WidgetUnparent(existingContainer) | |||
| 1315 | - | } | |||
| 1316 | - | } else if parent.container != 0 { | |||
| 1317 | - | // Target has a parent paned - remove it (automatically unparents in GTK4) | |||
| 1318 | - | if parent.left == target { | |||
| 1319 | - | webkit.PanedSetStartChild(parent.container, 0) | |||
| 1320 | - | } else if parent.right == target { | |||
| 1321 | - | webkit.PanedSetEndChild(parent.container, 0) | |||
| 1322 | - | } | |||
| 1323 | - | } | |||
| 1324 | - | ||||
| 1325 | - | // Build the complete stack structure with hidden widgets | |||
| 1326 | - | webkit.BoxAppend(stackInternalBox, titleBar) | |||
| 1327 | - | webkit.BoxAppend(stackInternalBox, existingContainer) | |||
| 1328 | - | ||||
| 1329 | - | // Immediately reattach the stack wrapper to minimize visibility gap | |||
| 1330 | - | if parent == nil { | |||
| 1331 | - | if wm.window != nil { | |||
| 1332 | - | wm.window.SetChild(stackWrapperContainer) | |||
| 1333 | - | } | |||
| 1334 | - | } else if parent.container != 0 { | |||
| 1335 | - | if parent.left == target { | |||
| 1336 | - | webkit.PanedSetStartChild(parent.container, stackWrapperContainer) | |||
| 1337 | - | } else if parent.right == target { | |||
| 1338 | - | webkit.PanedSetEndChild(parent.container, stackWrapperContainer) | |||
| 1339 | - | } | |||
| 1340 | - | } | |||
| 1341 | - | ||||
| 1342 | - | // Convert target to a stacked leaf node | |||
| 1343 | - | target.isStacked = true | |||
| 1344 | - | target.isLeaf = true | |||
| 1345 | - | target.container = existingContainer | |||
| 1346 | - | target.titleBar = titleBar | |||
| 1347 | - | ||||
| 1348 | - | // Create the stack container node - container points to wrapper, stackWrapper points to internal box | |||
| 1349 | - | stackNode = &paneNode{ | |||
| 1350 | - | isStacked: true, | |||
| 1351 | - | isLeaf: false, | |||
| 1352 | - | container: stackWrapperContainer, // Wrapper for GTK operations (splits, etc.) | |||
| 1353 | - | stackWrapper: stackInternalBox, // Internal box for stack operations | |||
| 1354 | - | stackedPanes: []*paneNode{target}, | |||
| 1355 | - | activeStackIndex: 0, // KEEP CURRENT PANE ACTIVE during transition (index 0) | |||
| 1356 | - | parent: parent, | |||
| 1357 | - | } | |||
| 1358 | - | ||||
| 1359 | - | // Update target's parent to be the stack node | |||
| 1360 | - | target.parent = stackNode | |||
| 1361 | - | ||||
| 1362 | - | // Update parent references (GTK operations already done above) | |||
| 1363 | - | if parent == nil { | |||
| 1364 | - | wm.root = stackNode | |||
| 1365 | - | } else { | |||
| 1366 | - | if parent.left == target { | |||
| 1367 | - | parent.left = stackNode | |||
| 1368 | - | } else if parent.right == target { | |||
| 1369 | - | parent.right = stackNode | |||
| 1370 | - | } | |||
| 1371 | - | } | |||
| 1372 | - | ||||
| 1373 | - | // New pane will be inserted at index 1 (after the original pane) | |||
| 1374 | - | insertIndex = 1 | |||
| 1375 | - | } | |||
| 1376 | - | ||||
| 1377 | - | // Create new leaf node for the new pane | |||
| 1378 | - | newLeaf := &paneNode{ | |||
| 1379 | - | pane: newPane, | |||
| 1380 | - | parent: stackNode, | |||
| 1381 | - | container: newContainer, | |||
| 1382 | - | isLeaf: true, | |||
| 1383 | - | } | |||
| 1384 | - | ||||
| 1385 | - | // Create title bar for the new pane | |||
| 1386 | - | newTitleBar := wm.createTitleBar(newLeaf) | |||
| 1387 | - | newLeaf.titleBar = newTitleBar | |||
| 1388 | - | ||||
| 1389 | - | // Keep new container hidden initially - will be shown after transition | |||
| 1390 | - | webkit.WidgetSetVisible(newContainer, false) | |||
| 1391 | - | webkit.WidgetSetVisible(newTitleBar, false) | |||
| 1392 | - | ||||
| 1393 | - | // Insert the new pane at the correct position in the slice | |||
| 1394 | - | stackNode.stackedPanes = append(stackNode.stackedPanes, nil) // Expand slice | |||
| 1395 | - | copy(stackNode.stackedPanes[insertIndex+1:], stackNode.stackedPanes[insertIndex:]) // Shift elements | |||
| 1396 | - | stackNode.stackedPanes[insertIndex] = newLeaf // Insert new pane | |||
| 1397 | - | ||||
| 1398 | - | // Unparent the new container before adding it to the stack (only if it has a parent) | |||
| 1399 | - | if webkit.WidgetGetParent(newContainer) != 0 { | |||
| 1400 | - | webkit.WidgetUnparent(newContainer) | |||
| 1401 | - | } | |||
| 1402 | - | ||||
| 1403 | - | // Add the new widgets to the internal stack box (not the wrapper) | |||
| 1404 | - | // GTK will handle the visual order correctly as long as we manage visibility properly | |||
| 1405 | - | webkit.BoxAppend(stackNode.stackWrapper, newTitleBar) | |||
| 1406 | - | webkit.BoxAppend(stackNode.stackWrapper, newContainer) | |||
| 1407 | - | ||||
| 1408 | - | // Update workspace state first | |||
| 1409 | - | wm.viewToNode[newPane.webView] = newLeaf | |||
| 1410 | - | wm.ensureHover(newLeaf) | |||
| 1411 | - | wm.app.panes = append(wm.app.panes, newPane) | |||
| 1412 | - | if newPane.zoomController != nil { | |||
| 1413 | - | newPane.zoomController.ApplyInitialZoom() | |||
| 1414 | - | } | |||
| 1415 | - | ||||
| 1416 | - | // Update CSS classes | |||
| 1417 | - | wm.ensurePaneBaseClasses() | |||
| 1418 | - | ||||
| 1419 | - | // Mark stack operation timestamp to prevent focus conflicts | |||
| 1420 | - | wm.lastStackOperation = time.Now() | |||
| 1421 | - | ||||
| 1422 | - | // Update stack visibility to show current state (existing pane still active) | |||
| 1423 | - | wm.stackedPaneManager.UpdateStackVisibility(stackNode) | |||
| 1424 | - | ||||
| 1425 | - | // Transition to the new pane after a brief delay to avoid rendering conflicts | |||
| 1426 | - | webkit.IdleAdd(func() bool { | |||
| 1427 | - | // Now switch to the new pane | |||
| 1428 | - | stackNode.activeStackIndex = insertIndex | |||
| 1429 | - | wm.stackedPaneManager.UpdateStackVisibility(stackNode) | |||
| 1430 | - | wm.currentlyFocused = newLeaf | |||
| 1431 | - | wm.focusManager.SetActivePane(newLeaf) | |||
| 1432 | - | return false // Remove idle callback | |||
| 1433 | - | }) | |||
| 1434 | - | ||||
| 1435 | - | log.Printf("[workspace] stacked new pane: stackNode=%p newLeaf=%p stackSize=%d activeIndex=%d insertIndex=%d", | |||
| 1436 | - | stackNode, newLeaf, len(stackNode.stackedPanes), stackNode.activeStackIndex, insertIndex) | |||
| 1437 | - | return newLeaf, nil | |||
| 1438 | - | } | |||
| 1439 | - | ||||
| 1440 | // UpdateTitleBar updates the title bar label for a WebView in stacked panes | 1214 | // UpdateTitleBar updates the title bar label for a WebView in stacked panes | ||
| 1441 | func (wm *WorkspaceManager) UpdateTitleBar(webView *webkit.WebView, title string) { | 1215 | func (wm *WorkspaceManager) UpdateTitleBar(webView *webkit.WebView, title string) { | ||
| 1442 | - | if wm == nil || webView == nil || title == "" { | 1216 | + | // Delegate to StackedPaneManager which has the correct logic |
| 1443 | - | return | 1217 | + | if wm.stackedPaneManager != nil { |
| 1444 | - | } | 1218 | + | wm.stackedPaneManager.UpdateTitleBar(webView, title) |
| 1445 | - | ||||
| 1446 | - | // Find the pane node for this WebView | |||
| 1447 | - | node, exists := wm.viewToNode[webView] | |||
| 1448 | - | if !exists || node == nil || !node.isLeaf { | |||
| 1449 | - | return | |||
| 1450 | } | 1219 | } | ||
| 1451 | - | ||||
| 1452 | - | // Check if this pane is in a stack and has a title bar | |||
| 1453 | - | if node.parent != nil && node.parent.isStacked && node.titleBar != 0 { | |||
| 1454 | - | // Update the title bar label with the new title | |||
| 1455 | - | wm.updateTitleBarLabel(node, title) | |||
| 1456 | - | log.Printf("[workspace] updated title bar for WebView %s: %s", webView.ID(), title) | |||
| 1457 | - | } | |||
| 1458 | - | } | |||
| 1459 | - | ||||
| 1460 | - | // updateTitleBarLabel updates the label widget within a title bar by recreating it | |||
| 1461 | - | func (wm *WorkspaceManager) updateTitleBarLabel(node *paneNode, title string) { | |||
| 1462 | - | if node == nil || node.titleBar == 0 || node.parent == nil || !node.parent.isStacked { | |||
| 1463 | - | return | |||
| 1464 | - | } | |||
| 1465 | - | ||||
| 1466 | - | // Format the title for display | |||
| 1467 | - | displayTitle := title | |||
| 1468 | - | if len(displayTitle) > 50 { | |||
| 1469 | - | displayTitle = displayTitle[:47] + "..." | |||
| 1470 | - | } | |||
| 1471 | - | ||||
| 1472 | - | // Recreate the title bar with the new title | |||
| 1473 | - | stackNode := node.parent | |||
| 1474 | - | oldTitleBar := node.titleBar | |||
| 1475 | - | ||||
| 1476 | - | // Create new title bar with updated title | |||
| 1477 | - | newTitleBar := wm.createTitleBarWithTitle(node, displayTitle) | |||
| 1478 | - | if newTitleBar == 0 { | |||
| 1479 | - | log.Printf("[workspace] failed to create new title bar") | |||
| 1480 | - | return | |||
| 1481 | - | } | |||
| 1482 | - | ||||
| 1483 | - | // Replace the old title bar in the internal stack box (not the wrapper) | |||
| 1484 | - | // Insert the new one after the old one, then remove the old one | |||
| 1485 | - | webkit.BoxInsertChildAfter(stackNode.stackWrapper, newTitleBar, oldTitleBar) | |||
| 1486 | - | webkit.BoxRemove(stackNode.stackWrapper, oldTitleBar) | |||
| 1487 | - | ||||
| 1488 | - | // Update the node reference | |||
| 1489 | - | node.titleBar = newTitleBar | |||
| 1490 | - | ||||
| 1491 | - | log.Printf("[workspace] updated title bar label: %s", displayTitle) | |||
| 1492 | - | } | |||
| 1493 | - | ||||
| 1494 | - | // createTitleBarWithTitle creates a title bar with a specific title | |||
| 1495 | - | func (wm *WorkspaceManager) createTitleBarWithTitle(pane *paneNode, title string) uintptr { | |||
| 1496 | - | titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8) | |||
| 1497 | - | if titleBox == 0 { | |||
| 1498 | - | log.Printf("[workspace] failed to create title bar box") | |||
| 1499 | - | return 0 | |||
| 1500 | - | } | |||
| 1501 | - | webkit.WidgetSetHExpand(titleBox, true) | |||
| 1502 | - | webkit.WidgetSetVExpand(titleBox, false) | |||
| 1503 | - | ||||
| 1504 | - | // Use the provided title | |||
| 1505 | - | displayTitle := title | |||
| 1506 | - | if displayTitle == "" { | |||
| 1507 | - | displayTitle = "New Tab" | |||
| 1508 | - | } | |||
| 1509 | - | ||||
| 1510 | - | label := webkit.NewLabel(displayTitle) | |||
| 1511 | - | if label != 0 { | |||
| 1512 | - | webkit.LabelSetEllipsize(label, webkit.EllipsizeEnd) | |||
| 1513 | - | webkit.WidgetSetHExpand(label, true) | |||
| 1514 | - | webkit.BoxAppend(titleBox, label) | |||
| 1515 | - | } | |||
| 1516 | - | ||||
| 1517 | - | // Add CSS classes | |||
| 1518 | - | webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title") | |||
| 1519 | - | webkit.WidgetAddCSSClass(label, "stacked-pane-title-text") | |||
| 1520 | - | ||||
| 1521 | - | log.Printf("[workspace] created title bar with custom title: box=%#x label=%#x title=%s", titleBox, label, displayTitle) | |||
| 1522 | - | return titleBox | |||
| 1523 | - | } | |||
| 1524 | - | ||||
| 1525 | - | // createTitleBar creates a title bar widget for a pane in a stack. | |||
| 1526 | - | func (wm *WorkspaceManager) createTitleBar(pane *paneNode) uintptr { | |||
| 1527 | - | titleBox := webkit.NewBox(webkit.OrientationHorizontal, 8) | |||
| 1528 | - | if titleBox == 0 { | |||
| 1529 | - | log.Printf("[workspace] failed to create title bar box") | |||
| 1530 | - | return 0 | |||
| 1531 | - | } | |||
| 1532 | - | ||||
| 1533 | - | webkit.WidgetSetHExpand(titleBox, true) | |||
| 1534 | - | webkit.WidgetSetVExpand(titleBox, false) | |||
| 1535 | - | ||||
| 1536 | - | // Create label for page title and domain | |||
| 1537 | - | title := "New Tab" | |||
| 1538 | - | if pane.pane != nil && pane.pane.webView != nil { | |||
| 1539 | - | if pageTitle := pane.pane.webView.GetTitle(); pageTitle != "" { | |||
| 1540 | - | title = pageTitle | |||
| 1541 | - | } | |||
| 1542 | - | } | |||
| 1543 | - | ||||
| 1544 | - | label := webkit.NewLabel(title) | |||
| 1545 | - | if label != 0 { | |||
| 1546 | - | webkit.LabelSetEllipsize(label, webkit.EllipsizeEnd) | |||
| 1547 | - | webkit.WidgetSetHExpand(label, true) | |||
| 1548 | - | webkit.BoxAppend(titleBox, label) | |||
| 1549 | - | } | |||
| 1550 | - | ||||
| 1551 | - | // Add CSS classes | |||
| 1552 | - | webkit.WidgetAddCSSClass(titleBox, "stacked-pane-title") | |||
| 1553 | - | webkit.WidgetAddCSSClass(label, "stacked-pane-title-text") | |||
| 1554 | - | ||||
| 1555 | - | log.Printf("[workspace] created title bar: box=%#x label=%#x title=%s", titleBox, label, title) | |||
| 1556 | - | return titleBox | |||
| 1557 | } | 1220 | } | ||
| 1558 | 1221 | ||||
| 1559 | // updateStackVisibility updates the visibility of panes in a stack. | 1222 | // updateStackVisibility updates the visibility of panes in a stack. | ||
M internal/app/browser/workspace_test.go +10 -10
| @@ -92,7 +92,7 @@ func (twm *TestWorkspaceManager) createTestStack(t *testing.T, numPanes int) *pa | |||
| 92 | 92 | target := twm.root | |
| 93 | 93 | ||
| 94 | 94 | // Stack the root pane to create the first stack with 2 panes | |
| 95 | - | newLeaf, err := twm.stackPane(target) | |
| 95 | + | newLeaf, err := twm.stackedPaneManager.StackPane(target) | |
| 96 | 96 | if err != nil { | |
| 97 | 97 | t.Fatalf("Failed to create initial stack: %v", err) | |
| 98 | 98 | } | |
| @@ -107,7 +107,7 @@ func (twm *TestWorkspaceManager) createTestStack(t *testing.T, numPanes int) *pa | |||
| 107 | 107 | // Add additional panes to the existing stack | |
| 108 | 108 | for i := 2; i < numPanes; i++ { | |
| 109 | 109 | // Create a new pane by stacking one of the existing panes in the stack | |
| 110 | - | additionalPane, err := twm.stackPane(stackContainer.stackedPanes[0]) | |
| 110 | + | additionalPane, err := twm.stackedPaneManager.StackPane(stackContainer.stackedPanes[0]) | |
| 111 | 111 | if err != nil { | |
| 112 | 112 | t.Fatalf("Failed to stack pane %d: %v", i, err) | |
| 113 | 113 | } | |
| @@ -324,7 +324,7 @@ func TestStackCreation(t *testing.T) { | |||
| 324 | 324 | } | |
| 325 | 325 | ||
| 326 | 326 | // Create first stack | |
| 327 | - | newPane, err := twm.stackPane(twm.root) | |
| 327 | + | newPane, err := twm.stackedPaneManager.StackPane(twm.root) | |
| 328 | 328 | if err != nil { | |
| 329 | 329 | t.Fatalf("Failed to create stack: %v", err) | |
| 330 | 330 | } | |
| @@ -779,7 +779,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||
| 779 | 779 | t.Fatalf("Failed to create split for second stack: %v", err) | |
| 780 | 780 | } | |
| 781 | 781 | ||
| 782 | - | stackB, err := twm.stackPane(newPane) | |
| 782 | + | stackB, err := twm.stackedPaneManager.StackPane(newPane) | |
| 783 | 783 | if err != nil { | |
| 784 | 784 | t.Fatalf("Failed to create second stack: %v", err) | |
| 785 | 785 | } | |
| @@ -830,7 +830,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||
| 830 | 830 | } | |
| 831 | 831 | ||
| 832 | 832 | // Create second stack | |
| 833 | - | stackB, err := twm.stackPane(rightPane) | |
| 833 | + | stackB, err := twm.stackedPaneManager.StackPane(rightPane) | |
| 834 | 834 | if err != nil { | |
| 835 | 835 | t.Fatalf("Failed to create stack B: %v", err) | |
| 836 | 836 | } | |
| @@ -875,7 +875,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||
| 875 | 875 | } | |
| 876 | 876 | ||
| 877 | 877 | // Create stackB on right | |
| 878 | - | stackB, err := twm.stackPane(rightPane) | |
| 878 | + | stackB, err := twm.stackedPaneManager.StackPane(rightPane) | |
| 879 | 879 | if err != nil { | |
| 880 | 880 | t.Fatalf("Failed to create stackB: %v", err) | |
| 881 | 881 | } | |
| @@ -887,7 +887,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||
| 887 | 887 | t.Fatalf("Failed to split stackB: %v", err) | |
| 888 | 888 | } | |
| 889 | 889 | ||
| 890 | - | stackC, err := twm.stackPane(bottomPane) | |
| 890 | + | stackC, err := twm.stackedPaneManager.StackPane(bottomPane) | |
| 891 | 891 | if err != nil { | |
| 892 | 892 | t.Fatalf("Failed to create stackC: %v", err) | |
| 893 | 893 | } | |
| @@ -1031,7 +1031,7 @@ func TestUserScenarioCtrlPSCtrlPR(t *testing.T) { | |||
| 1031 | 1031 | ||
| 1032 | 1032 | // Step 2: Ctrl+P S - Stack the root pane (simulates user's Ctrl+P S) | |
| 1033 | 1033 | rootPaneContainer := twm.root.container | |
| 1034 | - | _, err := twm.stackPane(twm.root) | |
| 1034 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) | |
| 1035 | 1035 | if err != nil { | |
| 1036 | 1036 | t.Fatalf("Stack pane failed: %v", err) | |
| 1037 | 1037 | } | |
| @@ -1114,7 +1114,7 @@ func TestUserScenarioCtrlPSCtrlPR(t *testing.T) { | |||
| 1114 | 1114 | } | |
| 1115 | 1115 | ||
| 1116 | 1116 | // Step 2: Ctrl+P S - Stack the root pane | |
| 1117 | - | _, err := twm.stackPane(twm.root) | |
| 1117 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) | |
| 1118 | 1118 | if err != nil { | |
| 1119 | 1119 | t.Fatalf("Stack pane failed: %v", err) | |
| 1120 | 1120 | } | |
| @@ -1272,7 +1272,7 @@ func TestSiblingPromotionFix(t *testing.T) { | |||
| 1272 | 1272 | // When Target is closed, Stack gets promoted to replace Split | |
| 1273 | 1273 | ||
| 1274 | 1274 | // Step 1: Create a stack | |
| 1275 | - | _, err := twm.stackPane(twm.root) | |
| 1275 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) | |
| 1276 | 1276 | if err != nil { | |
| 1277 | 1277 | t.Fatalf("Stack creation failed: %v", err) | |
| 1278 | 1278 | } | |
| @@ -92,7 +92,7 @@ func (twm *TestWorkspaceManager) createTestStack(t *testing.T, numPanes int) *pa | |||||
| 92 | target := twm.root | 92 | target := twm.root | ||
| 93 | 93 | ||||
| 94 | // Stack the root pane to create the first stack with 2 panes | 94 | // Stack the root pane to create the first stack with 2 panes | ||
| 95 | - | newLeaf, err := twm.stackPane(target) | 95 | + | newLeaf, err := twm.stackedPaneManager.StackPane(target) |
| 96 | if err != nil { | 96 | if err != nil { | ||
| 97 | t.Fatalf("Failed to create initial stack: %v", err) | 97 | t.Fatalf("Failed to create initial stack: %v", err) | ||
| 98 | } | 98 | } | ||
| @@ -107,7 +107,7 @@ func (twm *TestWorkspaceManager) createTestStack(t *testing.T, numPanes int) *pa | |||||
| 107 | // Add additional panes to the existing stack | 107 | // Add additional panes to the existing stack | ||
| 108 | for i := 2; i < numPanes; i++ { | 108 | for i := 2; i < numPanes; i++ { | ||
| 109 | // Create a new pane by stacking one of the existing panes in the stack | 109 | // Create a new pane by stacking one of the existing panes in the stack | ||
| 110 | - | additionalPane, err := twm.stackPane(stackContainer.stackedPanes[0]) | 110 | + | additionalPane, err := twm.stackedPaneManager.StackPane(stackContainer.stackedPanes[0]) |
| 111 | if err != nil { | 111 | if err != nil { | ||
| 112 | t.Fatalf("Failed to stack pane %d: %v", i, err) | 112 | t.Fatalf("Failed to stack pane %d: %v", i, err) | ||
| 113 | } | 113 | } | ||
| @@ -324,7 +324,7 @@ func TestStackCreation(t *testing.T) { | |||||
| 324 | } | 324 | } | ||
| 325 | 325 | ||||
| 326 | // Create first stack | 326 | // Create first stack | ||
| 327 | - | newPane, err := twm.stackPane(twm.root) | 327 | + | newPane, err := twm.stackedPaneManager.StackPane(twm.root) |
| 328 | if err != nil { | 328 | if err != nil { | ||
| 329 | t.Fatalf("Failed to create stack: %v", err) | 329 | t.Fatalf("Failed to create stack: %v", err) | ||
| 330 | } | 330 | } | ||
| @@ -779,7 +779,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||||
| 779 | t.Fatalf("Failed to create split for second stack: %v", err) | 779 | t.Fatalf("Failed to create split for second stack: %v", err) | ||
| 780 | } | 780 | } | ||
| 781 | 781 | ||||
| 782 | - | stackB, err := twm.stackPane(newPane) | 782 | + | stackB, err := twm.stackedPaneManager.StackPane(newPane) |
| 783 | if err != nil { | 783 | if err != nil { | ||
| 784 | t.Fatalf("Failed to create second stack: %v", err) | 784 | t.Fatalf("Failed to create second stack: %v", err) | ||
| 785 | } | 785 | } | ||
| @@ -830,7 +830,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||||
| 830 | } | 830 | } | ||
| 831 | 831 | ||||
| 832 | // Create second stack | 832 | // Create second stack | ||
| 833 | - | stackB, err := twm.stackPane(rightPane) | 833 | + | stackB, err := twm.stackedPaneManager.StackPane(rightPane) |
| 834 | if err != nil { | 834 | if err != nil { | ||
| 835 | t.Fatalf("Failed to create stack B: %v", err) | 835 | t.Fatalf("Failed to create stack B: %v", err) | ||
| 836 | } | 836 | } | ||
| @@ -875,7 +875,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||||
| 875 | } | 875 | } | ||
| 876 | 876 | ||||
| 877 | // Create stackB on right | 877 | // Create stackB on right | ||
| 878 | - | stackB, err := twm.stackPane(rightPane) | 878 | + | stackB, err := twm.stackedPaneManager.StackPane(rightPane) |
| 879 | if err != nil { | 879 | if err != nil { | ||
| 880 | t.Fatalf("Failed to create stackB: %v", err) | 880 | t.Fatalf("Failed to create stackB: %v", err) | ||
| 881 | } | 881 | } | ||
| @@ -887,7 +887,7 @@ func TestMultipleStacksNavigation(t *testing.T) { | |||||
| 887 | t.Fatalf("Failed to split stackB: %v", err) | 887 | t.Fatalf("Failed to split stackB: %v", err) | ||
| 888 | } | 888 | } | ||
| 889 | 889 | ||||
| 890 | - | stackC, err := twm.stackPane(bottomPane) | 890 | + | stackC, err := twm.stackedPaneManager.StackPane(bottomPane) |
| 891 | if err != nil { | 891 | if err != nil { | ||
| 892 | t.Fatalf("Failed to create stackC: %v", err) | 892 | t.Fatalf("Failed to create stackC: %v", err) | ||
| 893 | } | 893 | } | ||
| @@ -1031,7 +1031,7 @@ func TestUserScenarioCtrlPSCtrlPR(t *testing.T) { | |||||
| 1031 | 1031 | ||||
| 1032 | // Step 2: Ctrl+P S - Stack the root pane (simulates user's Ctrl+P S) | 1032 | // Step 2: Ctrl+P S - Stack the root pane (simulates user's Ctrl+P S) | ||
| 1033 | rootPaneContainer := twm.root.container | 1033 | rootPaneContainer := twm.root.container | ||
| 1034 | - | _, err := twm.stackPane(twm.root) | 1034 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) |
| 1035 | if err != nil { | 1035 | if err != nil { | ||
| 1036 | t.Fatalf("Stack pane failed: %v", err) | 1036 | t.Fatalf("Stack pane failed: %v", err) | ||
| 1037 | } | 1037 | } | ||
| @@ -1114,7 +1114,7 @@ func TestUserScenarioCtrlPSCtrlPR(t *testing.T) { | |||||
| 1114 | } | 1114 | } | ||
| 1115 | 1115 | ||||
| 1116 | // Step 2: Ctrl+P S - Stack the root pane | 1116 | // Step 2: Ctrl+P S - Stack the root pane | ||
| 1117 | - | _, err := twm.stackPane(twm.root) | 1117 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) |
| 1118 | if err != nil { | 1118 | if err != nil { | ||
| 1119 | t.Fatalf("Stack pane failed: %v", err) | 1119 | t.Fatalf("Stack pane failed: %v", err) | ||
| 1120 | } | 1120 | } | ||
| @@ -1272,7 +1272,7 @@ func TestSiblingPromotionFix(t *testing.T) { | |||||
| 1272 | // When Target is closed, Stack gets promoted to replace Split | 1272 | // When Target is closed, Stack gets promoted to replace Split | ||
| 1273 | 1273 | ||||
| 1274 | // Step 1: Create a stack | 1274 | // Step 1: Create a stack | ||
| 1275 | - | _, err := twm.stackPane(twm.root) | 1275 | + | _, err := twm.stackedPaneManager.StackPane(twm.root) |
| 1276 | if err != nil { | 1276 | if err != nil { | ||
| 1277 | t.Fatalf("Stack creation failed: %v", err) | 1277 | t.Fatalf("Stack creation failed: %v", err) | ||
| 1278 | } | 1278 | } | ||