ghostty/macos/Sources/Ghostty/InspectorView.swift
2023-10-24 15:27:14 -07:00

153 lines
5.4 KiB
Swift

import Foundation
import MetalKit
import SwiftUI
import GhosttyKit
extension Ghostty {
/// InspectableSurface is a type of Surface view that allows an inspector to be attached.
struct InspectableSurface: View {
/// Same as SurfaceWrapper, see the doc comments there.
@ObservedObject var surfaceView: SurfaceView
var isSplit: Bool = false
var body: some View {
SplitView(.vertical, left: {
SurfaceWrapper(surfaceView: surfaceView, isSplit: isSplit)
}, right: {
InspectorViewRepresentable(surfaceView: surfaceView)
})
}
}
struct InspectorViewRepresentable: NSViewRepresentable {
/// The surface that this inspector represents.
let surfaceView: SurfaceView
func makeNSView(context: Context) -> InspectorView {
let view = InspectorView()
view.surfaceView = self.surfaceView
return view
}
func updateNSView(_ view: InspectorView, context: Context) {
view.surfaceView = self.surfaceView
}
}
class InspectorView: MTKView {
let commandQueue: MTLCommandQueue
var surfaceView: SurfaceView? = nil {
didSet { surfaceViewDidChange() }
}
private var inspector: ghostty_inspector_t? {
guard let surfaceView = self.surfaceView else { return nil }
return surfaceView.inspector
}
override init(frame: CGRect, device: MTLDevice?) {
// Initialize our Metal primitives
guard
let device = device ?? MTLCreateSystemDefaultDevice(),
let commandQueue = device.makeCommandQueue() else {
fatalError("GPU not available")
}
// Setup our properties before initializing the parent
self.commandQueue = commandQueue
super.init(frame: frame, device: device)
// After initializing the parent we can set our own properties
self.device = MTLCreateSystemDefaultDevice()
self.clearColor = MTLClearColor(red: 0x28 / 0xFF, green: 0x2C / 0xFF, blue: 0x34 / 0xFF, alpha: 1.0)
// Setup our tracking areas for mouse events
updateTrackingAreas()
}
required init(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
}
deinit {
trackingAreas.forEach { removeTrackingArea($0) }
}
// MARK: Internal Inspector Funcs
private func surfaceViewDidChange() {
guard let inspector = self.inspector else { return }
guard let device = self.device else { return }
let devicePtr = Unmanaged.passRetained(device).toOpaque()
ghostty_inspector_metal_init(inspector, devicePtr)
}
private func updateSize() {
guard let inspector = self.inspector 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_inspector_set_content_scale(inspector, xScale, yScale)
// When our scale factor changes, so does our fb size so we send that too
ghostty_inspector_set_size(inspector, UInt32(fbFrame.size.width), UInt32(fbFrame.size.height))
}
// MARK: NSView
override func updateTrackingAreas() {
// To update our tracking area we just recreate it all.
trackingAreas.forEach { removeTrackingArea($0) }
// This tracking area is across the entire frame to notify us of mouse movements.
addTrackingArea(NSTrackingArea(
rect: frame,
options: [
.mouseMoved,
// Only send mouse events that happen in our visible (not obscured) rect
.inVisibleRect,
// We want active always because we want to still send mouse reports
// even if we're not focused or key.
.activeAlways,
],
owner: self,
userInfo: nil))
}
override func viewDidChangeBackingProperties() {
super.viewDidChangeBackingProperties()
updateSize()
}
override func resize(withOldSuperviewSize oldSize: NSSize) {
super.resize(withOldSuperviewSize: oldSize)
updateSize()
}
// MARK: MTKView
override func draw(_ dirtyRect: NSRect) {
guard
let commandBuffer = self.commandQueue.makeCommandBuffer(),
let descriptor = self.currentRenderPassDescriptor else {
return
}
ghostty_inspector_metal_render(
inspector,
Unmanaged.passRetained(commandBuffer).toOpaque(),
Unmanaged.passRetained(descriptor).toOpaque()
)
guard let drawable = self.currentDrawable else { return }
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
}