Fix panel resize hitbox and stale portal frame behavior (#114)
* ok * Drop GhosttyTerminalView changes from resize PR
This commit is contained in:
parent
fc1de08561
commit
3b50c6594c
2 changed files with 109 additions and 5 deletions
|
|
@ -23,9 +23,70 @@ final class WindowTerminalHostView: NSView {
|
|||
override var isOpaque: Bool { false }
|
||||
|
||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||
if shouldPassThroughToSplitDivider(at: point) {
|
||||
return nil
|
||||
}
|
||||
let hitView = super.hitTest(point)
|
||||
return hitView === self ? nil : hitView
|
||||
}
|
||||
|
||||
private func shouldPassThroughToSplitDivider(at point: NSPoint) -> Bool {
|
||||
guard let window else { return false }
|
||||
let windowPoint = convert(point, to: nil)
|
||||
guard let rootView = window.contentView else { return false }
|
||||
return Self.containsSplitDivider(at: windowPoint, in: rootView)
|
||||
}
|
||||
|
||||
private static func containsSplitDivider(at windowPoint: NSPoint, in view: NSView) -> Bool {
|
||||
guard !view.isHidden else { return false }
|
||||
|
||||
if let splitView = view as? NSSplitView {
|
||||
let pointInSplit = splitView.convert(windowPoint, from: nil)
|
||||
if splitView.bounds.contains(pointInSplit) {
|
||||
// Keep divider interactions reliable even when portal-hosted terminal frames
|
||||
// temporarily overlap divider edges during rapid layout churn.
|
||||
let expansion: CGFloat = 5
|
||||
let dividerCount = max(0, splitView.arrangedSubviews.count - 1)
|
||||
for dividerIndex in 0..<dividerCount {
|
||||
let first = splitView.arrangedSubviews[dividerIndex].frame
|
||||
let second = splitView.arrangedSubviews[dividerIndex + 1].frame
|
||||
let thickness = splitView.dividerThickness
|
||||
let dividerRect: NSRect
|
||||
if splitView.isVertical {
|
||||
guard first.width > 1, second.width > 1 else { continue }
|
||||
let x = max(0, first.maxX)
|
||||
dividerRect = NSRect(
|
||||
x: x,
|
||||
y: 0,
|
||||
width: thickness,
|
||||
height: splitView.bounds.height
|
||||
)
|
||||
} else {
|
||||
guard first.height > 1, second.height > 1 else { continue }
|
||||
let y = max(0, first.maxY)
|
||||
dividerRect = NSRect(
|
||||
x: 0,
|
||||
y: y,
|
||||
width: splitView.bounds.width,
|
||||
height: thickness
|
||||
)
|
||||
}
|
||||
let expandedDividerRect = dividerRect.insetBy(dx: -expansion, dy: -expansion)
|
||||
if expandedDividerRect.contains(pointInSplit) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for subview in view.subviews.reversed() {
|
||||
if containsSplitDivider(at: windowPoint, in: subview) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
|
@ -35,6 +96,7 @@ final class WindowTerminalPortal: NSObject {
|
|||
private weak var installedContainerView: NSView?
|
||||
private weak var installedReferenceView: NSView?
|
||||
private var installConstraints: [NSLayoutConstraint] = []
|
||||
private var hasDeferredFullSyncScheduled = false
|
||||
|
||||
private struct Entry {
|
||||
weak var hostedView: GhosttySurfaceScrollView?
|
||||
|
|
@ -226,8 +288,37 @@ final class WindowTerminalPortal: NSObject {
|
|||
|
||||
func synchronizeHostedViewForAnchor(_ anchorView: NSView) {
|
||||
pruneDeadEntries()
|
||||
guard let hostedId = hostedByAnchorId[ObjectIdentifier(anchorView)] else { return }
|
||||
synchronizeHostedView(withId: hostedId)
|
||||
let anchorId = ObjectIdentifier(anchorView)
|
||||
let primaryHostedId = hostedByAnchorId[anchorId]
|
||||
if let primaryHostedId {
|
||||
synchronizeHostedView(withId: primaryHostedId)
|
||||
}
|
||||
|
||||
// Failsafe: during aggressive divider drags/structural churn, one anchor can miss a
|
||||
// geometry callback while another fires. Reconcile all mapped hosted views so no stale
|
||||
// frame remains "stuck" onscreen until the next interaction.
|
||||
synchronizeAllHostedViews(excluding: primaryHostedId)
|
||||
scheduleDeferredFullSynchronizeAll()
|
||||
}
|
||||
|
||||
private func scheduleDeferredFullSynchronizeAll() {
|
||||
guard !hasDeferredFullSyncScheduled else { return }
|
||||
hasDeferredFullSyncScheduled = true
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.hasDeferredFullSyncScheduled = false
|
||||
self.synchronizeAllHostedViews(excluding: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeAllHostedViews(excluding hostedIdToSkip: ObjectIdentifier?) {
|
||||
guard ensureInstalled() else { return }
|
||||
pruneDeadEntries()
|
||||
let hostedIds = Array(entriesByHostedId.keys)
|
||||
for hostedId in hostedIds {
|
||||
if hostedId == hostedIdToSkip { continue }
|
||||
synchronizeHostedView(withId: hostedId)
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeHostedView(withId hostedId: ObjectIdentifier) {
|
||||
|
|
@ -261,12 +352,20 @@ final class WindowTerminalPortal: NSObject {
|
|||
|
||||
let frameInWindow = anchorView.convert(anchorView.bounds, to: nil)
|
||||
let frameInHost = hostView.convert(frameInWindow, from: nil)
|
||||
let hasFiniteFrame =
|
||||
frameInHost.origin.x.isFinite &&
|
||||
frameInHost.origin.y.isFinite &&
|
||||
frameInHost.size.width.isFinite &&
|
||||
frameInHost.size.height.isFinite
|
||||
let anchorHidden = Self.isHiddenOrAncestorHidden(anchorView)
|
||||
let tinyFrame = frameInHost.width <= 1 || frameInHost.height <= 1
|
||||
let outsideHostBounds = !frameInHost.intersects(hostView.bounds)
|
||||
let shouldHide =
|
||||
!entry.visibleInUI ||
|
||||
anchorHidden ||
|
||||
tinyFrame
|
||||
tinyFrame ||
|
||||
!hasFiniteFrame ||
|
||||
outsideHostBounds
|
||||
|
||||
let oldFrame = hostedView.frame
|
||||
#if DEBUG
|
||||
|
|
@ -301,7 +400,8 @@ final class WindowTerminalPortal: NSObject {
|
|||
dlog(
|
||||
"portal.hidden hosted=\(portalDebugToken(hostedView)) value=\(shouldHide ? 1 : 0) " +
|
||||
"visibleInUI=\(entry.visibleInUI ? 1 : 0) anchorHidden=\(anchorHidden ? 1 : 0) " +
|
||||
"tiny=\(tinyFrame ? 1 : 0) frame=\(portalDebugFrame(frameInHost))"
|
||||
"tiny=\(tinyFrame ? 1 : 0) finite=\(hasFiniteFrame ? 1 : 0) " +
|
||||
"outside=\(outsideHostBounds ? 1 : 0) frame=\(portalDebugFrame(frameInHost))"
|
||||
)
|
||||
#endif
|
||||
hostedView.isHidden = shouldHide
|
||||
|
|
@ -316,6 +416,10 @@ final class WindowTerminalPortal: NSObject {
|
|||
if anchor.window !== currentWindow || anchor.superview == nil {
|
||||
return hostedId
|
||||
}
|
||||
if let reference = installedReferenceView,
|
||||
!anchor.isDescendant(of: reference) {
|
||||
return hostedId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/bonsplit
vendored
2
vendor/bonsplit
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 2bd60ba40dc3350bd3c774b5f2de9f9b9c1b39fb
|
||||
Subproject commit 748d9c0fe12edebd5448b946ce2c23d7549cd073
|
||||
Loading…
Add table
Add a link
Reference in a new issue