mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-06-09 08:58:39 +03:00
124 lines
4.6 KiB
Swift
124 lines
4.6 KiB
Swift
import OSLog
|
|
import SwiftUI
|
|
import GhosttyKit
|
|
|
|
/// 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: TerminalSurfaceView_Real
|
|
|
|
init(app: ghostty_app_t) {
|
|
self._state = StateObject(wrappedValue: TerminalSurfaceView_Real(app))
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
func updateNSView(_ view: TerminalSurfaceView_Real, context: Context) {
|
|
// Nothing we need to do here.
|
|
}
|
|
}
|
|
|
|
// The actual NSView implementation for the terminal surface.
|
|
class TerminalSurfaceView_Real: NSView, ObservableObject {
|
|
// We need to support being a first responder so that we can get input events
|
|
override var acceptsFirstResponder: Bool { return true }
|
|
|
|
// I don't thikn we need this but this lets us know we should redraw our layer
|
|
// so we'll use that to tell ghostty to refresh.
|
|
override var wantsUpdateLayer: Bool { return true }
|
|
|
|
private var surface: ghostty_surface_t? = nil
|
|
private var error: Error? = nil
|
|
|
|
init(_ app: ghostty_app_t) {
|
|
// Initialize with some default frame size. The important thing is that this
|
|
// is non-zero so that our layer bounds are non-zero so that our renderer
|
|
// can do SOMETHING.
|
|
super.init(frame: NSMakeRect(0, 0, 800, 600))
|
|
|
|
// Setup our surface. This will also initialize all the terminal IO.
|
|
var surface_cfg = ghostty_surface_config_s(
|
|
nsview: Unmanaged.passUnretained(self).toOpaque(),
|
|
scale_factor: NSScreen.main!.backingScaleFactor)
|
|
guard let surface = ghostty_surface_new(app, &surface_cfg) else {
|
|
self.error = AppError.surfaceCreateError
|
|
return
|
|
}
|
|
|
|
self.surface = surface;
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported for this view")
|
|
}
|
|
|
|
override func resize(withOldSuperviewSize oldSize: NSSize) {
|
|
super.resize(withOldSuperviewSize: oldSize)
|
|
|
|
if let surface = self.surface {
|
|
// Ghostty wants to know the actual framebuffer size...
|
|
let fbFrame = self.convertToBacking(self.frame);
|
|
ghostty_surface_set_size(surface, UInt32(fbFrame.size.width), UInt32(fbFrame.size.height))
|
|
}
|
|
}
|
|
|
|
override func viewDidChangeBackingProperties() {
|
|
guard let surface = self.surface else { return }
|
|
|
|
// Detect our X/Y scale factor so we can update our surface
|
|
let fbFrame = self.convertToBacking(self.frame)
|
|
let xScale = fbFrame.size.width / self.frame.size.width
|
|
let yScale = fbFrame.size.height / self.frame.size.height
|
|
ghostty_surface_set_content_scale(surface, xScale, yScale)
|
|
|
|
// When our scale factor changes, so does our fb size so we send that too
|
|
ghostty_surface_set_size(surface, UInt32(fbFrame.size.width), UInt32(fbFrame.size.height))
|
|
}
|
|
|
|
override func updateLayer() {
|
|
guard let surface = self.surface else { return }
|
|
ghostty_surface_refresh(surface);
|
|
}
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
print("Mouse down: \(event)")
|
|
}
|
|
|
|
override func keyDown(with event: NSEvent) {
|
|
print("Key down: \(event)")
|
|
|
|
if let surface = self.surface {
|
|
if (event.keyCode == 36) {
|
|
ghostty_surface_key(surface, press, enter, 0)
|
|
}
|
|
}
|
|
|
|
self.interpretKeyEvents([event])
|
|
}
|
|
|
|
override func doCommand(by selector: Selector) {
|
|
// This currently just prevents NSBeep from interpretKeyEvents but in the future
|
|
// we may want to make some of this work.
|
|
|
|
// print("SEL: \(selector)")
|
|
}
|
|
}
|
|
|
|
/*
|
|
struct TerminalSurfaceView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
TerminalSurfaceView()
|
|
}
|
|
}
|
|
*/
|