mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Adopt @Observable in SwiftUI code
This moves off of ObservableObject and @Published over to @Observable, which is preferred as it allows views to observe changes on a per-property basis rather than an entire object at a time. I went through the app and tried to compare behavior where I could, and I couldn't spot any visual differences, but I am sure there's good chance I have missed something. This also requires updating the minimum macOS deployment target to 14.0. If that is unacceptable, I am happy just closing this PR.
This commit is contained in:
@ -784,7 +784,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 0.1;
|
MARKETING_VERSION = 0.1;
|
||||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
||||||
@ -953,7 +953,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 0.1;
|
MARKETING_VERSION = 0.1;
|
||||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty.debug;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty.debug;
|
||||||
@ -1006,7 +1006,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 0.1;
|
MARKETING_VERSION = 0.1;
|
||||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
||||||
|
@ -2,18 +2,18 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct Ghostty_iOSApp: App {
|
struct Ghostty_iOSApp: App {
|
||||||
@StateObject private var ghostty_app = Ghostty.App()
|
@State private var ghostty_app = Ghostty.App()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
iOS_GhosttyTerminal()
|
iOS_GhosttyTerminal()
|
||||||
.environmentObject(ghostty_app)
|
.environment(ghostty_app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct iOS_GhosttyTerminal: View {
|
struct iOS_GhosttyTerminal: View {
|
||||||
@EnvironmentObject private var ghostty_app: Ghostty.App
|
@Environment(Ghostty.App.self) private var ghostty_app: Ghostty.App
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@ -26,7 +26,7 @@ struct iOS_GhosttyTerminal: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct iOS_GhosttyInitView: View {
|
struct iOS_GhosttyInitView: View {
|
||||||
@EnvironmentObject private var ghostty_app: Ghostty.App
|
@Environment(Ghostty.App.self) private var ghostty_app: Ghostty.App
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -4,8 +4,8 @@ import OSLog
|
|||||||
import Sparkle
|
import Sparkle
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
|
@Observable
|
||||||
class AppDelegate: NSObject,
|
class AppDelegate: NSObject,
|
||||||
ObservableObject,
|
|
||||||
NSApplicationDelegate,
|
NSApplicationDelegate,
|
||||||
UNUserNotificationCenterDelegate,
|
UNUserNotificationCenterDelegate,
|
||||||
GhosttyAppDelegate
|
GhosttyAppDelegate
|
||||||
@ -99,7 +99,7 @@ class AppDelegate: NSObject,
|
|||||||
private var appearanceObserver: NSKeyValueObservation? = nil
|
private var appearanceObserver: NSKeyValueObservation? = nil
|
||||||
|
|
||||||
/// The custom app icon image that is currently in use.
|
/// The custom app icon image that is currently in use.
|
||||||
@Published private(set) var appIcon: NSImage? = nil {
|
private(set) var appIcon: NSImage? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
NSApplication.shared.applicationIconImage = appIcon
|
NSApplication.shared.applicationIconImage = appIcon
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ import OSLog
|
|||||||
// it. You have to yield secure input on application deactivation (because
|
// it. You have to yield secure input on application deactivation (because
|
||||||
// it'll affect other apps) and reacquire on reactivation, and every enable
|
// it'll affect other apps) and reacquire on reactivation, and every enable
|
||||||
// needs to be balanced with a disable.
|
// needs to be balanced with a disable.
|
||||||
class SecureInput : ObservableObject {
|
@Observable
|
||||||
|
class SecureInput {
|
||||||
static let shared = SecureInput()
|
static let shared = SecureInput()
|
||||||
|
|
||||||
private static let logger = Logger(
|
private static let logger = Logger(
|
||||||
@ -28,10 +29,11 @@ class SecureInput : ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The scoped objects and whether they're currently in focus.
|
// The scoped objects and whether they're currently in focus.
|
||||||
|
@ObservationIgnored
|
||||||
private var scoped: [ObjectIdentifier: Bool] = [:]
|
private var scoped: [ObjectIdentifier: Bool] = [:]
|
||||||
|
|
||||||
// This is set to true when we've successfully called EnableSecureInput.
|
// This is set to true when we've successfully called EnableSecureInput.
|
||||||
@Published private(set) var enabled: Bool = false
|
private(set) var enabled: Bool = false
|
||||||
|
|
||||||
// This is true if we want to enable secure input. We want to enable
|
// This is true if we want to enable secure input. We want to enable
|
||||||
// secure input if its enabled globally or any of the scoped objects are
|
// secure input if its enabled globally or any of the scoped objects are
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
protocol ConfigurationErrorsViewModel: ObservableObject {
|
protocol ConfigurationErrorsViewModel: AnyObject, Observable {
|
||||||
var errors: [String] { get set }
|
var errors: [String] { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigurationErrorsView<ViewModel: ConfigurationErrorsViewModel>: View {
|
struct ConfigurationErrorsView<ViewModel: ConfigurationErrorsViewModel>: View {
|
||||||
@ObservedObject var model: ViewModel
|
var model: ViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
// We need access to our app delegate to know if we're quitting or not.
|
// We need access to our app delegate to know if we're quitting or not.
|
||||||
@EnvironmentObject private var appDelegate: AppDelegate
|
@Environment(AppDelegate.self) private var appDelegate: AppDelegate
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -25,6 +25,7 @@ import GhosttyKit
|
|||||||
///
|
///
|
||||||
/// The primary idea of all the behaviors we don't implement here are that subclasses may not
|
/// The primary idea of all the behaviors we don't implement here are that subclasses may not
|
||||||
/// want these behaviors.
|
/// want these behaviors.
|
||||||
|
@Observable
|
||||||
class BaseTerminalController: NSWindowController,
|
class BaseTerminalController: NSWindowController,
|
||||||
NSWindowDelegate,
|
NSWindowDelegate,
|
||||||
TerminalViewDelegate,
|
TerminalViewDelegate,
|
||||||
@ -36,12 +37,13 @@ class BaseTerminalController: NSWindowController,
|
|||||||
let ghostty: Ghostty.App
|
let ghostty: Ghostty.App
|
||||||
|
|
||||||
/// The currently focused surface.
|
/// The currently focused surface.
|
||||||
|
@ObservationIgnored
|
||||||
var focusedSurface: Ghostty.SurfaceView? = nil {
|
var focusedSurface: Ghostty.SurfaceView? = nil {
|
||||||
didSet { syncFocusToSurfaceTree() }
|
didSet { syncFocusToSurfaceTree() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The surface tree for this window.
|
/// The surface tree for this window.
|
||||||
@Published var surfaceTree: Ghostty.SplitNode? = nil {
|
var surfaceTree: Ghostty.SplitNode? = nil {
|
||||||
didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) }
|
didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,21 +610,22 @@ class TerminalController: BaseTerminalController {
|
|||||||
syncAppearance(focusedSurface.derivedConfig)
|
syncAppearance(focusedSurface.derivedConfig)
|
||||||
|
|
||||||
// We also want to get notified of certain changes to update our appearance.
|
// We also want to get notified of certain changes to update our appearance.
|
||||||
focusedSurface.$derivedConfig
|
observeFocusedSurface(focusedSurface)
|
||||||
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
|
||||||
.store(in: &surfaceAppearanceCancellables)
|
|
||||||
focusedSurface.$backgroundColor
|
|
||||||
.sink { [weak self, weak focusedSurface] _ in self?.syncAppearanceOnPropertyChange(focusedSurface) }
|
|
||||||
.store(in: &surfaceAppearanceCancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAppearanceOnPropertyChange(_ surface: Ghostty.SurfaceView?) {
|
func observeFocusedSurface(_ focusedSurface: Ghostty.SurfaceView) {
|
||||||
guard let surface else { return }
|
/// Use Observable to observe properties that will invalidate the surface
|
||||||
DispatchQueue.main.async { [weak self, weak surface] in
|
/// appearance.
|
||||||
guard let surface else { return }
|
withObservationTracking {
|
||||||
|
_ = focusedSurface.derivedConfig
|
||||||
|
_ = focusedSurface.backgroundColor
|
||||||
|
} onChange: { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
guard self.focusedSurface == surface else { return }
|
DispatchQueue.main.async {
|
||||||
self.syncAppearance(surface.derivedConfig)
|
guard self.focusedSurface == focusedSurface else { return }
|
||||||
|
self.syncAppearance(focusedSurface.derivedConfig)
|
||||||
|
self.observeFocusedSurface(focusedSurface)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,18 +28,18 @@ protocol TerminalViewDelegate: AnyObject {
|
|||||||
/// The view model is a required implementation for TerminalView callers. This contains
|
/// The view model is a required implementation for TerminalView callers. This contains
|
||||||
/// the main state between the TerminalView caller and SwiftUI. This abstraction is what
|
/// the main state between the TerminalView caller and SwiftUI. This abstraction is what
|
||||||
/// allows AppKit to own most of the data in SwiftUI.
|
/// allows AppKit to own most of the data in SwiftUI.
|
||||||
protocol TerminalViewModel: ObservableObject {
|
protocol TerminalViewModel: AnyObject, Observable {
|
||||||
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
||||||
/// and children. This should be @Published.
|
/// and children.
|
||||||
var surfaceTree: Ghostty.SplitNode? { get set }
|
var surfaceTree: Ghostty.SplitNode? { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main terminal view. This terminal view supports splits.
|
/// The main terminal view. This terminal view supports splits.
|
||||||
struct TerminalView<ViewModel: TerminalViewModel>: View {
|
struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||||
@ObservedObject var ghostty: Ghostty.App
|
var ghostty: Ghostty.App
|
||||||
|
|
||||||
// The required view model
|
// The required view model
|
||||||
@ObservedObject var viewModel: ViewModel
|
@Bindable var viewModel: ViewModel
|
||||||
|
|
||||||
// An optional delegate to receive information about terminal changes.
|
// An optional delegate to receive information about terminal changes.
|
||||||
weak var delegate: (any TerminalViewDelegate)? = nil
|
weak var delegate: (any TerminalViewDelegate)? = nil
|
||||||
@ -86,9 +86,9 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) {
|
if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) {
|
||||||
DebugBuildWarningView()
|
DebugBuildWarningView()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ghostty.TerminalSplit(node: $viewModel.surfaceTree)
|
Ghostty.TerminalSplit(node: $viewModel.surfaceTree)
|
||||||
.environmentObject(ghostty)
|
.environment(ghostty)
|
||||||
.focused($focused)
|
.focused($focused)
|
||||||
.onAppear { self.focused = true }
|
.onAppear { self.focused = true }
|
||||||
.onChange(of: focusedSurface) { newValue in
|
.onChange(of: focusedSurface) { newValue in
|
||||||
|
@ -13,7 +13,8 @@ protocol GhosttyAppDelegate: AnyObject {
|
|||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
// IMPORTANT: THIS IS NOT DONE.
|
// IMPORTANT: THIS IS NOT DONE.
|
||||||
// This is a refactor/redo of Ghostty.AppState so that it supports both macOS and iOS
|
// This is a refactor/redo of Ghostty.AppState so that it supports both macOS and iOS
|
||||||
class App: ObservableObject {
|
@Observable
|
||||||
|
class App {
|
||||||
enum Readiness: String {
|
enum Readiness: String {
|
||||||
case loading, error, ready
|
case loading, error, ready
|
||||||
}
|
}
|
||||||
@ -22,16 +23,16 @@ extension Ghostty {
|
|||||||
weak var delegate: GhosttyAppDelegate?
|
weak var delegate: GhosttyAppDelegate?
|
||||||
|
|
||||||
/// The readiness value of the state.
|
/// The readiness value of the state.
|
||||||
@Published var readiness: Readiness = .loading
|
var readiness: Readiness = .loading
|
||||||
|
|
||||||
/// The global app configuration. This defines the app level configuration plus any behavior
|
/// The global app configuration. This defines the app level configuration plus any behavior
|
||||||
/// for new windows, tabs, etc. Note that when creating a new window, it may inherit some
|
/// for new windows, tabs, etc. Note that when creating a new window, it may inherit some
|
||||||
/// configuration (i.e. font size) from the previously focused window. This would override this.
|
/// configuration (i.e. font size) from the previously focused window. This would override this.
|
||||||
@Published private(set) var config: Config
|
private(set) var config: Config
|
||||||
|
|
||||||
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
||||||
/// in theory you can have multiple... I don't know why you would...
|
/// in theory you can have multiple... I don't know why you would...
|
||||||
@Published var app: ghostty_app_t? = nil {
|
var app: ghostty_app_t? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
guard let old = oldValue else { return }
|
guard let old = oldValue else { return }
|
||||||
ghostty_app_free(old)
|
ghostty_app_free(old)
|
||||||
|
@ -3,7 +3,7 @@ import GhosttyKit
|
|||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// Maps to a `ghostty_config_t` and the various operations on that.
|
/// Maps to a `ghostty_config_t` and the various operations on that.
|
||||||
class Config: ObservableObject {
|
class Config {
|
||||||
// The underlying C pointer to the Ghostty config structure. This
|
// The underlying C pointer to the Ghostty config structure. This
|
||||||
// should never be accessed directly. Any operations on this should
|
// should never be accessed directly. Any operations on this should
|
||||||
// be called from the functions on this or another class.
|
// be called from the functions on this or another class.
|
||||||
|
@ -195,9 +195,10 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Leaf: ObservableObject, Equatable, Hashable, Codable {
|
@Observable
|
||||||
|
class Leaf: Equatable, Hashable, Codable {
|
||||||
let app: ghostty_app_t
|
let app: ghostty_app_t
|
||||||
@Published var surface: SurfaceView
|
var surface: SurfaceView
|
||||||
|
|
||||||
weak var parent: SplitNode.Container?
|
weak var parent: SplitNode.Container?
|
||||||
|
|
||||||
@ -250,13 +251,14 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Container: ObservableObject, Equatable, Hashable, Codable {
|
@Observable
|
||||||
|
class Container: Equatable, Hashable, Codable {
|
||||||
let app: ghostty_app_t
|
let app: ghostty_app_t
|
||||||
let direction: SplitViewDirection
|
let direction: SplitViewDirection
|
||||||
|
|
||||||
@Published var topLeft: SplitNode
|
var topLeft: SplitNode
|
||||||
@Published var bottomRight: SplitNode
|
var bottomRight: SplitNode
|
||||||
@Published var split: CGFloat = 0.5
|
var split: CGFloat = 0.5
|
||||||
|
|
||||||
var resizeEvent: PassthroughSubject<Double, Never> = .init()
|
var resizeEvent: PassthroughSubject<Double, Never> = .init()
|
||||||
|
|
||||||
|
@ -294,11 +294,11 @@ extension Ghostty {
|
|||||||
|
|
||||||
/// This represents a split view that is in the horizontal or vertical split state.
|
/// This represents a split view that is in the horizontal or vertical split state.
|
||||||
private struct TerminalSplitContainer: View {
|
private struct TerminalSplitContainer: View {
|
||||||
@EnvironmentObject var ghostty: Ghostty.App
|
@Environment(Ghostty.App.self) var ghostty: Ghostty.App
|
||||||
|
|
||||||
let neighbors: SplitNode.Neighbors
|
let neighbors: SplitNode.Neighbors
|
||||||
@Binding var node: SplitNode?
|
@Binding var node: SplitNode?
|
||||||
@StateObject var container: SplitNode.Container
|
@Bindable var container: SplitNode.Container
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
SplitView(
|
SplitView(
|
||||||
|
@ -6,10 +6,10 @@ import GhosttyKit
|
|||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// InspectableSurface is a type of Surface view that allows an inspector to be attached.
|
/// InspectableSurface is a type of Surface view that allows an inspector to be attached.
|
||||||
struct InspectableSurface: View {
|
struct InspectableSurface: View {
|
||||||
@EnvironmentObject var ghostty: Ghostty.App
|
@Environment(Ghostty.App.self) var ghostty: Ghostty.App
|
||||||
|
|
||||||
/// Same as SurfaceWrapper, see the doc comments there.
|
/// Same as SurfaceWrapper, see the doc comments there.
|
||||||
@ObservedObject var surfaceView: SurfaceView
|
var surfaceView: SurfaceView
|
||||||
var isSplit: Bool = false
|
var isSplit: Bool = false
|
||||||
|
|
||||||
// Maintain whether our view has focus or not
|
// Maintain whether our view has focus or not
|
||||||
|
@ -5,7 +5,7 @@ import GhosttyKit
|
|||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// Render a terminal for the active app in the environment.
|
/// Render a terminal for the active app in the environment.
|
||||||
struct Terminal: View {
|
struct Terminal: View {
|
||||||
@EnvironmentObject private var ghostty: Ghostty.App
|
@Environment(Ghostty.App.self) private var ghostty: Ghostty.App
|
||||||
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
|
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -22,10 +22,10 @@ extension Ghostty {
|
|||||||
struct SurfaceForApp<Content: View>: View {
|
struct SurfaceForApp<Content: View>: View {
|
||||||
let content: ((SurfaceView) -> Content)
|
let content: ((SurfaceView) -> Content)
|
||||||
|
|
||||||
@StateObject private var surfaceView: SurfaceView
|
@State private var surfaceView: SurfaceView
|
||||||
|
|
||||||
init(_ app: ghostty_app_t, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) {
|
init(_ app: ghostty_app_t, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) {
|
||||||
_surfaceView = StateObject(wrappedValue: SurfaceView(app))
|
_surfaceView = State(wrappedValue: SurfaceView(app))
|
||||||
self.content = content
|
self.content = content
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ extension Ghostty {
|
|||||||
struct SurfaceWrapper: View {
|
struct SurfaceWrapper: View {
|
||||||
// The surface to create a view for. This must be created upstream. As long as this
|
// The surface to create a view for. This must be created upstream. As long as this
|
||||||
// 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
|
var surfaceView: SurfaceView
|
||||||
|
|
||||||
// True if this surface is part of a split view. This is important to know so
|
// 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.
|
// we know whether to dim the surface out of focus.
|
||||||
@ -54,10 +54,15 @@ extension Ghostty {
|
|||||||
|
|
||||||
#if canImport(AppKit)
|
#if canImport(AppKit)
|
||||||
// Observe SecureInput to detect when its enabled
|
// Observe SecureInput to detect when its enabled
|
||||||
@ObservedObject private var secureInput = SecureInput.shared
|
private var secureInput = SecureInput.shared
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
init(surfaceView: SurfaceView, isSplit: Bool = false) {
|
||||||
|
self.surfaceView = surfaceView
|
||||||
|
self.isSplit = isSplit
|
||||||
|
}
|
||||||
|
|
||||||
@EnvironmentObject private var ghostty: Ghostty.App
|
@Environment(Ghostty.App.self) private var ghostty: Ghostty.App
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
|
@ -5,55 +5,56 @@ import GhosttyKit
|
|||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// The NSView implementation for a terminal surface.
|
/// The NSView implementation for a terminal surface.
|
||||||
class SurfaceView: OSView, ObservableObject {
|
@Observable
|
||||||
|
class SurfaceView: OSView {
|
||||||
/// Unique ID per surface
|
/// Unique ID per surface
|
||||||
let uuid: UUID
|
let uuid: UUID
|
||||||
|
|
||||||
// The current title of the surface as defined by the pty. This can be
|
// The current title of the surface as defined by the pty. This can be
|
||||||
// changed with escape codes. This is public because the callbacks go
|
// changed with escape codes. This is public because the callbacks go
|
||||||
// to the app level and it is set from there.
|
// to the app level and it is set from there.
|
||||||
@Published private(set) var title: String = "👻"
|
private(set) var title: String = "👻"
|
||||||
|
|
||||||
// The current pwd of the surface as defined by the pty. This can be
|
// The current pwd of the surface as defined by the pty. This can be
|
||||||
// changed with escape codes.
|
// changed with escape codes.
|
||||||
@Published var pwd: String? = nil
|
var pwd: String? = nil
|
||||||
|
|
||||||
// The cell size of this surface. This is set by the core when the
|
// The cell size of this surface. This is set by the core when the
|
||||||
// surface is first created and any time the cell size changes (i.e.
|
// surface is first created and any time the cell size changes (i.e.
|
||||||
// when the font size changes). This is used to allow windows to be
|
// when the font size changes). This is used to allow windows to be
|
||||||
// resized in discrete steps of a single cell.
|
// resized in discrete steps of a single cell.
|
||||||
@Published var cellSize: NSSize = .zero
|
var cellSize: NSSize = .zero
|
||||||
|
|
||||||
// The health state of the surface. This currently only reflects the
|
// The health state of the surface. This currently only reflects the
|
||||||
// renderer health. In the future we may want to make this an enum.
|
// renderer health. In the future we may want to make this an enum.
|
||||||
@Published var healthy: Bool = true
|
var healthy: Bool = true
|
||||||
|
|
||||||
// Any error while initializing the surface.
|
// Any error while initializing the surface.
|
||||||
@Published var error: Error? = nil
|
var error: Error? = nil
|
||||||
|
|
||||||
// The hovered URL string
|
// The hovered URL string
|
||||||
@Published var hoverUrl: String? = nil
|
var hoverUrl: String? = nil
|
||||||
|
|
||||||
// The currently active key sequence. The sequence is not active if this is empty.
|
// The currently active key sequence. The sequence is not active if this is empty.
|
||||||
@Published var keySequence: [Ghostty.KeyEquivalent] = []
|
var keySequence: [Ghostty.KeyEquivalent] = []
|
||||||
|
|
||||||
// The time this surface last became focused. This is a ContinuousClock.Instant
|
// The time this surface last became focused. This is a ContinuousClock.Instant
|
||||||
// on supported platforms.
|
// on supported platforms.
|
||||||
@Published var focusInstant: ContinuousClock.Instant? = nil
|
var focusInstant: ContinuousClock.Instant? = nil
|
||||||
|
|
||||||
// Returns sizing information for the surface. This is the raw C
|
// Returns sizing information for the surface. This is the raw C
|
||||||
// structure because I'm lazy.
|
// structure because I'm lazy.
|
||||||
@Published var surfaceSize: ghostty_surface_size_s? = nil
|
var surfaceSize: ghostty_surface_size_s? = nil
|
||||||
|
|
||||||
// Whether the pointer should be visible or not
|
// Whether the pointer should be visible or not
|
||||||
@Published private(set) var pointerStyle: BackportPointerStyle = .default
|
private(set) var pointerStyle: BackportPointerStyle = .default
|
||||||
|
|
||||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||||
@Published private(set) var derivedConfig: DerivedConfig
|
private(set) var derivedConfig: DerivedConfig
|
||||||
|
|
||||||
/// The background color within the color palette of the surface. This is only set if it is
|
/// The background color within the color palette of the surface. This is only set if it is
|
||||||
/// dynamically updated. Otherwise, the background color is the default background color.
|
/// dynamically updated. Otherwise, the background color is the default background color.
|
||||||
@Published private(set) var backgroundColor: Color? = nil
|
private(set) var backgroundColor: Color? = nil
|
||||||
|
|
||||||
// An initial size to request for a window. This will only affect
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
@ -89,7 +90,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// True if the inspector should be visible
|
// True if the inspector should be visible
|
||||||
@Published var inspectorVisible: Bool = false {
|
var inspectorVisible: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if (oldValue && !inspectorVisible) {
|
if (oldValue && !inspectorVisible) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
|
@ -3,37 +3,38 @@ import GhosttyKit
|
|||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// The UIView implementation for a terminal surface.
|
/// The UIView implementation for a terminal surface.
|
||||||
class SurfaceView: UIView, ObservableObject {
|
@Observable
|
||||||
|
class SurfaceView: UIView {
|
||||||
/// Unique ID per surface
|
/// Unique ID per surface
|
||||||
let uuid: UUID
|
let uuid: UUID
|
||||||
|
|
||||||
// The current title of the surface as defined by the pty. This can be
|
// The current title of the surface as defined by the pty. This can be
|
||||||
// changed with escape codes. This is public because the callbacks go
|
// changed with escape codes. This is public because the callbacks go
|
||||||
// to the app level and it is set from there.
|
// to the app level and it is set from there.
|
||||||
@Published var title: String = "👻"
|
var title: String = "👻"
|
||||||
|
|
||||||
// The current pwd of the surface.
|
// The current pwd of the surface.
|
||||||
@Published var pwd: String? = nil
|
var pwd: String? = nil
|
||||||
|
|
||||||
// The cell size of this surface. This is set by the core when the
|
// The cell size of this surface. This is set by the core when the
|
||||||
// surface is first created and any time the cell size changes (i.e.
|
// surface is first created and any time the cell size changes (i.e.
|
||||||
// when the font size changes). This is used to allow windows to be
|
// when the font size changes). This is used to allow windows to be
|
||||||
// resized in discrete steps of a single cell.
|
// resized in discrete steps of a single cell.
|
||||||
@Published var cellSize: OSSize = .zero
|
var cellSize: OSSize = .zero
|
||||||
|
|
||||||
// The health state of the surface. This currently only reflects the
|
// The health state of the surface. This currently only reflects the
|
||||||
// renderer health. In the future we may want to make this an enum.
|
// renderer health. In the future we may want to make this an enum.
|
||||||
@Published var healthy: Bool = true
|
var healthy: Bool = true
|
||||||
|
|
||||||
// Any error while initializing the surface.
|
// Any error while initializing the surface.
|
||||||
@Published var error: Error? = nil
|
var error: Error? = nil
|
||||||
|
|
||||||
// The hovered URL
|
// The hovered URL
|
||||||
@Published var hoverUrl: String? = nil
|
var hoverUrl: String? = nil
|
||||||
|
|
||||||
// The time this surface last became focused. This is a ContinuousClock.Instant
|
// The time this surface last became focused. This is a ContinuousClock.Instant
|
||||||
// on supported platforms.
|
// on supported platforms.
|
||||||
@Published var focusInstant: ContinuousClock.Instant? = nil
|
var focusInstant: ContinuousClock.Instant? = nil
|
||||||
|
|
||||||
// Returns sizing information for the surface. This is the raw C
|
// Returns sizing information for the surface. This is the raw C
|
||||||
// structure because I'm lazy.
|
// structure because I'm lazy.
|
||||||
|
Reference in New Issue
Block a user