mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-27 03:48:37 +03:00
91 lines
3.4 KiB
Swift
91 lines
3.4 KiB
Swift
import SwiftUI
|
|
import GhosttyKit
|
|
|
|
extension Ghostty {
|
|
/// The UIView implementation for a terminal surface.
|
|
class SurfaceView: UIView, ObservableObject {
|
|
/// Unique ID per surface
|
|
let uuid: UUID
|
|
|
|
// 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
|
|
// to the app level and it is set from there.
|
|
@Published var title: String = "👻"
|
|
|
|
// 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.
|
|
// when the font size changes). This is used to allow windows to be
|
|
// resized in discrete steps of a single cell.
|
|
@Published var cellSize: OSSize = .zero
|
|
|
|
// The health state of the surface. This currently only reflects the
|
|
// renderer health. In the future we may want to make this an enum.
|
|
@Published var healthy: Bool = true
|
|
|
|
// Any error while initializing the surface.
|
|
@Published var error: Error? = nil
|
|
|
|
private(set) var surface: ghostty_surface_t?
|
|
|
|
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
|
|
self.uuid = uuid ?? .init()
|
|
|
|
// 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: CGRect(x: 0, y: 0, width: 800, height: 600))
|
|
|
|
// Setup our surface. This will also initialize all the terminal IO.
|
|
let surface_cfg = baseConfig ?? SurfaceConfiguration()
|
|
var surface_cfg_c = surface_cfg.ghosttyConfig(view: self)
|
|
guard let surface = ghostty_surface_new(app, &surface_cfg_c) else {
|
|
// TODO
|
|
return
|
|
}
|
|
self.surface = surface;
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported for this view")
|
|
}
|
|
|
|
deinit {
|
|
guard let surface = self.surface else { return }
|
|
ghostty_surface_free(surface)
|
|
}
|
|
|
|
func focusDidChange(_ focused: Bool) {
|
|
guard let surface = self.surface else { return }
|
|
ghostty_surface_set_focus(surface, focused)
|
|
}
|
|
|
|
func sizeDidChange(_ size: CGSize) {
|
|
guard let surface = self.surface else { return }
|
|
|
|
// Ghostty wants to know the actual framebuffer size... It is very important
|
|
// here that we use "size" and NOT the view frame. If we're in the middle of
|
|
// an animation (i.e. a fullscreen animation), the frame will not yet be updated.
|
|
// The size represents our final size we're going for.
|
|
let scale = self.contentScaleFactor
|
|
ghostty_surface_set_content_scale(surface, scale, scale)
|
|
ghostty_surface_set_size(
|
|
surface,
|
|
UInt32(size.width * scale),
|
|
UInt32(size.height * scale)
|
|
)
|
|
}
|
|
|
|
// MARK: UIView
|
|
|
|
override class var layerClass: AnyClass {
|
|
get {
|
|
return CAMetalLayer.self
|
|
}
|
|
}
|
|
|
|
override func didMoveToWindow() {
|
|
sizeDidChange(frame.size)
|
|
}
|
|
}
|
|
}
|