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)
|
||||
|
||||
RUST_LIB="$ROOT/core/target/release"
|
||||
RUST_LIB="$ROOT/target/release"
|
||||
export MACOSX_DEPLOYMENT_TARGET=14.0
|
||||
export ZERO_AR_DATE=0
|
||||
echo "Building Rust core (release)..."
|
||||
cd "$ROOT/core" && cargo build --release
|
||||
echo "Building Rust workspace (release)..."
|
||||
cd "$ROOT" && cargo build --release -p swiftly-viewport
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Rust build failed"
|
||||
exit 1
|
||||
fi
|
||||
cd "$ROOT"
|
||||
|
||||
if [ ! -f "$RUST_LIB/libswiftly_core.a" ]; then
|
||||
echo "ERROR: libswiftly_core.a not found at $RUST_LIB"
|
||||
if [ ! -f "$RUST_LIB/libswiftly_viewport.a" ]; then
|
||||
echo "ERROR: libswiftly_viewport.a not found at $RUST_LIB"
|
||||
exit 1
|
||||
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 ---
|
||||
ICONS="$ROOT/assets/icon_sources"
|
||||
|
|
@ -63,6 +62,11 @@ swiftc \
|
|||
"${RUST_FLAGS[@]}" \
|
||||
-framework Cocoa \
|
||||
-framework SwiftUI \
|
||||
-framework Metal \
|
||||
-framework MetalKit \
|
||||
-framework QuartzCore \
|
||||
-framework CoreGraphics \
|
||||
-framework CoreFoundation \
|
||||
-O \
|
||||
-o "$MACOS/Swiftly" \
|
||||
"$ROOT"/src/*.swift
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
cord-expr = { path = "../../Cord/crates/cord-expr" }
|
||||
|
|
@ -41,6 +41,3 @@ tree-sitter-make = "1"
|
|||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.27"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
|
|
|||
|
|
@ -6,13 +6,17 @@ struct ContentView: View {
|
|||
|
||||
var body: some View {
|
||||
let _ = themeVersion
|
||||
EditorView(state: state)
|
||||
.frame(minWidth: 400)
|
||||
.frame(minWidth: 700, minHeight: 400)
|
||||
.background(Color(ns: Theme.current.base))
|
||||
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
||||
themeVersion += 1
|
||||
}
|
||||
HSplitView {
|
||||
EditorView(state: state)
|
||||
.frame(minWidth: 400)
|
||||
IcedViewportRepresentable()
|
||||
.frame(minWidth: 200)
|
||||
}
|
||||
.frame(minWidth: 700, minHeight: 400)
|
||||
.background(Color(ns: Theme.current.base))
|
||||
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
||||
themeVersion += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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