diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index affcf56b3..77399b2e8 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + A507573E299FF33C009D7DC7 /* TerminalSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */; }; A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; }; A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30534299BEAAA0047F10C /* GhosttyApp.swift */; }; A5B30537299BEAAA0047F10C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30536299BEAAA0047F10C /* ContentView.swift */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSurfaceView.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; }; A5B30534299BEAAA0047F10C /* GhosttyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyApp.swift; sourceTree = ""; }; @@ -43,6 +45,7 @@ A5B30534299BEAAA0047F10C /* GhosttyApp.swift */, A5B30536299BEAAA0047F10C /* ContentView.swift */, A535B9D9299C569B0017E2E4 /* ErrorView.swift */, + A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */, ); path = Sources; sourceTree = ""; @@ -150,6 +153,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A507573E299FF33C009D7DC7 /* TerminalSurfaceView.swift in Sources */, A5B30537299BEAAA0047F10C /* ContentView.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */, diff --git a/macos/Sources/GhosttyApp.swift b/macos/Sources/GhosttyApp.swift index e9429df57..11f6a80e7 100644 --- a/macos/Sources/GhosttyApp.swift +++ b/macos/Sources/GhosttyApp.swift @@ -20,7 +20,7 @@ struct GhosttyApp: App { case .error: ErrorView() case .ready: - ContentView() + TerminalSurfaceView() } } } diff --git a/macos/Sources/TerminalSurfaceView.swift b/macos/Sources/TerminalSurfaceView.swift new file mode 100644 index 000000000..dcff76146 --- /dev/null +++ b/macos/Sources/TerminalSurfaceView.swift @@ -0,0 +1,59 @@ +import SwiftUI + +/// A surface is terminology in Ghostty for a terminal surface, or a place where a terminal is actually drawn +/// and interacted with. The word "surface" is used because a surface may represent a window, a tab, +/// a split, a small preview pane, etc. It is ANYTHING that has a terminal drawn to it. +/// +/// We just wrap an AppKit NSView here at the moment so that we can behave as low level as possible +/// since that is what the Metal renderer in Ghostty expects. In the future, it may make more sense to +/// wrap an MTKView and use that, but for legacy reasons we didn't do that to begin with. +struct TerminalSurfaceView: NSViewRepresentable { + @StateObject private var state = TerminalSurfaceState() + + func makeNSView(context: Context) -> TerminalSurfaceView_Real { + // We need the view as part of the state to be created previously because + // the view is sent to the Ghostty API so that it can manipulate it + // directly since we draw on a render thread. + return state.view; + } + + func updateNSView(_ view: TerminalSurfaceView_Real, context: Context) { + // Nothing we need to do here. + } +} + +/// The state for the terminal surface view. +class TerminalSurfaceState: ObservableObject { + var view: TerminalSurfaceView_Real; + + init() { + view = TerminalSurfaceView_Real() + } +} + +// The actual NSView implementation for the terminal surface. +class TerminalSurfaceView_Real: NSView { + // We need to support being a first responder so that we can get input events + override var acceptsFirstResponder: Bool { return true } + + override func draw(_ dirtyRect: NSRect) { + print("DRAW: \(dirtyRect)") + NSColor.green.setFill() + dirtyRect.fill() + super.draw(dirtyRect) + } + + override func mouseDown(with event: NSEvent) { + print("Mouse down: \(event)") + } + + override func keyDown(with event: NSEvent) { + print("Key down: \(event)") + } +} + +struct TerminalSurfaceView_Previews: PreviewProvider { + static var previews: some View { + TerminalSurfaceView() + } +}