-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix minimal mode workspace and pane tab clicks #2650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8f44462
ee88d7c
96fd15a
1111d4c
e2e96fe
19898e9
6c13568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,6 @@ | ||
| import AppKit | ||
| import ObjectiveC | ||
| #if DEBUG | ||
| import Bonsplit | ||
| #endif | ||
| import ObjectiveC | ||
|
|
||
| private var cmuxWindowTerminalPortalKey: UInt8 = 0 | ||
| private var cmuxWindowTerminalPortalCloseObserverKey: UInt8 = 0 | ||
|
|
@@ -146,6 +144,16 @@ final class WindowTerminalHostView: NSView { | |
| } | ||
|
|
||
| if isPointerEvent { | ||
| if shouldPassThroughToTitlebar(at: point) { | ||
| clearActiveDividerCursor(restoreArrow: false) | ||
| return nil | ||
| } | ||
|
|
||
| if shouldPassThroughToPaneTabBar(at: point, eventType: currentEvent?.type) { | ||
| clearActiveDividerCursor(restoreArrow: false) | ||
| return nil | ||
| } | ||
|
|
||
| if shouldPassThroughToSidebarResizer(at: point) { | ||
| clearActiveDividerCursor(restoreArrow: false) | ||
| return nil | ||
|
|
@@ -199,6 +207,48 @@ final class WindowTerminalHostView: NSView { | |
| return hitView === self ? nil : hitView | ||
| } | ||
|
|
||
| private func shouldPassThroughToTitlebar(at point: NSPoint) -> Bool { | ||
| guard let window else { return false } | ||
|
|
||
| // Window-level terminal portals sit above SwiftUI. In minimal mode the | ||
| // pane tab strip is intentionally rendered in the titlebar band, so | ||
| // terminal hosts must never claim hits there. | ||
| let windowPoint = convert(point, to: nil) | ||
| let nativeTitlebarHeight = window.frame.height - window.contentLayoutRect.height | ||
| let customTitlebarBandHeight = max(28, min(72, nativeTitlebarHeight)) | ||
| let interactionBandMinY = window.contentLayoutRect.maxY - customTitlebarBandHeight - 0.5 | ||
| return windowPoint.y >= interactionBandMinY | ||
| } | ||
|
|
||
| private func shouldPassThroughToPaneTabBar( | ||
| at point: NSPoint, | ||
| eventType: NSEvent.EventType? | ||
| ) -> Bool { | ||
| switch eventType { | ||
| case .leftMouseDown, .leftMouseUp, | ||
| .rightMouseDown, .rightMouseUp, | ||
| .otherMouseDown, .otherMouseUp: | ||
| break | ||
| default: | ||
| return false | ||
| } | ||
|
|
||
| let windowPoint = convert(point, to: nil) | ||
| let registryHit = window.map { | ||
| BonsplitTabBarHitRegionRegistry.containsWindowPoint(windowPoint, in: $0) | ||
| } ?? false | ||
| let result = registryHit || Self.hasUnderlyingBonsplitTabBarBackground(at: windowPoint, below: self) | ||
| #if DEBUG | ||
| if eventType == .leftMouseDown { | ||
| dlog( | ||
| "portal.term.passThroughTabBar wp=\(Int(windowPoint.x)),\(Int(windowPoint.y)) " + | ||
| "registry=\(registryHit ? 1 : 0) result=\(result ? 1 : 0)" | ||
| ) | ||
| } | ||
| #endif | ||
| return result | ||
| } | ||
|
Comment on lines
+223
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pane-tab hover is still blocked here. Line 227 only passes mouse down/up through to the underlying Bonsplit tab bar. That restores click/right-click, but hover/enter/exit/cursor updates for split-pane and left-pane tabs will still get swallowed by the terminal portal host. 🤖 Prompt for AI Agents |
||
|
|
||
| private func shouldPassThroughToSidebarResizer(at point: NSPoint) -> Bool { | ||
| // The sidebar resizer handle is implemented in SwiftUI. When terminals | ||
| // are portal-hosted, this AppKit host can otherwise sit above the handle | ||
|
|
@@ -382,6 +432,74 @@ final class WindowTerminalHostView: NSView { | |
| } | ||
| } | ||
|
|
||
| private static func hasBonsplitTabBarBackground(at windowPoint: NSPoint, in view: NSView) -> Bool { | ||
| guard !view.isHidden, view.alphaValue > 0 else { return false } | ||
|
|
||
| let className = NSStringFromClass(type(of: view)) | ||
| if className.contains("TabBarBackgroundNSView") { | ||
| let pointInView = view.convert(windowPoint, from: nil) | ||
| let inside = view.bounds.contains(pointInView) | ||
| #if DEBUG | ||
| let frameInWindow = view.convert(view.bounds, to: nil) | ||
| dlog( | ||
| "portal.term.bg.found cls=\(className) " + | ||
| "boundsInWin=\(Int(frameInWindow.minX)),\(Int(frameInWindow.minY)) " + | ||
| "\(Int(frameInWindow.width))x\(Int(frameInWindow.height)) " + | ||
| "wp=\(Int(windowPoint.x)),\(Int(windowPoint.y)) inside=\(inside ? 1 : 0)" | ||
| ) | ||
| #endif | ||
| if inside { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| for subview in view.subviews.reversed() { | ||
| if hasBonsplitTabBarBackground(at: windowPoint, in: subview) { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| private static func hasUnderlyingBonsplitTabBarBackground( | ||
| at windowPoint: NSPoint, | ||
| below portalHost: NSView | ||
| ) -> Bool { | ||
| if let container = portalHost.superview, | ||
| let hostIndex = container.subviews.firstIndex(of: portalHost) { | ||
| for sibling in container.subviews[..<hostIndex].reversed() { | ||
| guard !sibling.isHidden, sibling.alphaValue > 0 else { continue } | ||
| // Minimal mode lets the pane tab strip render into the titlebar band, | ||
| // so the immediate sibling container may not contain the click even | ||
| // though a descendant tab-bar backing view does. | ||
| if hasBonsplitTabBarBackground(at: windowPoint, in: sibling) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| guard let window = portalHost.window, | ||
| let rootView = window.contentView else { | ||
| return false | ||
| } | ||
| return hasBonsplitTabBarBackground(at: windowPoint, in: rootView) | ||
| } | ||
|
|
||
| #if DEBUG | ||
| static func hasBonsplitTabBarBackgroundForTesting(at windowPoint: NSPoint, in view: NSView) -> Bool { | ||
| hasBonsplitTabBarBackground(at: windowPoint, in: view) | ||
| } | ||
|
|
||
| static func hasUnderlyingBonsplitTabBarBackgroundForTesting( | ||
| at windowPoint: NSPoint, | ||
| below portalHost: NSView | ||
| ) -> Bool { | ||
| hasUnderlyingBonsplitTabBarBackground(at: windowPoint, below: portalHost) | ||
| } | ||
| #endif | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicated tab bar detection logic across portal classesLow Severity
Additional Locations (2)Reviewed by Cursor Bugbot for commit e2e96fe. Configure here. |
||
|
|
||
| #if DEBUG | ||
| private func logDragRouteDecision( | ||
| passThrough: Bool, | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hover still won’t reach pane tabs.
Because Line 705 only whitelists mouse down/up events, split-pane and left-pane tab bars under the browser portal still won’t receive move/enter/exit/cursor updates. Click/right-click will work again, but hover highlight and pointer-state changes stay intercepted by the host.
🤖 Prompt for AI Agents