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.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", "e", "f", "g", "i", "v", "x", "z", "p", "t", "=", "+", "-", "0": keyDown(with: event) return true default: break } } if cmd && shift { switch chars { case "g", "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 } func sendCommand(_ command: UInt32) { guard let h = viewportHandle else { return } viewport_send_command(h, command) } func setTheme(_ name: String) { guard let h = viewportHandle else { return } name.withCString { cstr in viewport_set_theme(h, cstr) } } }