Use notifications to deal with NSScrollPocket
parent
bbaee5e0a0
commit
d678e2e305
|
|
@ -102,6 +102,19 @@ class SurfaceScrollView: NSView {
|
||||||
self?.handleLiveScroll()
|
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
|
// Listen for derived config changes to update scrollbar settings live
|
||||||
surfaceView.$derivedConfig
|
surfaceView.$derivedConfig
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
|
|
@ -134,20 +147,6 @@ class SurfaceScrollView: NSView {
|
||||||
|
|
||||||
override func layout() {
|
override func layout() {
|
||||||
super.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
|
// Fill entire bounds with scroll view
|
||||||
scrollView.frame = bounds
|
scrollView.frame = bounds
|
||||||
|
|
@ -283,6 +282,49 @@ class SurfaceScrollView: NSView {
|
||||||
scrollView.reflectScrolledClipView(scrollView.contentView)
|
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
|
/// Calculate the appropriate document view height given a scrollbar state
|
||||||
private func documentHeight(_ scrollbar: Ghostty.Action.Scrollbar?) -> CGFloat {
|
private func documentHeight(_ scrollbar: Ghostty.Action.Scrollbar?) -> CGFloat {
|
||||||
let contentHeight = scrollView.contentSize.height
|
let contentHeight = scrollView.contentSize.height
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue