mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
adam changes
This commit is contained in:
3344
Cargo.lock
generated
3344
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
59
Cargo.toml
59
Cargo.toml
@@ -1,66 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"editor",
|
||||
"desktop",
|
||||
"proc-macros",
|
||||
"frontend/wasm",
|
||||
"node-graph/gapplication-io",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
"node-graph/preprocessor",
|
||||
"libraries/dyn-any",
|
||||
"libraries/path-bool",
|
||||
"libraries/bezier-rs",
|
||||
"libraries/math-parser",
|
||||
"website/other/bezier-rs-demos/wasm",
|
||||
]
|
||||
default-members = [
|
||||
"editor",
|
||||
"frontend/wasm",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Local dependencies
|
||||
bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
|
||||
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
|
||||
preprocessor = { path = "node-graph/preprocessor"}
|
||||
math-parser = { path = "libraries/math-parser" }
|
||||
path-bool = { path = "libraries/path-bool" }
|
||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||
graphene-brush = { path = "node-graph/gbrush" }
|
||||
graphene-core = { path = "node-graph/gcore" }
|
||||
graphene-math-nodes = { path = "node-graph/gmath-nodes" }
|
||||
graphene-path-bool = { path = "node-graph/gpath-bool" }
|
||||
graph-craft = { path = "node-graph/graph-craft" }
|
||||
graphene-raster-nodes = { path = "node-graph/graster-nodes" }
|
||||
graphene-std = { path = "node-graph/gstd" }
|
||||
graphene-svg-renderer = { path = "node-graph/gsvg-renderer" }
|
||||
interpreted-executor = { path = "node-graph/interpreted-executor" }
|
||||
node-macro = { path = "node-graph/node-macro" }
|
||||
wgpu-executor = { path = "node-graph/wgpu-executor" }
|
||||
graphite-proc-macros = { path = "proc-macros" }
|
||||
|
||||
# Workspace dependencies
|
||||
rustc-hash = "2.0"
|
||||
bytemuck = { version = "1.13", features = ["derive"] }
|
||||
@@ -69,6 +12,8 @@ serde_json = "1.0"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
reqwest = { version = "0.12", features = ["blocking", "rustls-tls", "json"] }
|
||||
futures = "0.3"
|
||||
cef = "138.5.0"
|
||||
include_dir = "0.7.4"
|
||||
env_logger = "0.11"
|
||||
log = "0.4"
|
||||
bitflags = { version = "2.4", features = ["serde"] }
|
||||
|
||||
@@ -8,25 +8,10 @@ repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
|
||||
[features]
|
||||
default = ["gpu"]
|
||||
gpu = ["graphite-editor/gpu"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
graphite-editor = { path = "../editor", features = [
|
||||
"gpu",
|
||||
"ron",
|
||||
"vello",
|
||||
"decouple-execution",
|
||||
] }
|
||||
wgpu = { workspace = true }
|
||||
winit = { workspace = true, features = ["serde"] }
|
||||
base64.workspace = true
|
||||
thiserror.workspace = true
|
||||
pollster = "0.3"
|
||||
cef = "138.5.0"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tracing = "0.1.41"
|
||||
bytemuck = { version = "1.23.1", features = ["derive"] }
|
||||
include_dir = "0.7.4"
|
||||
thiserror = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
cef = { workspace = true }
|
||||
include_dir = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::fs::metadata;
|
||||
|
||||
fn main() {
|
||||
let frontend_dir = format!("{}/../frontend/dist", env!("CARGO_MANIFEST_DIR"));
|
||||
let frontend_dir = format!("{}/../frontend-native/dist", env!("CARGO_MANIFEST_DIR"));
|
||||
println!("frontend_dir: {}", frontend_dir);
|
||||
metadata(&frontend_dir).expect("Failed to find frontend directory. Please build the frontend first.");
|
||||
metadata(format!("{}/index.html", &frontend_dir)).expect("Failed to find index.html in frontend directory.");
|
||||
|
||||
|
||||
@@ -4,10 +4,14 @@ use cef::{browser_host_create_browser_sync, initialize, BrowserSettings, Diction
|
||||
use thiserror::Error;
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
use crate::cef::internal::OffscreenRenderHandler;
|
||||
use crate::render::FrameBufferHandle;
|
||||
use crate::WindowStateHandle;
|
||||
|
||||
use super::input::{handle_window_event, InputState};
|
||||
use super::EventHandler;
|
||||
|
||||
use super::internal::{AppImpl, ClientImpl, NonBrowserAppImpl, RenderHandlerImpl};
|
||||
use super::internal::{AppImpl, ClientImpl, OffscreenApp, RenderHandlerImpl};
|
||||
|
||||
pub(crate) struct Setup {}
|
||||
pub(crate) struct Initialized {}
|
||||
@@ -37,7 +41,7 @@ impl Context<Setup> {
|
||||
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
|
||||
if !is_browser_process {
|
||||
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
||||
let mut app = NonBrowserAppImpl::new();
|
||||
let mut app = OffscreenApp::new();
|
||||
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
|
||||
if ret >= 0 {
|
||||
return Err(SetupError::SubprocessFailed(process_type.to_string()));
|
||||
@@ -53,19 +57,8 @@ impl Context<Setup> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn init(self, event_handler: impl EventHandler) -> Result<Context<Initialized>, InitError> {
|
||||
let mut settings = Settings::default();
|
||||
settings.windowless_rendering_enabled = 1;
|
||||
settings.multi_threaded_message_loop = 0;
|
||||
|
||||
let mut cef_app = AppImpl::new(event_handler.clone());
|
||||
|
||||
let res = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
||||
if res != 1 {
|
||||
return Err(InitError::InitializationFailed);
|
||||
}
|
||||
|
||||
let render_handler = RenderHandlerImpl::new(event_handler.clone());
|
||||
pub(crate) fn init(self, frame_buffer: FrameBufferHandle) -> Result<Context<Initialized>, InitError> {
|
||||
let render_handler = OffscreenRenderHandler::new(frame_buffer);
|
||||
let mut client = ClientImpl::new(render_handler);
|
||||
|
||||
let url = CefString::from("graphite://frontend/");
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) fn handle_window_event(context: &mut Context<Initialized>, event: &Wi
|
||||
|
||||
context.input_state.update_mouse_position(position);
|
||||
let mouse_event: MouseEvent = (&context.input_state).into();
|
||||
browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
||||
// browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
use cef::{App, BrowserProcessHandler, ImplApp, SchemeRegistrar, WrapApp};
|
||||
use cef::{App, BrowserProcessHandler, Frame, ImplApp, SchemeRegistrar, WrapApp};
|
||||
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
use crate::cef::EventHandler;
|
||||
use crate::render::{FrameBuffer, FrameBufferHandle};
|
||||
|
||||
use super::browser_process_handler::BrowserProcessHandlerImpl;
|
||||
use super::browser_process_handler::OffscreenBrowserProcessHandler;
|
||||
|
||||
pub(crate) struct AppImpl<H: EventHandler> {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
event_handler: H,
|
||||
struct OffscreenApp {
|
||||
object: *mut RcImpl<cef_dll_sys::_cef_app_t, Self>,
|
||||
frame_buffer: Arc<Mutex<FrameBuffer>>,
|
||||
}
|
||||
impl<H: EventHandler> AppImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> App {
|
||||
|
||||
impl OffscreenApp {
|
||||
fn new(frame_buffer: Arc<Mutex<FrameBuffer>>) -> App {
|
||||
App::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
frame_buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> ImplApp for AppImpl<H> {
|
||||
impl ImplApp for OffscreenApp {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerImpl::new(self.event_handler.clone()))
|
||||
println!("browser_process_handler");
|
||||
Some(OffscreenBrowserProcessHandler::new(self.frame_buffer.clone()))
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
println!("on_register_custom_schemes");
|
||||
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,22 +3,17 @@ use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_brows
|
||||
use cef::{BrowserProcessHandler, CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
|
||||
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
use crate::cef::EventHandler;
|
||||
|
||||
pub(crate) struct BrowserProcessHandlerImpl<H: EventHandler> {
|
||||
pub(crate) struct OffscreenBrowserProcessHandler {
|
||||
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
|
||||
event_handler: H,
|
||||
}
|
||||
impl<H: EventHandler> BrowserProcessHandlerImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
})
|
||||
impl OffscreenBrowserProcessHandler {
|
||||
pub(crate) fn new() -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self { object: std::ptr::null_mut() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||
impl ImplBrowserProcessHandler for OffscreenBrowserProcessHandler {
|
||||
fn on_context_initialized(&self) {
|
||||
cef::register_scheme_handler_factory(Some(&CefString::from("graphite")), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new())));
|
||||
}
|
||||
@@ -28,19 +23,16 @@ impl<H: EventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H>
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> Clone for BrowserProcessHandlerImpl<H> {
|
||||
impl Clone for OffscreenBrowserProcessHandler {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
event_handler: self.event_handler.clone(),
|
||||
}
|
||||
Self { object: self.object }
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> Rc for BrowserProcessHandlerImpl<H> {
|
||||
impl Rc for OffscreenBrowserProcessHandler {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
@@ -48,7 +40,7 @@ impl<H: EventHandler> Rc for BrowserProcessHandlerImpl<H> {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||
impl WrapBrowserProcessHandler for OffscreenBrowserProcessHandler {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
|
||||
use cef::{Client, ImplClient, RenderHandler, WrapClient};
|
||||
|
||||
|
||||
pub(crate) struct ClientImpl {
|
||||
object: *mut RcImpl<_cef_client_t, Self>,
|
||||
render_handler: RenderHandler,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
mod app;
|
||||
mod browser_process_handler;
|
||||
mod client;
|
||||
mod non_browser_app;
|
||||
mod offscreen_app;
|
||||
mod render_handler;
|
||||
|
||||
pub(crate) use app::AppImpl;
|
||||
pub(crate) use client::ClientImpl;
|
||||
pub(crate) use non_browser_app::NonBrowserAppImpl;
|
||||
pub(crate) use render_handler::RenderHandlerImpl;
|
||||
pub(crate) use offscreen_app::OffscreenApp;
|
||||
pub(crate) use render_handler::OffscreenRenderHandler;
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
use cef::{App, ImplApp, SchemeRegistrar, WrapApp};
|
||||
use cef::{App, BrowserProcessHandler, ImplApp, SchemeRegistrar, WrapApp};
|
||||
|
||||
use crate::cef::internal::browser_process_handler::OffscreenBrowserProcessHandler;
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
use crate::render::FrameBufferHandle;
|
||||
|
||||
pub(crate) struct NonBrowserAppImpl {
|
||||
pub(crate) struct OffscreenApp {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
}
|
||||
impl NonBrowserAppImpl {
|
||||
impl OffscreenApp {
|
||||
pub(crate) fn new() -> App {
|
||||
App::new(Self { object: std::ptr::null_mut() })
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplApp for NonBrowserAppImpl {
|
||||
impl ImplApp for OffscreenApp {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(OffscreenBrowserProcessHandler::new())
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||
}
|
||||
@@ -23,7 +29,7 @@ impl ImplApp for NonBrowserAppImpl {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NonBrowserAppImpl {
|
||||
impl Clone for OffscreenApp {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
@@ -32,7 +38,7 @@ impl Clone for NonBrowserAppImpl {
|
||||
Self { object: self.object }
|
||||
}
|
||||
}
|
||||
impl Rc for NonBrowserAppImpl {
|
||||
impl Rc for OffscreenApp {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
@@ -40,7 +46,7 @@ impl Rc for NonBrowserAppImpl {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WrapApp for NonBrowserAppImpl {
|
||||
impl WrapApp for OffscreenApp {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
|
||||
use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplRenderHandler, PaintElementType, Rect, RenderHandler, WrapRenderHandler};
|
||||
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, RenderHandler, WrapRenderHandler};
|
||||
|
||||
use crate::cef::EventHandler;
|
||||
use crate::render::FrameBufferHandle;
|
||||
|
||||
pub(crate) struct RenderHandlerImpl<H: EventHandler> {
|
||||
// CEF render handler for offscreen rendering
|
||||
pub struct OffscreenRenderHandler {
|
||||
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
||||
event_handler: H,
|
||||
frame_buffer: FrameBufferHandle,
|
||||
}
|
||||
impl<H: EventHandler> RenderHandlerImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> RenderHandler {
|
||||
|
||||
impl OffscreenRenderHandler {
|
||||
pub(crate) fn new(frame_buffer: FrameBufferHandle) -> RenderHandler {
|
||||
RenderHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
frame_buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||
impl ImplRenderHandler for OffscreenRenderHandler {
|
||||
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
|
||||
let frame_buffer = self.frame_buffer.inner.lock().unwrap();
|
||||
let width = frame_buffer.width() as i32;
|
||||
let height = frame_buffer.height() as i32;
|
||||
if let Some(rect) = rect {
|
||||
let view = self.event_handler.view();
|
||||
*rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: view.width as i32,
|
||||
height: view.height as i32,
|
||||
};
|
||||
*rect = Rect { x: 0, y: 0, width, height };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +40,7 @@ impl<H: EventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||
) {
|
||||
let buffer_size = (width * height * 4) as usize;
|
||||
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
|
||||
let draw_successful = self.event_handler.draw(buffer_slice.to_vec(), width as usize, height as usize);
|
||||
if !draw_successful {
|
||||
if let Some(browser) = browser {
|
||||
browser.host().unwrap().was_resized();
|
||||
}
|
||||
}
|
||||
self.frame_buffer.inner.lock().unwrap().add_buffer(buffer_slice, width, height);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||
@@ -54,7 +48,13 @@ impl<H: EventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> Clone for RenderHandlerImpl<H> {
|
||||
impl WrapRenderHandler for OffscreenRenderHandler {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for OffscreenRenderHandler {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
@@ -62,11 +62,12 @@ impl<H: EventHandler> Clone for RenderHandlerImpl<H> {
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
event_handler: self.event_handler.clone(),
|
||||
frame_buffer: self.frame_buffer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> Rc for RenderHandlerImpl<H> {
|
||||
|
||||
impl Rc for OffscreenRenderHandler {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
@@ -74,8 +75,3 @@ impl<H: EventHandler> Rc for RenderHandlerImpl<H> {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> WrapRenderHandler for RenderHandlerImpl<H> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,4 @@ mod context;
|
||||
|
||||
mod internal;
|
||||
|
||||
pub(crate) trait EventHandler: Clone {
|
||||
fn view(&self) -> View;
|
||||
fn draw(&self, buffer: Vec<u8>, width: usize, height: usize) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct View {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) height: usize,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub(crate) fn new(width: usize, height: usize) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
|
||||
|
||||
@@ -61,7 +61,7 @@ impl ImplSchemeHandlerFactory for GraphiteSchemeHandlerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist");
|
||||
static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend-native/dist");
|
||||
|
||||
struct GraphiteFrontendResourceHandler<'a> {
|
||||
object: *mut RcImpl<_cef_resource_handler_t, Self>,
|
||||
@@ -74,7 +74,7 @@ impl<'a> GraphiteFrontendResourceHandler<'a> {
|
||||
let data = if let Some(file) = file {
|
||||
Some(RefCell::new(file.contents().iter()))
|
||||
} else {
|
||||
println!("Failed to find asset at path: {}", path);
|
||||
println!("Failed to find asset at path: {}/{}", FRONTEND.path().to_str().unwrap(), path);
|
||||
None
|
||||
};
|
||||
let mimetype = if let Some(file) = file {
|
||||
|
||||
@@ -1,203 +1,42 @@
|
||||
use std::fmt::Debug;
|
||||
use std::process::exit;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::*;
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
|
||||
use winit::window::{Window, WindowId};
|
||||
use winit::window::WindowId;
|
||||
|
||||
mod cef;
|
||||
use cef::Setup;
|
||||
|
||||
mod winit_app;
|
||||
|
||||
mod render;
|
||||
use render::{FrameBuffer, GraphicsState};
|
||||
|
||||
pub(crate) enum CustomEvent {
|
||||
UiUpdate,
|
||||
Resized,
|
||||
DoBrowserWork,
|
||||
}
|
||||
use crate::render::FrameBufferHandle;
|
||||
use crate::winit_app::WinitApp;
|
||||
|
||||
pub(crate) struct WindowState {
|
||||
width: Option<usize>,
|
||||
height: Option<usize>,
|
||||
ui_fb: Option<FrameBuffer>,
|
||||
preview_fb: Option<FrameBuffer>,
|
||||
graphics_state: Option<GraphicsState>,
|
||||
event_loop_proxy: Option<EventLoopProxy<CustomEvent>>,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
width: None,
|
||||
height: None,
|
||||
ui_fb: None,
|
||||
preview_fb: None,
|
||||
graphics_state: None,
|
||||
event_loop_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(self) -> WindowStateHandle {
|
||||
WindowStateHandle { inner: Arc::new(Mutex::new(self)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WindowState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WindowState")
|
||||
.field("width", &self.width.is_some())
|
||||
.field("height", &self.height.is_some())
|
||||
.field("ui_fb", &self.ui_fb.is_some())
|
||||
.field("preview_fb", &self.preview_fb.is_some())
|
||||
.field("graphics_state", &self.graphics_state.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WindowStateHandle {
|
||||
inner: Arc<Mutex<WindowState>>,
|
||||
}
|
||||
|
||||
impl WindowStateHandle {
|
||||
fn with<'a, P>(&self, p: P) -> Result<(), PoisonError<MutexGuard<'a, WindowState>>>
|
||||
where
|
||||
P: FnOnce(&mut WindowState),
|
||||
{
|
||||
match self.inner.lock() {
|
||||
Ok(mut guard) => Ok(p(&mut guard)),
|
||||
Err(_) => todo!("not error handling yet"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for WindowStateHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self { inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CefEventHandler {
|
||||
window_state: WindowStateHandle,
|
||||
}
|
||||
|
||||
impl CefEventHandler {
|
||||
fn new(window_state: WindowStateHandle) -> Self {
|
||||
Self { window_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl cef::EventHandler for CefEventHandler {
|
||||
fn view(&self) -> cef::View {
|
||||
let mut w = 1;
|
||||
let mut h = 1;
|
||||
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState {
|
||||
width: Some(width),
|
||||
height: Some(height),
|
||||
..
|
||||
} => {
|
||||
w = *width;
|
||||
h = *height;
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cef::View::new(w, h)
|
||||
}
|
||||
|
||||
fn draw(&self, buffer: Vec<u8>, width: usize, height: usize) -> bool {
|
||||
let fb = FrameBuffer::new(buffer, width, height)
|
||||
.map_err(|e| {
|
||||
panic!("Failed to create FrameBuffer: {}", e);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut correct_size = true;
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::UiUpdate);
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
if width != s.width.unwrap_or(1) || height != s.height.unwrap_or(1) {
|
||||
correct_size = false;
|
||||
} else {
|
||||
s.ui_fb = Some(fb);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
correct_size
|
||||
}
|
||||
}
|
||||
|
||||
struct WinitApp {
|
||||
window_state: WindowStateHandle,
|
||||
cef_context: cef::Context<cef::Initialized>,
|
||||
window: Option<Arc<Window>>,
|
||||
}
|
||||
|
||||
impl WinitApp {
|
||||
fn new(window_state: WindowStateHandle, cef_context: cef::Context<cef::Initialized>) -> Self {
|
||||
Self {
|
||||
window_state,
|
||||
cef_context,
|
||||
window: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||
impl ApplicationHandler for WinitApp {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState { width: Some(w), height: Some(h), .. } => {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("CEF Offscreen Rendering")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(*w as u32, *h as u32)),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let graphics_state = pollster::block_on(GraphicsState::new(window.clone()));
|
||||
println!("resumed");
|
||||
let graphics_state = futures::executor::block_on(GraphicsState::init(event_loop));
|
||||
let width = graphics_state.window.inner_size().width;
|
||||
let height = graphics_state.window.inner_size().height;
|
||||
self.resize(width, height);
|
||||
|
||||
// Initialize with a test pattern so we always have something to render
|
||||
let initial_data = vec![34u8; (width * height * 4) as usize]; // Gray texture #22222222
|
||||
self.frame_buffer.inner.lock().unwrap().add_buffer(&initial_data, width, height);
|
||||
|
||||
self.window = Some(window.clone());
|
||||
s.graphics_state = Some(graphics_state);
|
||||
|
||||
let _ = thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
window.request_redraw();
|
||||
});
|
||||
|
||||
println!("Winit window created and ready");
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
self.graphics_state = Some(graphics_state);
|
||||
}
|
||||
|
||||
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
||||
match event {
|
||||
CustomEvent::DoBrowserWork => {
|
||||
self.cef_context.work();
|
||||
}
|
||||
CustomEvent::UiUpdate | CustomEvent::Resized => {
|
||||
if let Some(window) = &self.window {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
// Try load the frame buffer into the ui texture if it changes
|
||||
self.try_load_frame_buffer();
|
||||
|
||||
// Update the viewport texture if the canvas element changes
|
||||
self.try_load_viewport();
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
||||
@@ -209,94 +48,39 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
s.width = Some(physical_size.width as usize);
|
||||
s.height = Some(physical_size.height as usize);
|
||||
if let Some(elp) = &s.event_loop_proxy {
|
||||
let _ = elp.send_event(CustomEvent::Resized);
|
||||
}
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
self.resize(physical_size.width, physical_size.height);
|
||||
}
|
||||
|
||||
WindowEvent::RedrawRequested => {
|
||||
self.cef_context.work();
|
||||
|
||||
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState {
|
||||
width: Some(width),
|
||||
height: Some(height),
|
||||
graphics_state: Some(graphics_state),
|
||||
ui_fb,
|
||||
..
|
||||
} => {
|
||||
if let Some(fb) = &*ui_fb {
|
||||
graphics_state.update_texture(fb);
|
||||
if fb.width() != *width && fb.height() != *height {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
} else {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
|
||||
match graphics_state.render() {
|
||||
Ok(_) => {}
|
||||
Err(wgpu::SurfaceError::Lost) => {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||
event_loop.exit();
|
||||
}
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
match self.render() {
|
||||
Ok(_) => {}
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||
event_loop.exit();
|
||||
}
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
let cef_context = match cef::Context::<Setup>::new() {
|
||||
Ok(c) => c,
|
||||
Err(cef::SetupError::Subprocess) => exit(0),
|
||||
Err(cef::SetupError::SubprocessFailed(t)) => {
|
||||
println!("Subprocess of type {t} failed");
|
||||
println!("Subprocess of type {t} failed. args: {:?}", args);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let window_state = WindowState::new().handle();
|
||||
let frame_buffer = FrameBufferHandle::new();
|
||||
|
||||
window_state
|
||||
.with(|s| {
|
||||
s.width = Some(1200);
|
||||
s.height = Some(800);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap();
|
||||
|
||||
let cef_context = match cef_context.init(CefEventHandler::new(window_state.clone())) {
|
||||
let cef_context = match cef_context.init(frame_buffer.clone()) {
|
||||
Ok(c) => c,
|
||||
Err(cef::InitError::InitializationFailed) => {
|
||||
println!("Cef initialization failed");
|
||||
@@ -306,8 +90,11 @@ fn main() {
|
||||
|
||||
println!("Cef initialized successfully");
|
||||
|
||||
let mut winit_app = WinitApp::new(window_state, cef_context);
|
||||
let mut winit_app = WinitApp::new(cef_context, frame_buffer);
|
||||
|
||||
// Start winit event loop
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
event_loop.set_control_flow(ControlFlow::Poll);
|
||||
event_loop.run_app(&mut winit_app).unwrap();
|
||||
|
||||
winit_app.cef_context.shutdown();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let pos = array(
|
||||
// 1st triangle
|
||||
vec2f( -1.0, -1.0), // center
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
|
||||
// 2nd triangle
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( 1.0, 1.0), // right, top
|
||||
);
|
||||
let xy = pos[vertex_index];
|
||||
out.clip_position = vec4f(xy , 0.0, 1.0);
|
||||
let coords = (xy / 2. + 0.5);
|
||||
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
||||
@@ -1,74 +1,163 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cef::Frame;
|
||||
use thiserror::Error;
|
||||
use winit::window::Window;
|
||||
use winit::{event_loop::ActiveEventLoop, window::Window};
|
||||
|
||||
pub(crate) struct FrameBuffer {
|
||||
buffer: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
#[derive(Clone)]
|
||||
pub struct FrameBufferHandle {
|
||||
pub inner: Arc<Mutex<FrameBuffer>>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum FrameBufferError {
|
||||
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")]
|
||||
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
pub(crate) fn new(buffer: Vec<u8>, width: usize, height: usize) -> Result<Self, FrameBufferError> {
|
||||
let fb = Self { buffer, width, height };
|
||||
fb.validate_size()?;
|
||||
Ok(fb)
|
||||
}
|
||||
|
||||
pub(crate) fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub(crate) fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub(crate) fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn validate_size(&self) -> Result<(), FrameBufferError> {
|
||||
if self.buffer.len() != self.width * self.height * 4 {
|
||||
Err(FrameBufferError::InvalidSize {
|
||||
buffer_size: self.buffer.len(),
|
||||
expected_size: self.width * self.height * 4,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
impl FrameBufferHandle {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(FrameBuffer::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameBuffer {
|
||||
// The buffer is only valid after the CEF on_paint, and before it is loaded into the texture
|
||||
buffer_has_new_data: bool,
|
||||
buffer: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
viewport_resized: bool,
|
||||
viewport_top_left_x: u32,
|
||||
viewport_top_left_y: u32,
|
||||
viewport_width: u32,
|
||||
viewport_height: u32,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum FrameBufferError {
|
||||
#[error("Invalid buffer size. Expected {expected_width}x{expected_height} recieved {received_width}x{received_height}")]
|
||||
InvalidSize {
|
||||
expected_width: usize,
|
||||
expected_height: usize,
|
||||
received_width: usize,
|
||||
received_height: usize,
|
||||
},
|
||||
|
||||
#[error("Buffer dimensions are correct, but the allocated vec length : {vec_length} does not match the buffer length: {buffer_length}")]
|
||||
InvalidBufferSize { vec_length: usize, buffer_length: usize },
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
//Initialize the frame buffer to 4k, but set width and height to 0 as it should be initialized when
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buffer: Vec::with_capacity(3840 * 2160 * 4),
|
||||
buffer_has_new_data: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
viewport_resized: false,
|
||||
viewport_top_left_x: 0,
|
||||
viewport_top_left_y: 0,
|
||||
viewport_height: 0,
|
||||
viewport_width: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Always keep the frame buffer in sync with the window size
|
||||
pub fn resize(&mut self, width: u32, height: u32) -> Result<Self, FrameBufferError> {
|
||||
let new_size = width * height * 4;
|
||||
if self.buffer.len() < new_size {
|
||||
self.buffer.resize(new_size, 0);
|
||||
} else {
|
||||
self.buffer.truncate(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_buffer(&mut self, buffer_slice: &[u8], width: u32, height: u32) -> Result<(), FrameBufferError> {
|
||||
if width != self.width || height != self.height {
|
||||
Err(FrameBufferError::InvalidSize {
|
||||
expected_width: self.width,
|
||||
expected_height: self.height,
|
||||
received_width: width,
|
||||
received_height: height,
|
||||
})
|
||||
} else if buffer_slice.len() != self.buffer.len() {
|
||||
Err(FrameBufferError::InvalidBufferSize {
|
||||
vec_length: self.buffer.len(),
|
||||
buffer_length: buffer_slice.len(),
|
||||
})
|
||||
} else {
|
||||
self.buffer.copy_from_slice(buffer_slice);
|
||||
self.buffer_has_new_data = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn take_buffer(&mut self) -> Option<(&[u8], u32, u32)> {
|
||||
if buffer_has_new_data {
|
||||
Some((&self.buffer, self.width, self.height));
|
||||
self.buffer_has_new_data = false;
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_viewport_size(&mut self, viewport_top_left_x: u32, viewport_top_left_y: u32, viewport_width: u32, viewport_height: u32) {
|
||||
if self.viewport_top_left_x != viewport_top_left_x || self.viewport_top_left_y != viewport_top_left_y || self.viewport_width != viewport_width || self.viewport_height != viewport_height {
|
||||
self.viewport_resized = true;
|
||||
}
|
||||
self.viewport_top_left_x = viewport_top_left_x;
|
||||
self.viewport_top_left_y = viewport_top_left_y;
|
||||
self.viewport_width = viewport_width;
|
||||
self.viewport_height = viewport_height;
|
||||
}
|
||||
|
||||
pub fn get_viewport_size(&mut self) -> Option<(u32, u32, u32, u32)> {
|
||||
if self.viewport_resized {
|
||||
self.viewport_resized = false;
|
||||
Some((self.viewport_top_left_x, self.viewport_top_left_y, self.viewport_width, self.viewport_height))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub(crate) fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GraphicsState {
|
||||
surface: wgpu::Surface<'static>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
texture: Option<wgpu::Texture>,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
sampler: wgpu::Sampler,
|
||||
pub window: Arc<Window>,
|
||||
pub surface: wgpu::Surface<'static>,
|
||||
pub device: wgpu::Device,
|
||||
pub queue: wgpu::Queue,
|
||||
pub config: wgpu::SurfaceConfiguration,
|
||||
pub render_pipeline: wgpu::RenderPipeline,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl GraphicsState {
|
||||
pub(crate) async fn new(window: Arc<Window>) -> Self {
|
||||
pub(crate) async fn init(event_loop: &ActiveEventLoop) -> Self {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("CEF Offscreen Rendering Test")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(800, 600)),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let surface = instance.create_surface(window).unwrap();
|
||||
let surface = instance.create_surface(window.clone()).unwrap();
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
@@ -106,10 +195,61 @@ impl GraphicsState {
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
// Create shader module
|
||||
let shader = device.create_shader_module(wgpu::include_wgsl!("fullscreen_texture.wgsl"));
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
r#"
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let pos = array(
|
||||
// 1st triangle
|
||||
vec2f( -1.0, -1.0), // center
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
|
||||
// 2nd triangle
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( 1.0, 1.0), // right, top
|
||||
);
|
||||
let xy = pos[vertex_index];
|
||||
out.clip_position = vec4f(xy , 0.0, 1.0);
|
||||
let coords = (xy/ 2. + 0.5);
|
||||
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||
// // Generate a fullscreen triangle
|
||||
// let x = f32(i32(vertex_index) - 1);
|
||||
// let y = f32(i32(vertex_index & 1u) * 2 - 1);
|
||||
|
||||
// out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
|
||||
// out.tex_coords = vec2<f32>((x + 1.0) * 0.5, (1.0 - y) * 0.5);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Test: use texture coordinates as colors to debug
|
||||
// return vec4<f32>(in.tex_coords.x, in.tex_coords.y, 0.0, 1.0);
|
||||
// Uncomment this line to use CEF texture:
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
|
||||
// Create sampler
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
@@ -188,141 +328,23 @@ impl GraphicsState {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let mut graphics_state = Self {
|
||||
Self {
|
||||
window,
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
texture: None,
|
||||
bind_group: None,
|
||||
render_pipeline,
|
||||
sampler,
|
||||
};
|
||||
|
||||
// Initialize with a test pattern so we always have something to render
|
||||
let width = 800;
|
||||
let height = 600;
|
||||
let initial_data = vec![34u8; width * height * 4]; // Gray texture #222222FF
|
||||
|
||||
let fb = FrameBuffer::new(initial_data, width, height)
|
||||
.map_err(|e| {
|
||||
panic!("Failed to create initial FrameBuffer: {}", e);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
graphics_state.update_texture(&fb);
|
||||
|
||||
graphics_state
|
||||
}
|
||||
|
||||
pub(crate) fn resize(&mut self, width: usize, height: usize) {
|
||||
if width > 0 && height > 0 && (self.config.width != width as u32 || self.config.height != height as u32) {
|
||||
self.config.width = width as u32;
|
||||
self.config.height = height as u32;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) {
|
||||
let data = frame_buffer.buffer();
|
||||
let width = frame_buffer.width() as u32;
|
||||
let height = frame_buffer.height() as u32;
|
||||
|
||||
// Creates the cached ui texture, reconfigures the surface
|
||||
pub(crate) fn resize_surface(&mut self, width: u32, height: u32) {
|
||||
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {
|
||||
self.config.width = width;
|
||||
self.config.height = height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
|
||||
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
self.queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
data,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width),
|
||||
rows_per_image: Some(height),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.render_pipeline.get_bind_group_layout(0),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group"),
|
||||
});
|
||||
|
||||
self.texture = Some(texture);
|
||||
self.bind_group = Some(bind_group);
|
||||
}
|
||||
|
||||
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
if let Some(bind_group) = &self.bind_group {
|
||||
render_pass.set_bind_group(0, bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
|
||||
} else {
|
||||
println!("No bind group available - showing clear color only");
|
||||
}
|
||||
}
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
218
desktop/src/winit_app.rs
Normal file
218
desktop/src/winit_app.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::process::exit;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::*;
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
|
||||
use winit::window::WindowId;
|
||||
|
||||
use crate::cef::{Context, Initialized};
|
||||
use crate::render::{FrameBuffer, FrameBufferHandle, GraphicsState};
|
||||
|
||||
pub struct WinitApp {
|
||||
pub cef_context: Context<Initialized>,
|
||||
// Persistent initalized state when the window is created
|
||||
pub graphics_state: Option<GraphicsState>,
|
||||
|
||||
// Shared between winit and cef, and stores the state for the window size and ui frame buffer.
|
||||
// Automatically kept in sync with the width/height in graphics state surface config
|
||||
pub frame_buffer: FrameBufferHandle,
|
||||
|
||||
// Cached node graph output texture. And its position relative to the full ui
|
||||
pub viewport_top_left: u32,
|
||||
pub viewport_top_right: u32,
|
||||
pub viewport_texture: Option<wgpu::Texture>,
|
||||
pub viewport_bind_group: Option<wgpu::BindGroup>,
|
||||
|
||||
// Cached UI texture and bindgroup for the CEF overlay
|
||||
pub ui_texture: Option<wgpu::Texture>,
|
||||
pub ui_bind_group: Option<wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl WinitApp {
|
||||
pub fn new(cef_context: Context<Initialized>, frame_buffer: FrameBufferHandle) -> Self {
|
||||
Self {
|
||||
cef_context,
|
||||
graphics_state: None,
|
||||
frame_buffer,
|
||||
viewport_top_left: 0,
|
||||
viewport_top_right: 0,
|
||||
viewport_texture: None,
|
||||
viewport_bind_group: None,
|
||||
ui_texture: None,
|
||||
ui_bind_group: None,
|
||||
}
|
||||
}
|
||||
|
||||
// The single entrypoint for window resizing. It updates the frame buffer, surface config, cached UI overlay texture, and clears the viewport texture
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
if let Some(graphics_state) = &mut self.graphics_state {
|
||||
// Updates the surface config
|
||||
graphics_state.resize_surface(width, height);
|
||||
// Creates the cached ui texture, reconfigures the surface, and updates the frame buffer
|
||||
self.ui_texture = Some(graphics_state.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
}));
|
||||
|
||||
self.ui_bind_group = Some(graphics_state.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &graphics_state.render_pipeline.get_bind_group_layout(0),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(self.ui_texture.as_ref().unwrap()),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&graphics_state.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group"),
|
||||
}));
|
||||
|
||||
// Invalidate the viewport texture, since we need to wait until cef calls on paint so we can get the new viewport size, which always changes when the window changes
|
||||
self.viewport_bind_group = None;
|
||||
self.viewport_texture = None;
|
||||
}
|
||||
|
||||
// Keep the frame buffer in sync
|
||||
self.frame_buffer.lock().unwrap().resize(width, height);
|
||||
|
||||
if let Some(browser) = &self.cef_context.browser {
|
||||
browser.host().unwrap().was_resized();
|
||||
}
|
||||
}
|
||||
// Composites the cached ui overlay texture onto the cached node graph texture. This should be called when the DOM or node graph gets evaluated.
|
||||
pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let Some(graphics_state) = &mut self.graphics_state else {
|
||||
println!("Graphics state not initialized in render function");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let output = graphics_state.surface.get_current_texture()?;
|
||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = graphics_state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0 }),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&graphics_state.render_pipeline);
|
||||
|
||||
if let Some(node_graph_bind_group) = &self.viewport_bind_group {
|
||||
render_pass.set_bind_group(0, node_graph_bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
if let Some(ui_bind_group) = &self.ui_bind_group {
|
||||
render_pass.set_bind_group(0, ui_bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
} else {
|
||||
println!("No bind group available for ui overlay");
|
||||
}
|
||||
}
|
||||
|
||||
graphics_state.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Loads the framebuffer data from CEF into the texture if it changed
|
||||
pub fn try_load_frame_buffer(&mut self) -> Result<(), String> {
|
||||
// Load the data from the shared frame buffer to the texture
|
||||
if let Some((new_buffer, width, height)) = self.frame_buffer.inner.lock().unwrap().take_buffer() {
|
||||
let Some(cached_ui_texture) = &self.ui_texture else {
|
||||
return Err("UI texture must be initialzed before loading framebuffer data".to_string());
|
||||
};
|
||||
let Some(graphics_state) = &mut self.graphics_state else {
|
||||
return Err("graphics state must exist before loading framebuffer data".to_string());
|
||||
};
|
||||
graphics_state.queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &cached_ui_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
new_buffer,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width),
|
||||
rows_per_image: Some(height),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Load the viewport texture and keep its position in sync with the browser viewport
|
||||
pub fn try_load_viewport(&mut self) -> Result<(), String> {
|
||||
let Some(graphics_state) = &mut self.graphics_state else {
|
||||
println!("Graphics state not initialized in try_load_viewport");
|
||||
return Ok(());
|
||||
};
|
||||
// Only runs if the viewport changes, in which case the cached texture should be recreated
|
||||
if let Some((top_left, top_right, width, height)) = self.frame_buffer.inner.lock().unwrap().get_viewport_size() {
|
||||
self.viewport_top_left = top_left;
|
||||
self.viewport_top_right = top_right;
|
||||
|
||||
self.ui_texture = Some(graphics_state.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Vello Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
// Based on Vello requirements https://github.com/linebender/vello/blob/daf940230a24cbb123a458b6de95721af47aef98/vello/src/lib.rs#L460C36-L460C46
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::STORAGE_BINDING,
|
||||
view_formats: &[],
|
||||
}));
|
||||
|
||||
self.ui_bind_group = Some(graphics_state.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &graphics_state.render_pipeline.get_bind_group_layout(0),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(self.ui_texture.as_ref().unwrap()),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&graphics_state.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
24
frontend-native/.gitignore
vendored
Normal file
24
frontend-native/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
frontend-native/.vscode/extensions.json
vendored
Normal file
3
frontend-native/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
47
frontend-native/README.md
Normal file
47
frontend-native/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Svelte + TS + Vite
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
13
frontend-native/index.html
Normal file
13
frontend-native/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Svelte + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1417
frontend-native/package-lock.json
generated
Normal file
1417
frontend-native/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
frontend-native/package.json
Normal file
20
frontend-native/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "native-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"svelte": "^5.35.5",
|
||||
"svelte-check": "^4.2.2",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
1
frontend-native/public/vite.svg
Normal file
1
frontend-native/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
47
frontend-native/src/App.svelte
Normal file
47
frontend-native/src/App.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import svelteLogo from './assets/svelte.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import Counter from './lib/Counter.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank" rel="noreferrer">
|
||||
<img src={viteLogo} class="logo" alt="Vite Logo" />
|
||||
</a>
|
||||
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
|
||||
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + Svelte</h1>
|
||||
|
||||
<div class="card">
|
||||
<Counter />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
|
||||
</p>
|
||||
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and Svelte logos to learn more
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.svelte:hover {
|
||||
filter: drop-shadow(0 0 2em #ff3e00aa);
|
||||
}
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
79
frontend-native/src/app.css
Normal file
79
frontend-native/src/app.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
1
frontend-native/src/assets/svelte.svg
Normal file
1
frontend-native/src/assets/svelte.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
10
frontend-native/src/lib/Counter.svelte
Normal file
10
frontend-native/src/lib/Counter.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
let count: number = $state(0)
|
||||
const increment = () => {
|
||||
count += 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={increment}>
|
||||
count is {count}
|
||||
</button>
|
||||
9
frontend-native/src/main.ts
Normal file
9
frontend-native/src/main.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { mount } from 'svelte'
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
|
||||
export default app
|
||||
2
frontend-native/src/vite-env.d.ts
vendored
Normal file
2
frontend-native/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
7
frontend-native/svelte.config.js
Normal file
7
frontend-native/svelte.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
20
frontend-native/tsconfig.app.json
Normal file
20
frontend-native/tsconfig.app.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
7
frontend-native/tsconfig.json
Normal file
7
frontend-native/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
25
frontend-native/tsconfig.node.json
Normal file
25
frontend-native/tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
frontend-native/vite.config.ts
Normal file
7
frontend-native/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
})
|
||||
@@ -4,6 +4,7 @@
|
||||
"scripts": {
|
||||
"---------- DEV SERVER ----------": "",
|
||||
"start": "cd frontend && npm start",
|
||||
"start-desktop": "cd frontend-native && npm run build && cargo run -p graphite-desktop",
|
||||
"profiling": "cd frontend && npm run profiling",
|
||||
"production": "cd frontend && npm run production",
|
||||
"---------- BUILDS ----------": "",
|
||||
|
||||
Reference in New Issue
Block a user