mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-06-14 19:38:39 +03:00

The resizeIncrements property is only modified when the cell size of the focused window changes. If two splits have the same cell size then the property is not modified when focusing between the two splits.
188 lines
7.5 KiB
Swift
188 lines
7.5 KiB
Swift
import SwiftUI
|
|
import GhosttyKit
|
|
|
|
struct PrimaryView: View {
|
|
@ObservedObject var ghostty: Ghostty.AppState
|
|
|
|
// We need access to our app delegate to know if we're quitting or not.
|
|
// Make sure to use `@ObservedObject` so we can keep track of `appDelegate.confirmQuit`.
|
|
@ObservedObject var appDelegate: AppDelegate
|
|
|
|
// We need this to report back up the app controller which surface in this view is focused.
|
|
let focusedSurfaceWrapper: FocusedSurfaceWrapper
|
|
|
|
// If this is set, this is the base configuration that we build our surface out of.
|
|
let baseConfig: Ghostty.SurfaceConfiguration?
|
|
|
|
// We need access to our window to know if we're the key window to determine
|
|
// if we show the quit confirmation or not.
|
|
var window: NSWindow
|
|
|
|
// This handles non-native fullscreen
|
|
@State private var fullScreen = FullScreenHandler()
|
|
|
|
// This seems like a crutch after switching from SwiftUI to AppKit lifecycle.
|
|
@FocusState private var focused: Bool
|
|
|
|
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
|
|
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
|
|
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
|
|
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
|
|
|
|
// The title for our window
|
|
private var title: String {
|
|
var title = "👻"
|
|
|
|
if let surfaceTitle = surfaceTitle {
|
|
if (surfaceTitle.count > 0) {
|
|
title = surfaceTitle
|
|
}
|
|
}
|
|
|
|
if let zoomedSplit = zoomedSplit {
|
|
if zoomedSplit {
|
|
title = "🔍 " + title
|
|
}
|
|
}
|
|
|
|
return title
|
|
}
|
|
|
|
var body: some View {
|
|
switch ghostty.readiness {
|
|
case .loading:
|
|
Text("Loading")
|
|
case .error:
|
|
ErrorView()
|
|
case .ready:
|
|
let center = NotificationCenter.default
|
|
let gotoTab = center.publisher(for: Ghostty.Notification.ghosttyGotoTab)
|
|
let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen)
|
|
|
|
VStack(spacing: 0) {
|
|
// If we're running in debug mode we show a warning so that users
|
|
// know that performance will be degraded.
|
|
if (ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG) {
|
|
DebugBuildWarningView()
|
|
}
|
|
|
|
Ghostty.TerminalSplit(onClose: Self.closeWindow, baseConfig: self.baseConfig)
|
|
.ghosttyApp(ghostty.app!)
|
|
.ghosttyConfig(ghostty.config!)
|
|
.onReceive(gotoTab) { onGotoTab(notification: $0) }
|
|
.onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) }
|
|
.focused($focused)
|
|
.onAppear { self.focused = true }
|
|
.onChange(of: focusedSurface) { newValue in
|
|
self.focusedSurfaceWrapper.surface = newValue?.surface
|
|
}
|
|
.onChange(of: title) { newValue in
|
|
// We need to handle this manually because we are using AppKit lifecycle
|
|
// so navigationTitle no longer works.
|
|
self.window.title = newValue
|
|
}
|
|
.onChange(of: cellSize) { newValue in
|
|
guard let size = newValue else { return }
|
|
self.window.contentResizeIncrements = size
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static func closeWindow() {
|
|
guard let currentWindow = NSApp.keyWindow else { return }
|
|
currentWindow.close()
|
|
}
|
|
|
|
private func onGotoTab(notification: SwiftUI.Notification) {
|
|
// Notification center indiscriminately sends to every subscriber (makes sense)
|
|
// but we only want to process this once. In order to process it once lets only
|
|
// handle it if we're the focused window.
|
|
guard self.window.isKeyWindow else { return }
|
|
|
|
// Get the tab index from the notification
|
|
guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
|
|
guard let tabIndex = tabIndexAny as? Int32 else { return }
|
|
|
|
guard let windowController = window.windowController else { return }
|
|
guard let tabGroup = windowController.window?.tabGroup else { return }
|
|
let tabbedWindows = tabGroup.windows
|
|
|
|
// This will be the index we want to actual go to
|
|
let finalIndex: Int
|
|
|
|
// An index that is invalid is used to signal some special values.
|
|
if (tabIndex <= 0) {
|
|
guard let selectedWindow = tabGroup.selectedWindow else { return }
|
|
guard let selectedIndex = tabbedWindows.firstIndex(where: { $0 == selectedWindow }) else { return }
|
|
|
|
if (tabIndex == GHOSTTY_TAB_PREVIOUS.rawValue) {
|
|
finalIndex = selectedIndex - 1
|
|
} else if (tabIndex == GHOSTTY_TAB_NEXT.rawValue) {
|
|
finalIndex = selectedIndex + 1
|
|
} else {
|
|
return
|
|
}
|
|
} else {
|
|
// Tabs are 0-indexed here, so we subtract one from the key the user hit.
|
|
finalIndex = Int(tabIndex - 1)
|
|
}
|
|
|
|
guard finalIndex >= 0 && finalIndex < tabbedWindows.count else { return }
|
|
let targetWindow = tabbedWindows[finalIndex]
|
|
targetWindow.makeKeyAndOrderFront(nil)
|
|
}
|
|
|
|
private func onToggleFullscreen(notification: SwiftUI.Notification) {
|
|
// Just like in `onGotoTab`, we might receive this multiple times. But
|
|
// it's fine, because `toggleFullscreen` should only apply to the
|
|
// currently focused window.
|
|
guard self.window.isKeyWindow else { return }
|
|
|
|
// Check whether we use non-native fullscreen
|
|
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return }
|
|
guard let useNonNativeFullscreen = useNonNativeFullscreenAny as? ghostty_non_native_fullscreen_e else { return }
|
|
|
|
self.fullScreen.toggleFullscreen(window: window, nonNativeFullscreen: useNonNativeFullscreen)
|
|
// After toggling fullscreen we need to focus the terminal again.
|
|
self.focused = true
|
|
|
|
// For some reason focus always gets moved to the first split when
|
|
// toggling fullscreen, so we set it back to the correct one.
|
|
if let focusedSurface {
|
|
Ghostty.moveFocus(to: focusedSurface)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DebugBuildWarningView: View {
|
|
@State private var isPopover = false
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Spacer()
|
|
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundColor(.yellow)
|
|
|
|
Text("You're running a debug build of Ghostty! Performance will be degraded.")
|
|
.padding(.all, 8)
|
|
.popover(isPresented: $isPopover, arrowEdge: .bottom) {
|
|
Text("""
|
|
Debug builds of Ghostty are very slow and you may experience
|
|
performance problems. Debug builds are only recommended during
|
|
development.
|
|
""")
|
|
.padding(.all)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.background(Color(.windowBackgroundColor))
|
|
.frame(maxWidth: .infinity)
|
|
.onTapGesture {
|
|
isPopover = true
|
|
}
|
|
}
|
|
}
|