mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #423 from mitchellh/split-focus
macos: fade unfocused splits
This commit is contained in:
@ -183,6 +183,11 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if there are no neighbors
|
||||||
|
func isEmpty() -> Bool {
|
||||||
|
return self.previous == nil && self.next == nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +346,7 @@ extension Ghostty {
|
|||||||
let pubClose = center.publisher(for: Notification.ghosttyCloseSurface, object: leaf.surface)
|
let pubClose = center.publisher(for: Notification.ghosttyCloseSurface, object: leaf.surface)
|
||||||
let pubFocus = center.publisher(for: Notification.ghosttyFocusSplit, object: leaf.surface)
|
let pubFocus = center.publisher(for: Notification.ghosttyFocusSplit, object: leaf.surface)
|
||||||
|
|
||||||
SurfaceWrapper(surfaceView: leaf.surface)
|
SurfaceWrapper(surfaceView: leaf.surface, isSplit: !neighbors.isEmpty())
|
||||||
.onReceive(pub) { onNewSplit(notification: $0) }
|
.onReceive(pub) { onNewSplit(notification: $0) }
|
||||||
.onReceive(pubClose) { onClose(notification: $0) }
|
.onReceive(pubClose) { onClose(notification: $0) }
|
||||||
.onReceive(pubFocus) { onMoveFocus(notification: $0) }
|
.onReceive(pubFocus) { onMoveFocus(notification: $0) }
|
||||||
|
@ -38,6 +38,10 @@ extension Ghostty {
|
|||||||
// remains the same, the surface that is being rendered remains the same.
|
// remains the same, the surface that is being rendered remains the same.
|
||||||
@ObservedObject var surfaceView: SurfaceView
|
@ObservedObject var surfaceView: SurfaceView
|
||||||
|
|
||||||
|
// True if this surface is part of a split view. This is important to know so
|
||||||
|
// we know whether to dim the surface out of focus.
|
||||||
|
var isSplit: Bool = false
|
||||||
|
|
||||||
// Maintain whether our view has focus or not
|
// Maintain whether our view has focus or not
|
||||||
@FocusState private var surfaceFocus: Bool
|
@FocusState private var surfaceFocus: Bool
|
||||||
|
|
||||||
@ -49,73 +53,85 @@ extension Ghostty {
|
|||||||
private var hasFocus: Bool { surfaceFocus && windowFocus }
|
private var hasFocus: Bool { surfaceFocus && windowFocus }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// We use a GeometryReader to get the frame bounds so that our metal surface
|
ZStack {
|
||||||
// is up to date. See TerminalSurfaceView for why we don't use the NSView
|
// We use a GeometryReader to get the frame bounds so that our metal surface
|
||||||
// resize callback.
|
// is up to date. See TerminalSurfaceView for why we don't use the NSView
|
||||||
GeometryReader { geo in
|
// resize callback.
|
||||||
// We use these notifications to determine when the window our surface is
|
GeometryReader { geo in
|
||||||
// attached to is or is not focused.
|
// We use these notifications to determine when the window our surface is
|
||||||
let pubBecomeFocused = NotificationCenter.default.publisher(for: Notification.didBecomeFocusedSurface, object: surfaceView)
|
// attached to is or is not focused.
|
||||||
let pubBecomeKey = NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)
|
let pubBecomeFocused = NotificationCenter.default.publisher(for: Notification.didBecomeFocusedSurface, object: surfaceView)
|
||||||
let pubResign = NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)
|
let pubBecomeKey = NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)
|
||||||
|
let pubResign = NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)
|
||||||
|
|
||||||
Surface(view: surfaceView, hasFocus: hasFocus, size: geo.size)
|
Surface(view: surfaceView, hasFocus: hasFocus, size: geo.size)
|
||||||
.focused($surfaceFocus)
|
.focused($surfaceFocus)
|
||||||
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
||||||
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
||||||
.onReceive(pubBecomeKey) { notification in
|
.onReceive(pubBecomeKey) { notification in
|
||||||
guard let window = notification.object as? NSWindow else { return }
|
guard let window = notification.object as? NSWindow else { return }
|
||||||
guard let surfaceWindow = surfaceView.window else { return }
|
guard let surfaceWindow = surfaceView.window else { return }
|
||||||
windowFocus = surfaceWindow == window
|
windowFocus = surfaceWindow == window
|
||||||
}
|
|
||||||
.onReceive(pubResign) { notification in
|
|
||||||
guard let window = notification.object as? NSWindow else { return }
|
|
||||||
guard let surfaceWindow = surfaceView.window else { return }
|
|
||||||
if (surfaceWindow == window) {
|
|
||||||
windowFocus = false
|
|
||||||
}
|
}
|
||||||
}
|
.onReceive(pubResign) { notification in
|
||||||
.onReceive(pubBecomeFocused) { notification in
|
guard let window = notification.object as? NSWindow else { return }
|
||||||
// We only want to run this on older macOS versions where the .focused
|
guard let surfaceWindow = surfaceView.window else { return }
|
||||||
// method doesn't work properly. See the dispatch of this notification
|
if (surfaceWindow == window) {
|
||||||
// for more information.
|
windowFocus = false
|
||||||
if #available(macOS 13, *) { return }
|
}
|
||||||
|
}
|
||||||
|
.onReceive(pubBecomeFocused) { notification in
|
||||||
|
// We only want to run this on older macOS versions where the .focused
|
||||||
|
// method doesn't work properly. See the dispatch of this notification
|
||||||
|
// for more information.
|
||||||
|
if #available(macOS 13, *) { return }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
surfaceFocus = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear() {
|
|
||||||
// Welcome to the SwiftUI bug house of horrors. On macOS 12 (at least
|
|
||||||
// 12.5.1, didn't test other versions), the order in which the view
|
|
||||||
// is added to the window hierarchy is such that $surfaceFocus is
|
|
||||||
// not set to true for the first surface in a window. As a result,
|
|
||||||
// new windows are key (they have window focus) but the terminal surface
|
|
||||||
// does not have surface until the user clicks. Bad!
|
|
||||||
//
|
|
||||||
// There is a very real chance that I am doing something wrong, but it
|
|
||||||
// works great as-is on macOS 13, so I've instead decided to make the
|
|
||||||
// older macOS hacky. A workaround is on initial appearance to "steal
|
|
||||||
// focus" under certain conditions that seem to imply we're in the
|
|
||||||
// screwy state.
|
|
||||||
if #available(macOS 13, *) {
|
|
||||||
// If we're on a more modern version of macOS, do nothing.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if #available(macOS 12, *) {
|
|
||||||
// On macOS 13, the view is attached to a window at this point,
|
|
||||||
// so this is one extra check that we're a new view and behaving odd.
|
|
||||||
guard surfaceView.window == nil else { return }
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
surfaceFocus = true
|
surfaceFocus = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear() {
|
||||||
|
// Welcome to the SwiftUI bug house of horrors. On macOS 12 (at least
|
||||||
|
// 12.5.1, didn't test other versions), the order in which the view
|
||||||
|
// is added to the window hierarchy is such that $surfaceFocus is
|
||||||
|
// not set to true for the first surface in a window. As a result,
|
||||||
|
// new windows are key (they have window focus) but the terminal surface
|
||||||
|
// does not have surface until the user clicks. Bad!
|
||||||
|
//
|
||||||
|
// There is a very real chance that I am doing something wrong, but it
|
||||||
|
// works great as-is on macOS 13, so I've instead decided to make the
|
||||||
|
// older macOS hacky. A workaround is on initial appearance to "steal
|
||||||
|
// focus" under certain conditions that seem to imply we're in the
|
||||||
|
// screwy state.
|
||||||
|
if #available(macOS 13, *) {
|
||||||
|
// If we're on a more modern version of macOS, do nothing.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if #available(macOS 12, *) {
|
||||||
|
// On macOS 13, the view is attached to a window at this point,
|
||||||
|
// so this is one extra check that we're a new view and behaving odd.
|
||||||
|
guard surfaceView.window == nil else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
surfaceFocus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// I don't know how older macOS versions behave but Ghostty only
|
// I don't know how older macOS versions behave but Ghostty only
|
||||||
// supports back to macOS 12 so its moot.
|
// supports back to macOS 12 so its moot.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.ghosttySurfaceView(surfaceView)
|
||||||
|
|
||||||
|
// If we're part of a split view and don't have focus, we put a semi-transparent
|
||||||
|
// rectangle above our view to make it look unfocused. We use "surfaceFocus"
|
||||||
|
// because we want to keep our focused surface dark even if we don't have window
|
||||||
|
// focus.
|
||||||
|
if (isSplit && !surfaceFocus) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(.white)
|
||||||
|
.opacity(0.15)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ghosttySurfaceView(surfaceView)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user