Acord/src/IcedViewportView.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
}
}