218 lines
6.9 KiB
Swift
218 lines
6.9 KiB
Swift
import AppKit
|
|
import SwiftUI
|
|
|
|
class IcedViewportView: NSView {
|
|
private(set) var viewportHandle: OpaquePointer?
|
|
private var displayLink: CVDisplayLink?
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
wantsLayer = true
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
wantsLayer = true
|
|
}
|
|
|
|
override var isFlipped: Bool { true }
|
|
override var wantsUpdateLayer: Bool { true }
|
|
override var acceptsFirstResponder: Bool { true }
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidMoveToWindow() {
|
|
super.viewDidMoveToWindow()
|
|
if window != nil && viewportHandle == nil {
|
|
createViewport()
|
|
startDisplayLink()
|
|
window?.makeFirstResponder(self)
|
|
} else if window == nil {
|
|
stopDisplayLink()
|
|
destroyViewport()
|
|
}
|
|
}
|
|
|
|
private func createViewport() {
|
|
let scale = Float(window?.backingScaleFactor ?? 2.0)
|
|
let w = Float(bounds.width)
|
|
let h = Float(bounds.height)
|
|
let nsviewPtr = Unmanaged.passUnretained(self).toOpaque()
|
|
viewportHandle = viewport_create(nsviewPtr, w, h, scale)
|
|
}
|
|
|
|
private func destroyViewport() {
|
|
guard let handle = viewportHandle else { return }
|
|
viewport_destroy(handle)
|
|
viewportHandle = nil
|
|
}
|
|
|
|
deinit {
|
|
stopDisplayLink()
|
|
destroyViewport()
|
|
}
|
|
|
|
// MARK: - Display Link
|
|
|
|
private func startDisplayLink() {
|
|
guard displayLink == nil else { return }
|
|
var link: CVDisplayLink?
|
|
CVDisplayLinkCreateWithActiveCGDisplays(&link)
|
|
guard let link = link else { return }
|
|
|
|
let selfPtr = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
|
CVDisplayLinkSetOutputCallback(link, { _, _, _, _, _, userInfo -> CVReturn in
|
|
guard let userInfo = userInfo else { return kCVReturnSuccess }
|
|
let view = Unmanaged<IcedViewportView>.fromOpaque(userInfo).takeUnretainedValue()
|
|
DispatchQueue.main.async {
|
|
view.renderFrame()
|
|
}
|
|
return kCVReturnSuccess
|
|
}, selfPtr)
|
|
|
|
CVDisplayLinkStart(link)
|
|
displayLink = link
|
|
}
|
|
|
|
private func stopDisplayLink() {
|
|
guard let link = displayLink else { return }
|
|
CVDisplayLinkStop(link)
|
|
displayLink = nil
|
|
}
|
|
|
|
private func renderFrame() {
|
|
guard let handle = viewportHandle else { return }
|
|
viewport_render(handle)
|
|
}
|
|
|
|
// MARK: - Resize
|
|
|
|
override func setFrameSize(_ newSize: NSSize) {
|
|
super.setFrameSize(newSize)
|
|
resizeViewport()
|
|
}
|
|
|
|
override func setBoundsSize(_ newSize: NSSize) {
|
|
super.setBoundsSize(newSize)
|
|
resizeViewport()
|
|
}
|
|
|
|
private func resizeViewport() {
|
|
guard let handle = viewportHandle else { return }
|
|
let scale = Float(window?.backingScaleFactor ?? 2.0)
|
|
viewport_resize(handle, Float(bounds.width), Float(bounds.height), scale)
|
|
}
|
|
|
|
// MARK: - Mouse Events
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
window?.makeFirstResponder(self)
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 0, true)
|
|
}
|
|
|
|
override func mouseUp(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 0, false)
|
|
}
|
|
|
|
override func mouseMoved(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 255, false)
|
|
}
|
|
|
|
override func mouseDragged(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 0, true)
|
|
}
|
|
|
|
override func rightMouseDown(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 1, true)
|
|
}
|
|
|
|
override func rightMouseUp(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_mouse_event(h, Float(pt.x), Float(pt.y), 1, false)
|
|
}
|
|
|
|
override func scrollWheel(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let pt = convert(event.locationInWindow, from: nil)
|
|
viewport_scroll_event(h, Float(pt.x), Float(pt.y), Float(event.scrollingDeltaX), Float(event.scrollingDeltaY))
|
|
}
|
|
|
|
// MARK: - Key Events
|
|
|
|
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
|
guard viewportHandle != nil else { return super.performKeyEquivalent(with: event) }
|
|
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
|
|
let cmd = flags.contains(.command)
|
|
let shift = flags.contains(.shift)
|
|
let chars = event.charactersIgnoringModifiers ?? ""
|
|
|
|
if cmd && !shift {
|
|
switch chars {
|
|
case "a", "b", "c", "i", "v", "x", "z", "p", "t",
|
|
"=", "+", "-", "0":
|
|
keyDown(with: event)
|
|
return true
|
|
default: break
|
|
}
|
|
}
|
|
if cmd && shift {
|
|
switch chars {
|
|
case "z":
|
|
keyDown(with: event)
|
|
return true
|
|
default: break
|
|
}
|
|
}
|
|
return super.performKeyEquivalent(with: event)
|
|
}
|
|
|
|
override func keyDown(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let text = event.characters ?? ""
|
|
text.withCString { cstr in
|
|
viewport_key_event(h, UInt32(event.keyCode), UInt32(event.modifierFlags.rawValue), true, cstr)
|
|
}
|
|
}
|
|
|
|
override func keyUp(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
let text = event.characters ?? ""
|
|
text.withCString { cstr in
|
|
viewport_key_event(h, UInt32(event.keyCode), UInt32(event.modifierFlags.rawValue), false, cstr)
|
|
}
|
|
}
|
|
|
|
override func flagsChanged(with event: NSEvent) {
|
|
guard let h = viewportHandle else { return }
|
|
viewport_key_event(h, UInt32(event.keyCode), UInt32(event.modifierFlags.rawValue), true, nil)
|
|
}
|
|
|
|
// MARK: - Text Bridge
|
|
|
|
func setText(_ text: String) {
|
|
guard let h = viewportHandle else { return }
|
|
text.withCString { cstr in
|
|
viewport_set_text(h, cstr)
|
|
}
|
|
}
|
|
|
|
func getText() -> String {
|
|
guard let h = viewportHandle else { return "" }
|
|
guard let cstr = viewport_get_text(h) else { return "" }
|
|
let result = String(cString: cstr)
|
|
viewport_free_string(cstr)
|
|
return result
|
|
}
|
|
}
|