diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index dba3ca632..9ec41f95d 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -153,7 +153,8 @@ extension Ghostty { size: surfaceSize, overlay: ghostty.config.resizeOverlay, position: ghostty.config.resizeOverlayPosition, - duration: ghostty.config.resizeOverlayDuration) + duration: ghostty.config.resizeOverlayDuration, + focusInstant: surfaceView.focusInstant) } } @@ -274,19 +275,40 @@ extension Ghostty { let overlay: Ghostty.Config.ResizeOverlay let position: Ghostty.Config.ResizeOverlayPosition let duration: UInt + let focusInstant: Any? // This is the last size that we processed. This is how we handle our // timer state. @State var lastSize: CGSize? = nil + // Ready is set to true after a short delay. This avoids some of the + // challenges of initial view sizing from SwiftUI. + @State var ready: Bool = false + // Fixed value set based on personal taste. private let padding: CGFloat = 5 // This computed boolean is set to true when the overlay should be hidden. private var hidden: Bool { + // If we aren't ready yet then we wait... + if (!ready) { return true; } + // Hidden if we already processed this size. if (lastSize == geoSize) { return true; } - + + // If we were focused recently we hide it as well. This avoids showing + // the resize overlay when SwiftUI is lazily resizing. + if #available(macOS 13, iOS 16, *) { + if let instant = focusInstant as? ContinuousClock.Instant { + let d = instant.duration(to: ContinuousClock.now) + if (d < .milliseconds(500)) { + // Avoid this size completely. + lastSize = geoSize + return true; + } + } + } + // Hidden depending on overlay config switch (overlay) { case .never: return true; @@ -327,11 +349,24 @@ extension Ghostty { } .allowsHitTesting(false) .opacity(hidden ? 0 : 1) + .task { + // Sleep chosen arbitrarily... a better long term solution would be to detect + // when the size stabilizes (coalesce a value) for the first time and then after + // that show the resize overlay consistently. + try? await Task.sleep(nanoseconds: 500 * 1_000_000) + ready = true + } .task(id: geoSize) { // By ID-ing the task on the geoSize, we get the task to restart if our // geoSize changes. This also ensures that future resize overlays are shown // properly. - try? await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000) + + // We only sleep if we're ready. If we're not ready then we want to set + // our last size right away to avoid a flash. + if (ready) { + try? await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000) + } + lastSize = geoSize } } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 3d6c17750..1a68a2ea0 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -29,7 +29,11 @@ extension Ghostty { // The hovered URL string @Published var hoverUrl: String? = nil - + + // The time this surface last became focused. This is a ContinuousClock.Instant + // on supported platforms. + @Published var focusInstant: Any? = nil + // An initial size to request for a window. This will only affect // then the view is moved to a new window. var initialSize: NSSize? = nil @@ -208,6 +212,13 @@ extension Ghostty { guard self.focused != focused else { return } self.focused = focused ghostty_surface_set_focus(surface, focused) + + // On macOS 13+ we can store our continuous clock... + if #available(macOS 13, iOS 16, *) { + if (focused) { + focusInstant = ContinuousClock.now + } + } } func sizeDidChange(_ size: CGSize) { diff --git a/macos/Sources/Ghostty/SurfaceView_UIKit.swift b/macos/Sources/Ghostty/SurfaceView_UIKit.swift index a9739d7d4..ac5270c6a 100644 --- a/macos/Sources/Ghostty/SurfaceView_UIKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_UIKit.swift @@ -28,6 +28,10 @@ extension Ghostty { // The hovered URL @Published var hoverUrl: String? = nil + // The time this surface last became focused. This is a ContinuousClock.Instant + // on supported platforms. + @Published var focusInstant: Any? = nil + // Returns sizing information for the surface. This is the raw C // structure because I'm lazy. var surfaceSize: ghostty_surface_size_s? { @@ -67,6 +71,13 @@ extension Ghostty { func focusDidChange(_ focused: Bool) { guard let surface = self.surface else { return } ghostty_surface_set_focus(surface, focused) + + // On macOS 13+ we can store our continuous clock... + if #available(macOS 13, iOS 16, *) { + if (focused) { + focusInstant = ContinuousClock.now + } + } } func sizeDidChange(_ size: CGSize) {