Use notifications to deal with NSScrollPocket
parent
bbaee5e0a0
commit
d678e2e305
|
|
@ -102,6 +102,19 @@ class SurfaceScrollView: NSView {
|
|||
self?.handleLiveScroll()
|
||||
})
|
||||
|
||||
// Listen for frame change events. See the docstring for
|
||||
// handleFrameChange for why this is necessary.
|
||||
observers.append(NotificationCenter.default.addObserver(
|
||||
forName: NSView.frameDidChangeNotification,
|
||||
object: nil,
|
||||
// Since this observer is used to immediately override the event
|
||||
// that produced the notification, we let it run synchronously on
|
||||
// the posting thread.
|
||||
queue: nil
|
||||
) { [weak self] notification in
|
||||
self?.handleFrameChange(notification)
|
||||
})
|
||||
|
||||
// Listen for derived config changes to update scrollbar settings live
|
||||
surfaceView.$derivedConfig
|
||||
.sink { [weak self] _ in
|
||||
|
|
@ -135,20 +148,6 @@ class SurfaceScrollView: NSView {
|
|||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
// The SwiftUI ScrollView host likes to add its own styling overlays to
|
||||
// the titlebar area, which are incompatible with the hidden titlebar
|
||||
// style. They won't be present when the app is first opened, but will
|
||||
// appear when creating splits or cycling fullscreen. There's no public
|
||||
// way to disable them in AppKit, so we just have to play whack-a-mole.
|
||||
// See https://developer.apple.com/forums/thread/798392.
|
||||
if window is HiddenTitlebarTerminalWindow {
|
||||
for view in scrollView.subviews {
|
||||
if view.className.contains("NSScrollPocket") {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill entire bounds with scroll view
|
||||
scrollView.frame = bounds
|
||||
|
||||
|
|
@ -283,6 +282,49 @@ class SurfaceScrollView: NSView {
|
|||
scrollView.reflectScrolledClipView(scrollView.contentView)
|
||||
}
|
||||
|
||||
/// Handles a change in the frame of NSScrollPocket styling overlays
|
||||
///
|
||||
/// NSScrollView instances are set up with a subview hierarchy which, as far
|
||||
/// as I can tell, is intended to add a blur effect to any part of a scroll
|
||||
/// view that lies under the titlebar, presumably to complement a titlebar
|
||||
/// using liquid glass transparency. This doesn't work correctly with our
|
||||
/// hidden titlebar style, which does have a titlebar container, albeit
|
||||
/// hidden. The styling overlays don't care and size themselves to this
|
||||
/// container, creating a blurry, transparent field that clips the top of
|
||||
/// the surface view.
|
||||
///
|
||||
/// With other titlebar styles, these views always have zero frame size,
|
||||
/// presumably because there is no overlap between the scroll view and the
|
||||
/// titlebar container.
|
||||
///
|
||||
/// In native fullscreen, the titlebar detaches from the window and these
|
||||
/// views seem to work a bit differently, taking non-zero sizes for all
|
||||
/// styles without creating any problems.
|
||||
///
|
||||
/// To handle this in a way that minimizes the difference between how the
|
||||
/// hidden titlebar and other window styles behave, we do as follows: If we
|
||||
/// have the hidden titlebar style and we're not fullscreen, we listen to
|
||||
/// frame changes on NSScrollPocket-related objects in scrollView.subviews,
|
||||
/// and reset their frame to zero.
|
||||
///
|
||||
/// See also https://developer.apple.com/forums/thread/798392.
|
||||
private func handleFrameChange(_ notification: Notification) {
|
||||
guard let window = window as? HiddenTitlebarTerminalWindow else { return }
|
||||
guard !window.styleMask.contains(.fullScreen) else { return }
|
||||
guard let view = notification.object as? NSView else { return }
|
||||
guard view.className.contains("NSScrollPocket") else { return }
|
||||
guard scrollView.subviews.contains(view) else { return }
|
||||
// These guards to avoid an infinite loop don't actually seem necessary.
|
||||
// The number of times we reach this point during any given event (e.g.,
|
||||
// creating a split) is the same either way. We keep them anyway out of
|
||||
// an abundance of caution.
|
||||
view.postsFrameChangedNotifications = false
|
||||
view.frame = NSRect(x: 0, y: 0, width: 0, height: 0)
|
||||
view.postsFrameChangedNotifications = true
|
||||
}
|
||||
|
||||
// MARK: Calculations
|
||||
|
||||
/// Calculate the appropriate document view height given a scrollbar state
|
||||
private func documentHeight(_ scrollbar: Ghostty.Action.Scrollbar?) -> CGFloat {
|
||||
let contentHeight = scrollView.contentSize.height
|
||||
|
|
|
|||
Loading…
Reference in New Issue