Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Sources/TerminalWindowPortal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,20 @@ final class WindowTerminalHostView: NSView {
in: self,
eventType: eventType
) else { return false }
return decision.result
guard decision.result else { return false }
return hostedTerminalHitView(at: point) == nil
}

private func hostedTerminalHitView(at point: NSPoint) -> NSView? {
for subview in subviews.reversed() {
guard let hostedView = subview as? GhosttySurfaceScrollView,
!hostedView.isHidden,
hostedView.alphaValue > 0,
hostedView.frame.contains(point) else { continue }

return hostedView.hitTest(point) ?? hostedView
}
return nil
}

private func shouldPassThroughToChrome(at point: NSPoint, eventType: NSEvent.EventType?) -> Bool {
Expand Down
48 changes: 48 additions & 0 deletions cmuxTests/TerminalAndGhosttyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,54 @@ final class WindowTerminalHostViewTests: XCTestCase {
)
}

func testHostViewKeepsTerminalTopRowClickableWhenTabStripRegionOverlapsContent() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 420, height: 260),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
defer { window.orderOut(nil) }
guard let contentView = window.contentView,
let container = contentView.superview else {
XCTFail("Expected window content container")
return
}

let hostFrame = container.convert(contentView.bounds, from: contentView)
let host = WindowTerminalHostView(frame: hostFrame)
host.autoresizingMask = [.width, .height]

let terminalFrame = host.bounds.insetBy(dx: 0, dy: 32)
let hostedView = makeHostedTerminalView(frame: terminalFrame)
host.addSubview(hostedView)
container.addSubview(host, positioned: .above, relativeTo: contentView)

let tabStripOverlap: CGFloat = 2
let terminalTopInContent = contentView.convert(hostedView.frame, from: host).maxY
let tabStrip = FakeTabBarBackgroundNSView(
frame: NSRect(
x: 0,
y: terminalTopInContent - tabStripOverlap,
width: contentView.bounds.width,
height: 44
)
)
tabStrip.autoresizingMask = [.width, .minYMargin]
contentView.addSubview(tabStrip)

let pointInHostedView = NSPoint(x: hostedView.bounds.midX, y: hostedView.bounds.maxY - 0.5)
let pointInWindow = hostedView.convert(pointInHostedView, to: nil)
let pointInHost = host.convert(pointInWindow, from: nil)
let event = makeMouseDownEvent(at: pointInWindow, window: window)

assertHitFallsInsideHostedTerminal(
host.performHitTest(at: pointInHost, currentEvent: event),
hostedView: hostedView,
message: "The absolute top row of terminal content should own mouse-down hit-testing even if chrome hit regions overlap it"
)
}

func testHostViewPassesThroughWhenNoTerminalSubviewIsHit() {
let host = WindowTerminalHostView(frame: NSRect(x: 0, y: 0, width: 200, height: 120))

Expand Down
Loading