diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index 8234147..42c02c3 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -17,7 +17,7 @@ mod error; #[unsafe(no_mangle)] pub extern "C" fn processing_init() { error::clear_error(); - error::check(init); + error::check(|| init(Config::default())); } /// Create a WebGPU surface from a macOS NSWindow handle. diff --git a/crates/processing_pyo3/examples/background_image.py b/crates/processing_pyo3/examples/background_image.py new file mode 100644 index 0000000..632dcd9 --- /dev/null +++ b/crates/processing_pyo3/examples/background_image.py @@ -0,0 +1,12 @@ +from processing import * + +def setup(): + size(800, 600) + +def draw(): + background(220) + image("images/logo.png") + + +# TODO: this should happen implicitly on module load somehow +run() diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index f777edc..5c4c173 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -38,11 +38,13 @@ impl Drop for Graphics { #[pymethods] impl Graphics { #[new] - pub fn new(width: u32, height: u32) -> PyResult { + pub fn new(width: u32, height: u32, asset_path: &str) -> PyResult { let glfw_ctx = GlfwContext::new(width, height).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; - init().map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + let mut config = Config::new(); + config.set(ConfigKey::AssetRootPath, asset_path.to_string()); + init(config).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let surface = glfw_ctx .create_surface(width, height, 1.0) @@ -122,6 +124,12 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } + pub fn image(&self, file: &str) -> PyResult<()> { + let image = image_load(file).unwrap(); + graphics_record_command(self.entity, DrawCommand::BackgroundImage(image)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + pub fn push_matrix(&self) -> PyResult<()> { graphics_record_command(self.entity, DrawCommand::PushMatrix) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 432c433..258573e 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -7,7 +7,7 @@ //! receiver. //! //! To allow Python users to create a similar experience, we provide module-level -//! functions that forward to a singleton Graphics object bepub(crate) pub(crate) hind the scenes. +//! functions that forward to a singleton Graphics object pub(crate) behind the scenes. mod glfw; mod graphics; @@ -16,23 +16,37 @@ use pyo3::{exceptions::PyRuntimeError, prelude::*}; #[pymodule] fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_function(wrap_pyfunction!(size, m)?)?; - m.add_function(wrap_pyfunction!(run, m)?)?; - m.add_function(wrap_pyfunction!(background, m)?)?; - m.add_function(wrap_pyfunction!(fill, m)?)?; - m.add_function(wrap_pyfunction!(no_fill, m)?)?; - m.add_function(wrap_pyfunction!(stroke, m)?)?; - m.add_function(wrap_pyfunction!(no_stroke, m)?)?; - m.add_function(wrap_pyfunction!(stroke_weight, m)?)?; - m.add_function(wrap_pyfunction!(rect, m)?)?; - Ok(()) + Python::attach(|py| { + let sys = PyModule::import(py, "sys")?; + let argv: Vec = sys.getattr("argv")?.extract()?; + let filename: &str = argv[0].as_str(); + let os = PyModule::import(py, "os")?; + let path = os.getattr("path")?; + let dirname = path.getattr("dirname")?.call1((filename,))?; + let abspath = path.getattr("abspath")?.call1((dirname,))?; + let abspath = path.getattr("join")?.call1((abspath, "assets"))?; + + m.add("_root_dir", abspath)?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(size, m)?)?; + m.add_function(wrap_pyfunction!(run, m)?)?; + m.add_function(wrap_pyfunction!(background, m)?)?; + m.add_function(wrap_pyfunction!(fill, m)?)?; + m.add_function(wrap_pyfunction!(no_fill, m)?)?; + m.add_function(wrap_pyfunction!(stroke, m)?)?; + m.add_function(wrap_pyfunction!(no_stroke, m)?)?; + m.add_function(wrap_pyfunction!(stroke_weight, m)?)?; + m.add_function(wrap_pyfunction!(rect, m)?)?; + m.add_function(wrap_pyfunction!(image, m)?)?; + Ok(()) + }) } #[pyfunction] #[pyo3(pass_module)] fn size(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult<()> { - let graphics = Graphics::new(width, height)?; + let asset_path: String = module.getattr("_root_dir")?.extract()?; + let graphics = Graphics::new(width, height, asset_path.as_str())?; module.setattr("_graphics", graphics)?; Ok(()) } @@ -122,3 +136,9 @@ fn rect( ) -> PyResult<()> { get_graphics(module)?.rect(x, y, w, h, tl, tr, br, bl) } + +#[pyfunction] +#[pyo3(pass_module, signature = (image_file))] +fn image(module: &Bound<'_, PyModule>, image_file: &str) -> PyResult<()> { + get_graphics(module)?.image(image_file) +} diff --git a/crates/processing_render/Cargo.toml b/crates/processing_render/Cargo.toml index 72272e3..91c34d8 100644 --- a/crates/processing_render/Cargo.toml +++ b/crates/processing_render/Cargo.toml @@ -32,4 +32,4 @@ windows = { version = "0.58", features = ["Win32_Foundation", "Win32_System_Libr wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" -web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement"] } \ No newline at end of file +web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement"] } diff --git a/crates/processing_render/src/config.rs b/crates/processing_render/src/config.rs new file mode 100644 index 0000000..75766a1 --- /dev/null +++ b/crates/processing_render/src/config.rs @@ -0,0 +1,48 @@ +//! Options object for configuring various aspects of libprocessing. +//! +//! To add a new Config just add a new enum with associated value + +use bevy::prelude::Resource; +use std::collections::HashMap; + +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum ConfigKey { + AssetRootPath, +} + +// TODO: Consider Box instead of String +#[derive(Resource)] +pub struct Config { + map: HashMap, +} + +impl Clone for Config { + fn clone(&self) -> Self { + Config { + map: self.map.clone(), + } + } +} + +impl Config { + pub fn new() -> Self { + // TODO consider defaults + Config { + map: HashMap::new(), + } + } + + pub fn get(&self, k: ConfigKey) -> Option<&String> { + self.map.get(&k) + } + + pub fn set(&mut self, k: ConfigKey, v: String) { + self.map.insert(k, v); + } +} + +impl Default for Config { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/processing_render/src/image.rs b/crates/processing_render/src/image.rs index b038087..5b668eb 100644 --- a/crates/processing_render/src/image.rs +++ b/crates/processing_render/src/image.rs @@ -5,7 +5,8 @@ use std::path::PathBuf; use bevy::{ asset::{ - LoadState, RenderAssetUsages, handle_internal_asset_events, io::embedded::GetAssetServer, + AssetPath, LoadState, RenderAssetUsages, handle_internal_asset_events, + io::{AssetSourceId, embedded::GetAssetServer}, }, ecs::{entity::EntityHashMap, system::RunSystemOnce}, prelude::*, @@ -23,6 +24,7 @@ use bevy::{ }; use half::f16; +use crate::config::{Config, ConfigKey}; use crate::error::{ProcessingError, Result}; pub struct ImagePlugin; @@ -138,7 +140,16 @@ pub fn from_handle( } pub fn load(In(path): In, world: &mut World) -> Result { + let config = world.resource_mut::(); + let path: AssetPath = match config.get(ConfigKey::AssetRootPath) { + Some(_) => { + AssetPath::from_path_buf(path).with_source(AssetSourceId::from("assets_directory")) + } + None => AssetPath::from_path_buf(path), + }; + let handle: Handle = world.get_asset_server().load(path); + while let LoadState::Loading = world.get_asset_server().load_state(&handle) { world.run_system_once(handle_internal_asset_events).unwrap(); } diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 2beef68..be162a4 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod error; mod graphics; pub mod image; @@ -6,11 +7,13 @@ mod surface; use std::{cell::RefCell, num::NonZero, path::PathBuf, sync::OnceLock}; +use config::*; + #[cfg(not(target_arch = "wasm32"))] use bevy::log::tracing_subscriber; use bevy::{ app::{App, AppExit}, - asset::AssetEventSystems, + asset::{AssetEventSystems, io::AssetSourceBuilder}, prelude::*, render::render_resource::{Extent3d, TextureFormat}, }; @@ -202,9 +205,11 @@ pub fn surface_resize(graphics_entity: Entity, width: u32, height: u32) -> error }) } -fn create_app() -> App { +fn create_app(config: Config) -> App { let mut app = App::new(); + app.insert_resource(config.clone()); + #[cfg(not(target_arch = "wasm32"))] let plugins = DefaultPlugins .build() @@ -227,6 +232,13 @@ fn create_app() -> App { ..default() }); + if let Some(asset_path) = config.get(ConfigKey::AssetRootPath) { + app.register_asset_source( + "assets_directory", + AssetSourceBuilder::platform_default(asset_path, None), + ); + } + app.add_plugins(plugins); app.add_plugins((ImagePlugin, GraphicsPlugin, SurfacePlugin)); app.add_systems(First, (clear_transient_meshes, activate_cameras)) @@ -258,13 +270,13 @@ fn set_app(app: App) { /// Initialize the app, if not already initialized. Must be called from the main thread and cannot /// be called concurrently from multiple threads. #[cfg(not(target_arch = "wasm32"))] -pub fn init() -> error::Result<()> { +pub fn init(config: Config) -> error::Result<()> { setup_tracing()?; if is_already_init()? { return Ok(()); } - let mut app = create_app(); + let mut app = create_app(config); app.finish(); app.cleanup(); set_app(app); diff --git a/examples/background_image.rs b/examples/background_image.rs index 2f98eb6..1ae3711 100644 --- a/examples/background_image.rs +++ b/examples/background_image.rs @@ -19,7 +19,7 @@ fn main() { fn sketch() -> error::Result<()> { let mut glfw_ctx = GlfwContext::new(400, 400)?; - init()?; + init(Config::default())?; let width = 400; let height = 400; diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 9e372e1..9a28275 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -19,7 +19,7 @@ fn main() { fn sketch() -> error::Result<()> { let mut glfw_ctx = GlfwContext::new(400, 400)?; - init()?; + init(Config::default())?; let width = 400; let height = 400; diff --git a/examples/transforms.rs b/examples/transforms.rs index 2cecaa8..4d80f41 100644 --- a/examples/transforms.rs +++ b/examples/transforms.rs @@ -13,7 +13,7 @@ fn main() { fn sketch() -> error::Result<()> { let mut glfw_ctx = GlfwContext::new(400, 400)?; - init()?; + init(Config::default())?; let surface = glfw_ctx.create_surface(400, 400, 1.0)?; let graphics = graphics_create(surface, 400, 400)?; diff --git a/examples/update_pixels.rs b/examples/update_pixels.rs index 60c7de9..01638a7 100644 --- a/examples/update_pixels.rs +++ b/examples/update_pixels.rs @@ -19,7 +19,7 @@ fn main() { fn sketch() -> error::Result<()> { let mut glfw_ctx = GlfwContext::new(100, 100)?; - init()?; + init(Config::default())?; let width = 100; let height = 100; diff --git a/src/prelude.rs b/src/prelude.rs index c3ba153..312acb0 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,2 +1,2 @@ pub use bevy::prelude::default; -pub use processing_render::{render::command::DrawCommand, *}; +pub use processing_render::{config::*, render::command::DrawCommand, *};