add iced viewport crate with wgpu/metal surface embedded in swift app
This commit is contained in:
parent
3edc8838d9
commit
50aad4bf84
|
|
@ -0,0 +1,6 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["core", "viewport"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
18
build.sh
18
build.sh
|
|
@ -10,23 +10,22 @@ RESOURCES="$CONTENTS/Resources"
|
||||||
|
|
||||||
SDK=$(xcrun --show-sdk-path)
|
SDK=$(xcrun --show-sdk-path)
|
||||||
|
|
||||||
RUST_LIB="$ROOT/core/target/release"
|
RUST_LIB="$ROOT/target/release"
|
||||||
export MACOSX_DEPLOYMENT_TARGET=14.0
|
export MACOSX_DEPLOYMENT_TARGET=14.0
|
||||||
export ZERO_AR_DATE=0
|
export ZERO_AR_DATE=0
|
||||||
echo "Building Rust core (release)..."
|
echo "Building Rust workspace (release)..."
|
||||||
cd "$ROOT/core" && cargo build --release
|
cd "$ROOT" && cargo build --release -p swiftly-viewport
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "ERROR: Rust build failed"
|
echo "ERROR: Rust build failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
cd "$ROOT"
|
|
||||||
|
|
||||||
if [ ! -f "$RUST_LIB/libswiftly_core.a" ]; then
|
if [ ! -f "$RUST_LIB/libswiftly_viewport.a" ]; then
|
||||||
echo "ERROR: libswiftly_core.a not found at $RUST_LIB"
|
echo "ERROR: libswiftly_viewport.a not found at $RUST_LIB"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RUST_FLAGS=(-import-objc-header "$ROOT/core/include/swiftly.h" -L "$RUST_LIB" -lswiftly_core)
|
RUST_FLAGS=(-import-objc-header "$ROOT/viewport/include/swiftly.h" -L "$RUST_LIB" -lswiftly_viewport)
|
||||||
|
|
||||||
# --- App icon from pre-rendered PNGs ---
|
# --- App icon from pre-rendered PNGs ---
|
||||||
ICONS="$ROOT/assets/icon_sources"
|
ICONS="$ROOT/assets/icon_sources"
|
||||||
|
|
@ -63,6 +62,11 @@ swiftc \
|
||||||
"${RUST_FLAGS[@]}" \
|
"${RUST_FLAGS[@]}" \
|
||||||
-framework Cocoa \
|
-framework Cocoa \
|
||||||
-framework SwiftUI \
|
-framework SwiftUI \
|
||||||
|
-framework Metal \
|
||||||
|
-framework MetalKit \
|
||||||
|
-framework QuartzCore \
|
||||||
|
-framework CoreGraphics \
|
||||||
|
-framework CoreFoundation \
|
||||||
-O \
|
-O \
|
||||||
-o "$MACOS/Swiftly" \
|
-o "$MACOS/Swiftly" \
|
||||||
"$ROOT"/src/*.swift
|
"$ROOT"/src/*.swift
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "rlib"]
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cord-expr = { path = "../../Cord/crates/cord-expr" }
|
cord-expr = { path = "../../Cord/crates/cord-expr" }
|
||||||
|
|
@ -41,6 +41,3 @@ tree-sitter-make = "1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.27"
|
cbindgen = "0.27"
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,12 @@ struct ContentView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let _ = themeVersion
|
let _ = themeVersion
|
||||||
|
HSplitView {
|
||||||
EditorView(state: state)
|
EditorView(state: state)
|
||||||
.frame(minWidth: 400)
|
.frame(minWidth: 400)
|
||||||
|
IcedViewportRepresentable()
|
||||||
|
.frame(minWidth: 200)
|
||||||
|
}
|
||||||
.frame(minWidth: 700, minHeight: 400)
|
.frame(minWidth: 700, minHeight: 400)
|
||||||
.background(Color(ns: Theme.current.base))
|
.background(Color(ns: Theme.current.base))
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
import AppKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class IcedViewportView: NSView {
|
||||||
|
private 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()
|
||||||
|
} 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) {
|
||||||
|
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 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: - SwiftUI Bridge
|
||||||
|
|
||||||
|
struct IcedViewportRepresentable: NSViewRepresentable {
|
||||||
|
func makeNSView(context: Context) -> IcedViewportView {
|
||||||
|
IcedViewportView(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_ nsView: IcedViewportView, context: Context) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "swiftly-viewport"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
swiftly-core = { path = "../core" }
|
||||||
|
iced_wgpu = "0.14"
|
||||||
|
iced_graphics = "0.14"
|
||||||
|
iced_runtime = "0.14"
|
||||||
|
iced_widget = { version = "0.14", features = ["wgpu"] }
|
||||||
|
wgpu = "27"
|
||||||
|
raw-window-handle = "0.6"
|
||||||
|
pollster = "0.4"
|
||||||
|
smol_str = "0.2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cbindgen = "0.27"
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
fn main() {
|
||||||
|
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
cbindgen::generate(&crate_dir)
|
||||||
|
.expect("cbindgen failed")
|
||||||
|
.write_to_file(format!("{}/include/swiftly.h", crate_dir));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
language = "C"
|
||||||
|
include_guard = "SWIFTLY_VIEWPORT_H"
|
||||||
|
header = """
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "../../core/include/swiftly.h"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[parse]
|
||||||
|
parse_deps = false
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "../../core/include/swiftly.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef SWIFTLY_VIEWPORT_H
|
||||||
|
#define SWIFTLY_VIEWPORT_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef struct ViewportHandle ViewportHandle;
|
||||||
|
|
||||||
|
struct ViewportHandle *viewport_create(void *nsview, float width, float height, float scale);
|
||||||
|
|
||||||
|
void viewport_destroy(struct ViewportHandle *handle);
|
||||||
|
|
||||||
|
void viewport_render(struct ViewportHandle *handle);
|
||||||
|
|
||||||
|
void viewport_resize(struct ViewportHandle *handle, float width, float height, float scale);
|
||||||
|
|
||||||
|
void viewport_mouse_event(struct ViewportHandle *handle,
|
||||||
|
float x,
|
||||||
|
float y,
|
||||||
|
uint8_t button,
|
||||||
|
bool pressed);
|
||||||
|
|
||||||
|
void viewport_key_event(struct ViewportHandle *handle,
|
||||||
|
uint32_t key,
|
||||||
|
uint32_t modifiers,
|
||||||
|
bool pressed,
|
||||||
|
const char *text);
|
||||||
|
|
||||||
|
void viewport_scroll_event(struct ViewportHandle *handle,
|
||||||
|
float x,
|
||||||
|
float y,
|
||||||
|
float delta_x,
|
||||||
|
float delta_y);
|
||||||
|
|
||||||
|
#endif /* SWIFTLY_VIEWPORT_H */
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
use iced_wgpu::core::keyboard::{self, key};
|
||||||
|
use iced_wgpu::core::mouse;
|
||||||
|
use iced_wgpu::core::{Event, Point};
|
||||||
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
|
use crate::ViewportHandle;
|
||||||
|
|
||||||
|
pub fn push_mouse_event(handle: &mut ViewportHandle, x: f32, y: f32, button: u8, pressed: bool) {
|
||||||
|
let position = Point::new(x / handle.scale, y / handle.scale);
|
||||||
|
handle.cursor = mouse::Cursor::Available(position);
|
||||||
|
|
||||||
|
handle.events.push(Event::Mouse(mouse::Event::CursorMoved { position }));
|
||||||
|
|
||||||
|
let btn = match button {
|
||||||
|
0 => mouse::Button::Left,
|
||||||
|
1 => mouse::Button::Right,
|
||||||
|
2 => mouse::Button::Middle,
|
||||||
|
n => mouse::Button::Other(n as u16),
|
||||||
|
};
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
handle.events.push(Event::Mouse(mouse::Event::ButtonPressed(btn)));
|
||||||
|
} else {
|
||||||
|
handle.events.push(Event::Mouse(mouse::Event::ButtonReleased(btn)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_key_event(
|
||||||
|
handle: &mut ViewportHandle,
|
||||||
|
keycode: u32,
|
||||||
|
modifier_flags: u32,
|
||||||
|
pressed: bool,
|
||||||
|
text: Option<&str>,
|
||||||
|
) {
|
||||||
|
let modifiers = decode_modifiers(modifier_flags);
|
||||||
|
let physical = key::Physical::Unidentified(key::NativeCode::MacOS(keycode as u16));
|
||||||
|
let logical = text
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| keyboard::Key::Character(SmolStr::new(s)))
|
||||||
|
.unwrap_or(keyboard::Key::Unidentified);
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
handle.events.push(Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key: logical.clone(),
|
||||||
|
modified_key: logical,
|
||||||
|
physical_key: physical,
|
||||||
|
location: keyboard::Location::Standard,
|
||||||
|
modifiers,
|
||||||
|
text: text.filter(|s| !s.is_empty()).map(SmolStr::new),
|
||||||
|
repeat: false,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
handle.events.push(Event::Keyboard(keyboard::Event::KeyReleased {
|
||||||
|
key: logical.clone(),
|
||||||
|
modified_key: logical,
|
||||||
|
physical_key: physical,
|
||||||
|
location: keyboard::Location::Standard,
|
||||||
|
modifiers,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_scroll_event(
|
||||||
|
handle: &mut ViewportHandle,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
delta_x: f32,
|
||||||
|
delta_y: f32,
|
||||||
|
) {
|
||||||
|
let position = Point::new(x / handle.scale, y / handle.scale);
|
||||||
|
handle.cursor = mouse::Cursor::Available(position);
|
||||||
|
handle.events.push(Event::Mouse(mouse::Event::WheelScrolled {
|
||||||
|
delta: mouse::ScrollDelta::Pixels {
|
||||||
|
x: delta_x,
|
||||||
|
y: delta_y,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_modifiers(flags: u32) -> keyboard::Modifiers {
|
||||||
|
let mut m = keyboard::Modifiers::empty();
|
||||||
|
// NSEvent modifier flags
|
||||||
|
if flags & (1 << 17) != 0 { m |= keyboard::Modifiers::SHIFT; }
|
||||||
|
if flags & (1 << 18) != 0 { m |= keyboard::Modifiers::CTRL; }
|
||||||
|
if flags & (1 << 19) != 0 { m |= keyboard::Modifiers::ALT; }
|
||||||
|
if flags & (1 << 20) != 0 { m |= keyboard::Modifiers::LOGO; }
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
use iced_wgpu::core::{Color, Element, Length, Theme};
|
||||||
|
use iced_widget::{container, Text};
|
||||||
|
|
||||||
|
pub struct EditorState {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditorState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
text: String::from("Swiftly"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> Element<'_, (), Theme, iced_wgpu::Renderer> {
|
||||||
|
container(
|
||||||
|
Text::new(&self.text)
|
||||||
|
.size(32)
|
||||||
|
.color(Color::WHITE),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x(Length::Fill)
|
||||||
|
.center_y(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
use iced_graphics::{Viewport, Shell};
|
||||||
|
use iced_runtime::user_interface::{self, UserInterface};
|
||||||
|
use iced_wgpu::core::renderer::Style;
|
||||||
|
use iced_wgpu::core::{clipboard, mouse, Color, Font, Pixels, Size, Theme};
|
||||||
|
use iced_wgpu::Engine;
|
||||||
|
use raw_window_handle::{AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle};
|
||||||
|
|
||||||
|
use crate::editor::EditorState;
|
||||||
|
use crate::ViewportHandle;
|
||||||
|
|
||||||
|
pub fn create(nsview: *mut c_void, width: f32, height: f32, scale: f32) -> Option<ViewportHandle> {
|
||||||
|
let ptr = NonNull::new(nsview)?;
|
||||||
|
|
||||||
|
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||||
|
backends: wgpu::Backends::METAL,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let raw_window = RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr));
|
||||||
|
let raw_display = RawDisplayHandle::AppKit(AppKitDisplayHandle::new());
|
||||||
|
|
||||||
|
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
|
||||||
|
raw_display_handle: raw_display,
|
||||||
|
raw_window_handle: raw_window,
|
||||||
|
};
|
||||||
|
|
||||||
|
let surface = unsafe { instance.create_surface_unsafe(target).ok()? };
|
||||||
|
|
||||||
|
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
force_fallback_adapter: false,
|
||||||
|
}))
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let (device, queue) =
|
||||||
|
pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()?;
|
||||||
|
|
||||||
|
let phys_w = (width * scale) as u32;
|
||||||
|
let phys_h = (height * scale) as u32;
|
||||||
|
|
||||||
|
let caps = surface.get_capabilities(&adapter);
|
||||||
|
let format = caps.formats.first().copied()?;
|
||||||
|
|
||||||
|
surface.configure(
|
||||||
|
&device,
|
||||||
|
&wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format,
|
||||||
|
width: phys_w.max(1),
|
||||||
|
height: phys_h.max(1),
|
||||||
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
|
alpha_mode: caps
|
||||||
|
.alpha_modes
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(wgpu::CompositeAlphaMode::Auto),
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let engine = Engine::new(
|
||||||
|
&adapter,
|
||||||
|
device.clone(),
|
||||||
|
queue.clone(),
|
||||||
|
format,
|
||||||
|
None,
|
||||||
|
Shell::headless(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let renderer = iced_wgpu::Renderer::new(
|
||||||
|
engine,
|
||||||
|
Font::DEFAULT,
|
||||||
|
Pixels(16.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
let viewport = Viewport::with_physical_size(
|
||||||
|
Size::new(phys_w.max(1), phys_h.max(1)),
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(ViewportHandle {
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
format,
|
||||||
|
width: phys_w,
|
||||||
|
height: phys_h,
|
||||||
|
scale,
|
||||||
|
renderer,
|
||||||
|
viewport,
|
||||||
|
cache: user_interface::Cache::new(),
|
||||||
|
state: EditorState::new(),
|
||||||
|
events: Vec::new(),
|
||||||
|
cursor: mouse::Cursor::Unavailable,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(handle: &mut ViewportHandle) {
|
||||||
|
let frame = match handle.surface.get_current_texture() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let view = frame.texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
let logical_size = handle.viewport.logical_size();
|
||||||
|
|
||||||
|
let cache = std::mem::take(&mut handle.cache);
|
||||||
|
let mut ui = UserInterface::build(
|
||||||
|
handle.state.view(),
|
||||||
|
Size::new(logical_size.width, logical_size.height),
|
||||||
|
cache,
|
||||||
|
&mut handle.renderer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut clipboard = clipboard::Null;
|
||||||
|
let mut messages: Vec<()> = Vec::new();
|
||||||
|
|
||||||
|
let _ = ui.update(
|
||||||
|
&handle.events,
|
||||||
|
handle.cursor,
|
||||||
|
&mut handle.renderer,
|
||||||
|
&mut clipboard,
|
||||||
|
&mut messages,
|
||||||
|
);
|
||||||
|
handle.events.clear();
|
||||||
|
|
||||||
|
let theme = Theme::Dark;
|
||||||
|
let style = Style {
|
||||||
|
text_color: Color::WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
||||||
|
handle.cache = ui.into_cache();
|
||||||
|
|
||||||
|
let bg = Color::from_rgb(0.08, 0.08, 0.10);
|
||||||
|
handle.renderer.present(
|
||||||
|
Some(bg),
|
||||||
|
handle.format,
|
||||||
|
&view,
|
||||||
|
&handle.viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
frame.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(handle: &mut ViewportHandle, width: f32, height: f32, scale: f32) {
|
||||||
|
let phys_w = (width * scale) as u32;
|
||||||
|
let phys_h = (height * scale) as u32;
|
||||||
|
if phys_w == 0 || phys_h == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.width = phys_w;
|
||||||
|
handle.height = phys_h;
|
||||||
|
handle.scale = scale;
|
||||||
|
|
||||||
|
handle.viewport = Viewport::with_physical_size(
|
||||||
|
Size::new(phys_w, phys_h),
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
|
||||||
|
handle.surface.configure(
|
||||||
|
&handle.device,
|
||||||
|
&wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: handle.format,
|
||||||
|
width: phys_w,
|
||||||
|
height: phys_h,
|
||||||
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
|
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
use std::ffi::{c_char, c_void};
|
||||||
|
|
||||||
|
mod bridge;
|
||||||
|
mod editor;
|
||||||
|
mod handle;
|
||||||
|
|
||||||
|
pub use swiftly_core::*;
|
||||||
|
|
||||||
|
use editor::EditorState;
|
||||||
|
use iced_graphics::Viewport;
|
||||||
|
use iced_runtime::user_interface;
|
||||||
|
use iced_wgpu::core::Event;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct ViewportHandle {
|
||||||
|
surface: wgpu::Surface<'static>,
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
scale: f32,
|
||||||
|
|
||||||
|
renderer: iced_wgpu::Renderer,
|
||||||
|
viewport: Viewport,
|
||||||
|
cache: user_interface::Cache,
|
||||||
|
state: EditorState,
|
||||||
|
events: Vec<Event>,
|
||||||
|
cursor: iced_wgpu::core::mouse::Cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_create(
|
||||||
|
nsview: *mut c_void,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
scale: f32,
|
||||||
|
) -> *mut ViewportHandle {
|
||||||
|
if nsview.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
match handle::create(nsview, width, height, scale) {
|
||||||
|
Some(h) => Box::into_raw(Box::new(h)),
|
||||||
|
None => std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_destroy(handle: *mut ViewportHandle) {
|
||||||
|
if handle.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
drop(Box::from_raw(handle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_render(handle: *mut ViewportHandle) {
|
||||||
|
let h = match unsafe { handle.as_mut() } {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
handle::render(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_resize(
|
||||||
|
handle: *mut ViewportHandle,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
scale: f32,
|
||||||
|
) {
|
||||||
|
let h = match unsafe { handle.as_mut() } {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
handle::resize(h, width, height, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_mouse_event(
|
||||||
|
handle: *mut ViewportHandle,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
button: u8,
|
||||||
|
pressed: bool,
|
||||||
|
) {
|
||||||
|
let h = match unsafe { handle.as_mut() } {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
bridge::push_mouse_event(h, x, y, button, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_key_event(
|
||||||
|
handle: *mut ViewportHandle,
|
||||||
|
key: u32,
|
||||||
|
modifiers: u32,
|
||||||
|
pressed: bool,
|
||||||
|
text: *const c_char,
|
||||||
|
) {
|
||||||
|
let h = match unsafe { handle.as_mut() } {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let text_str = if text.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy())
|
||||||
|
};
|
||||||
|
bridge::push_key_event(h, key, modifiers, pressed, text_str.as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn viewport_scroll_event(
|
||||||
|
handle: *mut ViewportHandle,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
delta_x: f32,
|
||||||
|
delta_y: f32,
|
||||||
|
) {
|
||||||
|
let h = match unsafe { handle.as_mut() } {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
bridge::push_scroll_event(h, x, y, delta_x, delta_y);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue