//! # processing_pyo3 //! //! A Python module that exposes libprocessing using pyo3. //! In processing4 Java, the sketch runs implicitly inside a class that extends PApplet and //! executes main. This means that all PAplet methods can be called directly without an explicit //! receiver. //! //! To allow Python users to create a similar experience, we provide module-level //! functions that forward to a singleton Graphics object pub(crate) behind the scenes. pub(crate) mod color; pub(crate) mod compute; mod constants; #[cfg(feature = "cuda")] pub(crate) mod cuda; pub(crate) mod filter; mod glfw; mod gltf; mod graphics; mod host; mod input; pub(crate) mod material; pub(crate) mod math; mod midi; mod monitor; pub(crate) mod particles; pub(crate) mod shader; mod surface; mod time; #[cfg(feature = "webcam")] mod webcam; use compute::{Buffer, Compute}; use graphics::{ Font, Geometry, Graphics, Image, Light, PyBlendMode, Sampler, get_graphics, get_graphics_mut, }; use material::Material; use pyo3::{ BoundObject, exceptions::PyRuntimeError, prelude::*, types::{PyDict, PyList, PyTuple}, }; use shader::Shader; use std::ffi::{CStr, CString}; /// Register a window `Graphics` in the module's `_windows` list so the run loop /// draws + presents it each frame. fn register_window(module: &Bound<'_, PyModule>, window: &Py) -> PyResult<()> { let list = match module.getattr("_windows") { Ok(existing) if !existing.is_none() => existing .cast_into::() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?, _ => { let list = PyList::empty(module.py()); module.setattr("_windows", &list)?; list } }; list.append(window)?; Ok(()) } /// All window `Graphics` the run loop should drive: the main canvas (`_graphics`) /// followed by any `create_window` results (`_windows`). fn collect_windows(module: &Bound<'_, PyModule>) -> PyResult>> { let mut out = Vec::new(); if let Ok(main) = module.getattr("_graphics") && !main.is_none() { out.push( main.cast_into::() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))? .unbind(), ); } if let Ok(windows) = module.getattr("_windows") && let Ok(list) = windows.cast_into::() { for item in list.iter() { out.push( item.cast_into::() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))? .unbind(), ); } } Ok(out) } /// Replace the first run of `#` characters in `pattern` with a zero-padded frame /// number (Processing `saveFrame` semantics, e.g. `frame-####.png`). fn substitute_frame_number(pattern: &str, n: u32) -> String { match pattern.find('#') { Some(start) => { let hashes = pattern[start..].chars().take_while(|c| *c == '#').count(); format!( "{}{:0width$}{}", &pattern[..start], n, &pattern[start + hashes..], width = hashes ) } None => pattern.to_string(), } } use bevy::log::warn; use gltf::Gltf; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::env; #[derive(Clone, Copy)] pub(crate) struct LoopState { looping: bool, redraw_requested: bool, } impl Default for LoopState { fn default() -> Self { Self { looping: true, redraw_requested: false, } } } thread_local! { pub(crate) static LAST_GLOBALS: RefCell>> = RefCell::new(HashMap::new()); pub(crate) static LOOP_STATE: Cell = Cell::new(LoopState::default()); } fn update_loop_state(f: impl FnOnce(&mut LoopState)) { LOOP_STATE.with(|s| { let mut state = s.get(); f(&mut state); s.set(state); }); } /// Writes a new value to globals, iff the new value does not match a previous tracked value. pub(crate) fn set_tracked<'py, V>( globals: &Bound<'py, PyAny>, name: &'static str, new_value: V, ) -> PyResult<()> where V: IntoPyObject<'py>, PyErr: From, { let py = globals.py(); let owned: Py = new_value.into_pyobject(py)?.into_any().unbind(); let user_shadowed = LAST_GLOBALS.with(|cache| -> PyResult { let cache = cache.borrow(); let Some(last) = cache.get(name) else { return Ok(false); }; match globals.get_item(name) { Ok(current) => Ok(!current.eq(last.bind(py))?), // key isn't in globals, either because the dict is fresh (livecode reload etc) // or the user deleted it so we can safely repopulate Err(_) => Ok(false), } })?; if !user_shadowed { globals.set_item(name, owned.clone_ref(py))?; LAST_GLOBALS.with(|cache| { cache.borrow_mut().insert(name, owned); }); } Ok(()) } pub(crate) fn reset_tracked_globals() { LAST_GLOBALS.with(|cache| cache.borrow_mut().clear()); } fn canvas_size(graphics: &Graphics) -> PyResult<(u32, u32)> { let err = |e: processing::prelude::error::ProcessingError| PyRuntimeError::new_err(format!("{e}")); let width = ::processing::prelude::surface_width(graphics.surface.entity).map_err(err)?; let height = ::processing::prelude::surface_height(graphics.surface.entity).map_err(err)?; Ok(( if width == 0 { graphics.width } else { width }, if height == 0 { graphics.height } else { height }, )) } fn sync_globals(module: &Bound<'_, PyModule>, globals: &Bound<'_, PyAny>) -> PyResult<()> { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let (width, height) = canvas_size(&graphics)?; input::sync_globals(globals, graphics.surface.entity, width, height)?; surface::sync_globals(globals, &graphics.surface, width, height)?; time::sync_globals(globals)?; Ok(()) } fn mouse_event(module: &Bound<'_, PyModule>) -> PyResult> { // the graphics borrow has to end before the callback runs, which may draw let event = { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let (width, height) = canvas_size(&graphics)?; input::MouseEvent::current(graphics.surface.entity, width, height)? }; Ok(Py::new(module.py(), event)?.into_any()) } fn key_event(module: &Bound<'_, PyModule>) -> PyResult> { Ok(Py::new(module.py(), input::KeyEvent::current()?)?.into_any()) } /// Calls the sketch's `name` callback, with an event if it takes a parameter /// (`def mouse_pressed(e)`), as py5 and Processing's `mousePressed(MouseEvent)` do. fn call_event_callback( module: &Bound<'_, PyModule>, locals: &Bound<'_, PyAny>, name: &str, event: fn(&Bound<'_, PyModule>) -> PyResult>, ) -> PyResult<()> { let Ok(cb) = locals.get_item(name) else { return Ok(()); }; if !cb.is_callable() { return Ok(()); } let inspect = PyModule::import(module.py(), "inspect")?; // builtins without a signature can't take our event either let takes_event = inspect .call_method1("signature", (&cb,)) .and_then(|sig| sig.getattr("parameters")?.len()) .is_ok_and(|n| n > 0); if takes_event { cb.call1((event(module)?,))?; } else { cb.call0()?; } Ok(()) } fn dispatch_event_callbacks( module: &Bound<'_, PyModule>, locals: &Bound<'_, PyAny>, ) -> PyResult<()> { use processing::prelude::*; let err = |e: processing::prelude::error::ProcessingError| PyRuntimeError::new_err(format!("{e}")); if input_mouse_any_just_pressed().map_err(err)? { call_event_callback(module, locals, "mouse_pressed", mouse_event)?; } if input_mouse_any_just_released().map_err(err)? { call_event_callback(module, locals, "mouse_released", mouse_event)?; } if input_mouse_moved().map_err(err)? { if input_mouse_is_pressed().map_err(err)? { call_event_callback(module, locals, "mouse_dragged", mouse_event)?; } else { call_event_callback(module, locals, "mouse_moved", mouse_event)?; } } if input_mouse_scrolled().map_err(err)? { call_event_callback(module, locals, "mouse_wheel", mouse_event)?; } if input_key_any_just_pressed().map_err(err)? { call_event_callback(module, locals, "key_pressed", key_event)?; } if input_key_any_just_released().map_err(err)? { call_event_callback(module, locals, "key_released", key_event)?; } Ok(()) } fn create_graphics_context( module: &Bound<'_, PyModule>, width: u32, height: u32, transparent: bool, ) -> PyResult<()> { let py = module.py(); let env = detect_environment(module)?; let interactive = env != "script"; let log_level = if interactive { Some("error") } else { None }; let has_existing = module .getattr("_graphics") .ok() .map(|a| !a.is_none()) .unwrap_or(false); if has_existing { module.setattr("_graphics", py.None())?; } match env.as_str() { "embedded" => { let asset_path = match embedded_asset_root(module)? { Some(root) => root, None => get_asset_root()?, }; let hdr = embedded_hdr(module)?; let host_log_level = embedded_log_level(module)?; let log_level = host_log_level.as_deref().or(log_level); let graphics = Graphics::new_offscreen(width, height, asset_path.as_str(), log_level, hdr)?; module.setattr("_graphics", graphics)?; } "jupyter" => { let asset_path = get_asset_root()?; let graphics = Graphics::new_offscreen(width, height, asset_path.as_str(), log_level, false)?; module.setattr("_graphics", graphics)?; if !has_existing { let code = CString::new(JUPYTER_POST_EXECUTE_CODE)?; py.run(code.as_c_str(), None, None).map_err(|e| { PyRuntimeError::new_err(format!("Failed to register Jupyter hooks: {e}")) })?; } } "ipython" => { let asset_path = get_asset_root()?; let (sketch_root, sketch_file) = get_sketch_info()?; let graphics = Graphics::new( width, height, asset_path.as_str(), sketch_root.as_str(), sketch_file.as_str(), log_level, transparent, )?; module.setattr("_graphics", graphics)?; if !has_existing { let hook_code = CString::new(REGISTER_INPUTHOOK_CODE)?; py.run(hook_code.as_c_str(), None, None).map_err(|e| { PyRuntimeError::new_err(format!("Failed to register inputhook: {e}")) })?; let post_code = CString::new(IPYTHON_POST_EXECUTE_CODE)?; py.run(post_code.as_c_str(), None, None).map_err(|e| { PyRuntimeError::new_err(format!("Failed to register post-execute hook: {e}")) })?; } } _ => { let asset_path = get_asset_root()?; let (sketch_root, sketch_file) = get_sketch_info()?; let graphics = Graphics::new( width, height, asset_path.as_str(), sketch_root.as_str(), sketch_file.as_str(), log_level, transparent, )?; module.setattr("_graphics", graphics)?; } } Ok(()) } const DEFAULT_WIDTH: u32 = 100; const DEFAULT_HEIGHT: u32 = 100; fn ensure_graphics(module: &Bound<'_, PyModule>) -> PyResult<()> { if get_graphics(module)?.is_some() { return Ok(()); } create_graphics_context(module, DEFAULT_WIDTH, DEFAULT_HEIGHT, false) } macro_rules! graphics { ($module:expr) => {{ ensure_graphics($module)?; get_graphics($module)?.expect("ensure_graphics guarantees Some") }}; } fn get_asset_root() -> PyResult { if let Ok(val) = env::var("PROCESSING_ASSET_ROOT") { return Ok(val); } Python::attach(|py| { let sys = PyModule::import(py, "sys")?; let argv: Vec = sys.getattr("argv")?.extract()?; let filename = argv.first().map(|s| s.as_str()).unwrap_or(""); let os = PyModule::import(py, "os")?; let path = os.getattr("path")?; // in ipython/jupyter argv[0] is weird so we use cwd // todo: what is the correct way to get notebook path if filename.is_empty() || !path.getattr("isfile")?.call1((filename,))?.is_truthy()? { return Ok(os.getattr("getcwd")?.call0()?.to_string()); } let dirname = path.getattr("dirname")?.call1((filename,))?; Ok(path.getattr("abspath")?.call1((dirname,))?.to_string()) }) } fn get_sketch_info() -> PyResult<(String, String)> { Python::attach(|py| { let sys = PyModule::import(py, "sys")?; let argv: Vec = sys.getattr("argv")?.extract()?; let filename = argv.first().map(|s| s.as_str()).unwrap_or(""); let os = PyModule::import(py, "os")?; let path = os.getattr("path")?; if filename.is_empty() || !path.getattr("isfile")?.call1((filename,))?.is_truthy()? { let cwd = os.getattr("getcwd")?.call0()?.to_string(); return Ok((cwd, String::new())); } let dirname = path.getattr("dirname")?.call1((filename,))?; let abspath = path.getattr("abspath")?.call1((dirname,))?; let basename = path.getattr("basename")?.call1((filename,))?; Ok((abspath.to_string(), basename.to_string())) }) } const DETECT_ENV_CODE: &str = include_str!("python/detect_env.py"); const REGISTER_INPUTHOOK_CODE: &str = include_str!("python/register_inputhook.py"); const IPYTHON_POST_EXECUTE_CODE: &str = include_str!("python/ipython_post_execute.py"); const JUPYTER_POST_EXECUTE_CODE: &str = include_str!("python/jupyter_post_execute.py"); fn embedded_asset_root(module: &Bound<'_, PyModule>) -> PyResult> { match module.getattr("_asset_root") { Ok(attr) if !attr.is_none() => Ok(Some(attr.extract::()?)), _ => Ok(None), } } fn embedded_log_level(module: &Bound<'_, PyModule>) -> PyResult> { match module.getattr("_embedded_log_level") { Ok(attr) if !attr.is_none() => Ok(Some(attr.extract::()?)), _ => Ok(None), } } fn embedded_hdr(module: &Bound<'_, PyModule>) -> PyResult { match module.getattr("_embedded_hdr") { Ok(attr) if !attr.is_none() => attr.is_truthy(), _ => Ok(false), } } fn detect_environment(module: &Bound<'_, PyModule>) -> PyResult { let py = module.py(); if let Ok(flag) = module.getattr("_embedded") && flag.is_truthy()? { return Ok("embedded".to_string()); } let locals = PyDict::new(py); let code = CString::new(DETECT_ENV_CODE)?; py.run(code.as_c_str(), None, Some(&locals))?; locals .get_item("_env")? .ok_or_else(|| PyRuntimeError::new_err("Failed to detect environment"))? .extract() } #[pymodule] pub mod mewnala { use super::*; #[pymodule_export] use super::Buffer; #[pymodule_export] use super::Compute; #[pymodule_export] use super::Font; #[pymodule_export] use super::Geometry; #[pymodule_export] use super::Gltf; #[pymodule_export] use super::Graphics; #[pymodule_export] use super::Image; #[pymodule_export] use super::Light; #[pymodule_export] use super::Material; #[pymodule_export] use super::PyBlendMode; #[pymodule_export] use super::Sampler; #[pymodule_export] use super::Shader; #[pymodule_export] use super::color::PyColor; #[cfg(feature = "cuda")] #[pymodule_export] use super::cuda::CudaImage; #[pymodule_export] use super::input::KeyEvent; #[pymodule_export] use super::input::MouseEvent; #[pymodule_export] use super::math::PyQuat; #[pymodule_export] use super::math::PyVec2; #[pymodule_export] use super::math::PyVec3; #[pymodule_export] use super::math::PyVec4; #[pymodule_export] use super::monitor::Monitor; #[pymodule_export] use super::particles::Attribute; #[pymodule_export] use super::particles::AttributeFormat; #[pymodule_export] use super::particles::Grid; #[pymodule_export] use super::particles::Particles; #[pymodule_export] use super::surface::Surface; #[pymodule_init] fn init(module: &Bound<'_, PyModule>) -> PyResult<()> { if processing_render::ci::enabled() { PyModule::import(module.py(), "random")?.call_method1("seed", (0,))?; } super::constants::register(module) } #[pymodule] mod math { use super::*; #[pymodule_export] use crate::math::PyAffine2; #[pymodule_export] use crate::math::PyMat2; #[pymodule_export] use crate::math::PyQuat; #[pymodule_export] use crate::math::PyVec2; #[pymodule_export] use crate::math::PyVec3; #[pymodule_export] use crate::math::PyVec4; #[pymodule_export] use crate::math::PyVecIter; #[pyfunction] #[pyo3(signature = (*args))] fn vec2(args: &Bound<'_, PyTuple>) -> PyResult { PyVec2::py_new(args) } #[pyfunction] #[pyo3(signature = (*args))] fn vec3(args: &Bound<'_, PyTuple>) -> PyResult { PyVec3::py_new(args) } #[pyfunction] #[pyo3(signature = (*args))] fn vec4(args: &Bound<'_, PyTuple>) -> PyResult { PyVec4::py_new(args) } #[pyfunction] #[pyo3(signature = (*args))] fn quat(args: &Bound<'_, PyTuple>) -> PyResult { PyQuat::py_new(args) } } // top-level so `from mewnala import *` exposes hsva/srgb/etc. directly; // a `color` submodule would clash with the `color()` function #[pyfunction] fn color_hex(s: &str) -> PyResult { PyColor::hex(s) } #[pyfunction] #[pyo3(signature = (r, g, b, a=1.0))] fn srgb(r: f32, g: f32, b: f32, a: f32) -> PyColor { PyColor::srgb(r, g, b, a) } #[pyfunction] #[pyo3(signature = (r, g, b, a=1.0))] fn linear_rgb(r: f32, g: f32, b: f32, a: f32) -> PyColor { PyColor::linear(r, g, b, a) } #[pyfunction] #[pyo3(signature = (h, s, l, a=1.0))] fn hsla(h: f32, s: f32, l: f32, a: f32) -> PyColor { PyColor::hsla(h, s, l, a) } #[pyfunction] #[pyo3(signature = (h, s, v, a=1.0))] fn hsva(h: f32, s: f32, v: f32, a: f32) -> PyColor { PyColor::hsva(h, s, v, a) } #[pyfunction] #[pyo3(signature = (h, w, b, a=1.0))] fn hwba(h: f32, w: f32, b: f32, a: f32) -> PyColor { PyColor::hwba(h, w, b, a) } #[pyfunction] #[pyo3(signature = (l, a_axis, b_axis, alpha=1.0))] fn oklab(l: f32, a_axis: f32, b_axis: f32, alpha: f32) -> PyColor { PyColor::oklab(l, a_axis, b_axis, alpha) } #[pyfunction] #[pyo3(signature = (l, c, h, a=1.0))] fn oklch(l: f32, c: f32, h: f32, a: f32) -> PyColor { PyColor::oklch(l, c, h, a) } #[pyfunction] #[pyo3(signature = (l, a_axis, b_axis, alpha=1.0))] fn lab(l: f32, a_axis: f32, b_axis: f32, alpha: f32) -> PyColor { PyColor::lab(l, a_axis, b_axis, alpha) } #[pyfunction] #[pyo3(signature = (l, c, h, a=1.0))] fn lch(l: f32, c: f32, h: f32, a: f32) -> PyColor { PyColor::lch(l, c, h, a) } #[pyfunction] #[pyo3(signature = (x, y, z, a=1.0))] fn xyz(x: f32, y: f32, z: f32, a: f32) -> PyColor { PyColor::xyz(x, y, z, a) } #[cfg(feature = "webcam")] #[pymodule_export] use super::webcam::Webcam; #[pyfunction] #[pyo3(pass_module)] fn load_gltf(module: &Bound<'_, PyModule>, path: &str) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let entity = ::processing::prelude::gltf_load(graphics.entity, path) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; Ok(Gltf::from_entity(entity)) } #[pyfunction] #[pyo3(pass_module)] fn _poll_events(module: &Bound<'_, PyModule>) -> PyResult { let Some(mut graphics) = get_graphics_mut(module)? else { return Ok(true); }; Ok(graphics.surface.poll_events()) } #[pyfunction] #[pyo3(pass_module)] fn _begin_draw(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).begin_draw() } #[pyfunction] #[pyo3(pass_module)] fn _end_draw(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).end_draw() } #[pyfunction] #[pyo3(pass_module)] fn _present(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).present() } #[pyfunction] #[pyo3(pass_module)] fn _readback_png(module: &Bound<'_, PyModule>) -> PyResult>> { let Some(graphics) = get_graphics(module)? else { return Ok(None); }; graphics.readback_png().map(Some) } #[pyfunction] #[pyo3(pass_module)] fn flush(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).flush() } #[cfg(feature = "cuda")] #[pyfunction] #[pyo3(pass_module)] fn cuda(module: &Bound<'_, PyModule>) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.cuda() } #[cfg(feature = "cuda")] #[pyfunction] #[pyo3(pass_module)] fn update_graphics_from( module: &Bound<'_, PyModule>, obj: &Bound<'_, pyo3::PyAny>, ) -> PyResult<()> { graphics!(module).update_from(obj) } #[pyfunction] #[pyo3(pass_module)] fn _tick(module: &Bound<'_, PyModule>, ns: &Bound<'_, PyAny>) -> PyResult<()> { if get_graphics(module)?.is_none() { return Ok(()); } sync_globals(module, ns)?; dispatch_event_callbacks(module, ns)?; Ok(()) } /// Register an embedding host. From now on `size()` allocates an offscreen /// canvas instead of opening a window, `run()` is a no-op, and the host is /// expected to drive frames itself with `_tick()` + `_host_frame()` and to /// read the canvas back. #[pyfunction] #[pyo3(pass_module, signature = (asset_root=None, *, hdr=false, log_level=None))] fn _embed( module: &Bound<'_, PyModule>, asset_root: Option<&str>, hdr: bool, log_level: Option<&str>, ) -> PyResult<()> { let py = module.py(); module.setattr("_embedded", true)?; match asset_root { Some(root) => module.setattr("_asset_root", root)?, None => module.setattr("_asset_root", py.None())?, } module.setattr("_embedded_hdr", hdr)?; match log_level { Some(level) => module.setattr("_embedded_log_level", level)?, None => module.setattr("_embedded_log_level", py.None())?, } Ok(()) } #[pymodule_export] use super::host::SketchContext; #[pyfunction] fn _context_new() -> host::SketchContext { host::context_new() } #[pyfunction] #[pyo3(pass_module)] fn _context_enter( module: &Bound<'_, PyModule>, ctx: &Bound<'_, host::SketchContext>, ) -> PyResult<()> { host::context_enter(module, &mut ctx.borrow_mut()) } #[pyfunction] #[pyo3(pass_module)] fn _context_exit( module: &Bound<'_, PyModule>, ctx: &Bound<'_, host::SketchContext>, ) -> PyResult<()> { host::context_exit(module, &mut ctx.borrow_mut()) } /// Read a canvas back: `(bytes, width, height, format)`, rows top-first. #[pyfunction] fn _host_readback( py: Python<'_>, graphics: u64, ) -> PyResult<(Bound<'_, pyo3::types::PyBytes>, u32, u32, &'static str)> { host::readback(py, graphics) } #[pyfunction] fn _host_mouse_move(surface: u64, x: f32, y: f32) -> PyResult<()> { host::mouse_move(surface, x, y) } /// `button`: 0 left, 1 middle, 2 right. #[pyfunction] fn _host_mouse_button(surface: u64, button: u8, pressed: bool) -> PyResult<()> { host::mouse_button(surface, button, pressed) } #[pyfunction] fn _host_scroll(surface: u64, x: f32, y: f32) -> PyResult<()> { host::scroll(surface, x, y) } #[pyfunction] fn _host_input_flush() -> PyResult<()> { host::flush_input() } #[pyfunction] fn _host_image_update(image: u64, data: &Bound<'_, PyAny>) -> PyResult<()> { host::image_update(image, data) } #[pyfunction] fn _host_particles_capacity(particles: u64) -> PyResult { host::capacity(particles) } /// `[(name, components, buffer_bits)]` for every attribute buffer. #[pyfunction] fn _host_particles_attributes(particles: u64) -> PyResult> { host::attributes(particles) } /// The buffer for attribute `name` (created if missing), as raw bits. #[pyfunction] fn _host_particles_buffer(particles: u64, name: String, components: u32) -> PyResult { host::attribute_buffer(particles, name, components) } #[pyfunction] fn _host_buffer_read(py: Python<'_>, buffer: u64) -> PyResult> { host::read_buffer(py, buffer) } #[pyfunction] fn _host_buffer_write(buffer: u64, data: &Bound<'_, PyAny>) -> PyResult<()> { host::write_buffer(buffer, data) } #[pyfunction] #[pyo3(pass_module)] fn _ensure_graphics(module: &Bound<'_, PyModule>) -> PyResult<()> { ensure_graphics(module) } #[pyfunction] #[pyo3(pass_module, signature = (ns, force=false))] fn _host_frame( module: &Bound<'_, PyModule>, ns: &Bound<'_, PyAny>, force: bool, ) -> PyResult { let py = module.py(); ensure_graphics(module)?; let should_draw = force || LOOP_STATE.with(|s| { let state = s.get(); state.looping || state.redraw_requested }); if !should_draw { return Ok(false); } processing::prelude::advance_frame_count() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let windows = collect_windows(module)?; for wg in &windows { wg.bind(py).borrow().begin_draw()?; } sync_globals(module, ns)?; let draw_result = match ns.get_item("draw") { Ok(draw) if draw.is_callable() => draw.call0().map(|_| ()), _ => Ok(()), }; // Finish the frame even when draw() raised for wg in &windows { wg.bind(py).borrow().end_draw()?; } draw_result?; update_loop_state(|s| s.redraw_requested = false); Ok(true) } #[pyfunction] fn redraw() -> PyResult<()> { update_loop_state(|s| { if !s.looping { s.redraw_requested = true; } }); Ok(()) } #[pyfunction] #[pyo3(name = "loop")] fn loop_() -> PyResult<()> { update_loop_state(|s| s.looping = true); Ok(()) } #[pyfunction] fn no_loop() -> PyResult<()> { update_loop_state(|s| s.looping = false); Ok(()) } #[pyfunction] #[pyo3(pass_module)] fn window_title(module: &Bound<'_, PyModule>, title: &str) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_title(title) } #[pyfunction] #[pyo3(pass_module)] fn window_move(module: &Bound<'_, PyModule>, x: i32, y: i32) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_position(x, y) } #[pyfunction] #[pyo3(pass_module)] fn window_resize(module: &Bound<'_, PyModule>, w: u32, h: u32) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; ::processing::prelude::surface_resize(graphics.surface.entity, w, h) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } #[pyfunction] #[pyo3(pass_module)] fn window_resizable(module: &Bound<'_, PyModule>, resizable: bool) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_resizable(resizable) } #[pyfunction] #[pyo3(pass_module, signature = (monitor=None))] fn full_screen( module: &Bound<'_, PyModule>, monitor: Option<&crate::monitor::Monitor>, ) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_fullscreen(monitor) } #[pyfunction] #[pyo3(pass_module)] fn window_visible(module: &Bound<'_, PyModule>, visible: bool) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_visible(visible) } #[pyfunction] #[pyo3(pass_module)] fn window_decorated(module: &Bound<'_, PyModule>, decorated: bool) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_decorated(decorated) } #[pyfunction] #[pyo3(pass_module)] fn window_always_on_top(module: &Bound<'_, PyModule>, on_top: bool) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_always_on_top(on_top) } #[pyfunction] #[pyo3(pass_module)] fn window_opacity(module: &Bound<'_, PyModule>, opacity: f32) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.set_opacity(opacity) } #[pyfunction] #[pyo3(pass_module)] fn window_iconify(module: &Bound<'_, PyModule>) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.iconify() } #[pyfunction] #[pyo3(pass_module)] fn window_restore(module: &Bound<'_, PyModule>) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.restore() } #[pyfunction] #[pyo3(pass_module)] fn window_maximize(module: &Bound<'_, PyModule>) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.maximize() } #[pyfunction] #[pyo3(pass_module)] fn window_center_on( module: &Bound<'_, PyModule>, monitor: &crate::monitor::Monitor, ) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.center_on(monitor) } #[pyfunction] #[pyo3(pass_module)] fn window_position_on( module: &Bound<'_, PyModule>, monitor: &crate::monitor::Monitor, x: i32, y: i32, ) -> PyResult<()> { let Some(graphics) = get_graphics(module)? else { return Ok(()); }; graphics.surface.position_on(monitor, x, y) } #[pyfunction] #[pyo3(pass_module, signature = (width, height, *, transparent=false))] fn size( module: &Bound<'_, PyModule>, width: u32, height: u32, transparent: bool, ) -> PyResult<()> { create_graphics_context(module, width, height, transparent)?; let py = module.py(); let sys = PyModule::import(py, "sys")?; let frame = sys.getattr("_getframe")?.call1((0,))?; let caller_globals = frame.getattr("f_globals")?; sync_globals(module, &caller_globals)?; Ok(()) } #[pyfunction] #[pyo3(pass_module)] fn run(module: &Bound<'_, PyModule>) -> PyResult<()> { let env = detect_environment(module)?; if env != "script" { warn!("run() was called, but we're in an interactive environment ({env})."); return Ok(()); } let result: PyResult<()> = Python::attach(|py| { let builtins = PyModule::import(py, "builtins")?; let locals = builtins.getattr("locals")?.call0()?; let setup_fn = locals.get_item("setup").ok(); let mut draw_fn = locals.get_item("draw").ok(); if let Some(ref setup) = setup_fn { setup.call0()?; } ensure_graphics(module)?; let mut globals = if let Some(ref draw) = draw_fn { draw.getattr("__globals__")? } else if let Some(ref setup) = setup_fn { setup.getattr("__globals__")? } else { let sys = PyModule::import(py, "sys")?; let frame = sys.getattr("_getframe")?.call1((0,))?; frame.getattr("f_globals")? }; sync_globals(module, &globals)?; // no draw is defined. flush any top level code and then idle if draw_fn.is_none() { { let mut graphics = get_graphics_mut(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.surface.poll_events(); } get_graphics(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))? .end_draw()?; loop { { let mut graphics = get_graphics_mut(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; if !graphics.surface.poll_events() { break; } } dispatch_event_callbacks(module, &locals)?; std::thread::sleep(std::time::Duration::from_millis(16)); } return Ok(()); } let draw_fn_ref = draw_fn.as_mut().expect("checked above"); let mut first_frame = true; loop { { let mut graphics = get_graphics_mut(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; // TODO: this shouldn't be on the graphics object let sketch = graphics.poll_for_sketch_update()?; if !sketch.source.is_empty() { let locals = PyDict::new(py); let ok = CString::new(sketch.source.as_str()).unwrap(); let cstr: &CStr = ok.as_c_str(); match py.run(cstr, None, Some(&locals)) { Ok(_) => {} Err(e) => { eprintln!("sketch reload error: {e}"); } } *draw_fn_ref = locals.get_item("draw").unwrap().unwrap(); globals = draw_fn_ref.getattr("__globals__")?; reset_tracked_globals(); first_frame = true; dbg!(locals); } if !graphics.surface.poll_events() { break; } } dispatch_event_callbacks(module, &locals)?; let should_draw = first_frame || LOOP_STATE.with(|s| { let state = s.get(); state.looping || state.redraw_requested }); if !should_draw { std::thread::sleep(std::time::Duration::from_millis(16)); continue; } first_frame = false; processing::prelude::advance_frame_count() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; // One draw() per frame drives every window. Globals target the // primary window; any additional window is drawn via its own // Graphics methods, exactly like an offscreen buffer. Each window // is begun before draw() and presented after. let windows = collect_windows(module)?; for wg in &windows { wg.bind(py).borrow().begin_draw()?; } sync_globals(module, &globals)?; draw_fn_ref.call0()?; for wg in &windows { wg.bind(py).borrow().end_draw()?; } update_loop_state(|s| s.redraw_requested = false); } Ok(()) }); // tear down the app while the thread-local is still alive; the eager // TLS destructor aborts inside a Bevy resource drop let _ = ::processing::exit(0); result } #[pyfunction] #[pyo3(pass_module)] fn mode_3d(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).mode_3d() } #[pyfunction] #[pyo3(pass_module)] fn mode_2d(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).mode_2d() } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn camera_position(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).camera_position(args) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn camera_look_at(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).camera_look_at(args) } #[pyfunction] #[pyo3(pass_module)] fn orbit_camera(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).orbit_camera() } #[pyfunction] #[pyo3(pass_module)] fn free_camera(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).free_camera() } #[pyfunction] #[pyo3(pass_module)] fn pan_camera(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).pan_camera() } #[pyfunction] #[pyo3(pass_module)] fn disable_camera(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).disable_camera() } #[pyfunction] #[pyo3(pass_module)] fn camera_distance(module: &Bound<'_, PyModule>, distance: f32) -> PyResult<()> { graphics!(module).camera_distance(distance) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn camera_center(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).camera_center(args) } #[pyfunction] #[pyo3(pass_module)] fn camera_min_distance(module: &Bound<'_, PyModule>, min: f32) -> PyResult<()> { graphics!(module).camera_min_distance(min) } #[pyfunction] #[pyo3(pass_module)] fn camera_max_distance(module: &Bound<'_, PyModule>, max: f32) -> PyResult<()> { graphics!(module).camera_max_distance(max) } #[pyfunction] #[pyo3(pass_module)] fn camera_speed(module: &Bound<'_, PyModule>, speed: f32) -> PyResult<()> { graphics!(module).camera_speed(speed) } #[pyfunction] #[pyo3(pass_module)] fn camera_reset(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).camera_reset() } #[pyfunction] #[pyo3(pass_module)] fn push_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).push_matrix() } #[pyfunction] #[pyo3(pass_module)] fn pop_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).pop_matrix() } #[pyfunction] #[pyo3(pass_module)] fn push_style(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).push_style() } #[pyfunction] #[pyo3(pass_module)] fn pop_style(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).pop_style() } #[pyfunction] #[pyo3(pass_module)] fn push(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).push() } #[pyfunction] #[pyo3(pass_module)] fn pop(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).pop() } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn translate(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).translate(args) } #[pyfunction] #[pyo3(pass_module)] fn rotate(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).rotate(angle) } #[pyfunction] #[pyo3(pass_module)] fn rotate_x(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).rotate_x(angle) } #[pyfunction] #[pyo3(pass_module)] fn rotate_y(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).rotate_y(angle) } #[pyfunction] #[pyo3(pass_module)] fn rotate_z(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).rotate_z(angle) } #[pyfunction] #[pyo3(pass_module, signature = (angle, *args))] fn rotate_axis( module: &Bound<'_, PyModule>, angle: f32, args: &Bound<'_, PyTuple>, ) -> PyResult<()> { graphics!(module).rotate_axis(angle, args) } #[pyfunction(name = "box")] #[pyo3(pass_module, signature = (*args))] fn draw_box(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { let (w, h, d) = match args.len() { 1 => { let s: f32 = args.get_item(0)?.extract()?; (s, s, s) } 3 => { let w = args.get_item(0)?.extract()?; let h = args.get_item(1)?.extract()?; let d = args.get_item(2)?.extract()?; (w, h, d) } n => { return Err(pyo3::exceptions::PyTypeError::new_err(format!( "box() takes 1 or 3 arguments ({n} given)" ))); } }; graphics!(module).draw_box(w, h, d) } #[pyfunction] #[pyo3(pass_module, signature = (geometry))] fn draw_geometry(module: &Bound<'_, PyModule>, geometry: &Bound<'_, Geometry>) -> PyResult<()> { graphics!(module).draw_geometry(&*geometry.extract::>()?) } #[pyfunction] #[pyo3(pass_module, signature = (particles, geometry = None, topology = None))] fn particles( module: &Bound<'_, PyModule>, particles: &Bound<'_, super::particles::Particles>, geometry: Option<&Bound<'_, Geometry>>, topology: Option<&str>, ) -> PyResult<()> { let geometry = match geometry { Some(g) => Some(g.extract::>()?), None => None, }; graphics!(module).particles( &*particles.extract::>()?, geometry.as_deref(), topology, ) } #[pyfunction(name = "color")] #[pyo3(pass_module, signature = (*args))] fn create_color( module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>, ) -> PyResult { match get_graphics(module)? { Some(g) => g.color(args), None => { let mode = super::color::ColorMode::default(); super::color::extract_color_with_mode(args, &mode).map(super::color::PyColor::from) } } } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn background(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { let graphics = graphics!(module); let first = args.get_item(0)?; if first.is_instance_of::() { graphics.background_image(&*first.extract::>()?) } else { graphics.background(args) } } #[pyfunction] #[pyo3(pass_module)] fn clear(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).clear() } #[pyfunction] #[pyo3(pass_module, signature = (mode, max1=None, max2=None, max3=None, max_alpha=None))] fn color_mode<'py>( module: &Bound<'py, PyModule>, mode: &str, max1: Option<&Bound<'py, PyAny>>, max2: Option<&Bound<'py, PyAny>>, max3: Option<&Bound<'py, PyAny>>, max_alpha: Option<&Bound<'py, PyAny>>, ) -> PyResult<()> { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.set_color_mode(mode, max1, max2, max3, max_alpha) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn fill(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).fill(args) } #[pyfunction] #[pyo3(pass_module)] fn no_fill(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_fill() } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn stroke(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).stroke(args) } #[pyfunction] #[pyo3(pass_module)] fn no_stroke(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_stroke() } #[pyfunction] #[pyo3(pass_module)] fn stroke_weight(module: &Bound<'_, PyModule>, weight: f32) -> PyResult<()> { graphics!(module).stroke_weight(weight) } #[pyfunction] #[pyo3(pass_module)] fn stroke_cap(module: &Bound<'_, PyModule>, cap: &str) -> PyResult<()> { graphics!(module).stroke_cap(cap) } #[pyfunction] #[pyo3(pass_module)] fn stroke_join(module: &Bound<'_, PyModule>, join: &str) -> PyResult<()> { graphics!(module).stroke_join(join) } #[pyfunction] #[pyo3(pass_module, signature = (mode))] fn blend_mode(module: &Bound<'_, PyModule>, mode: &Bound<'_, PyBlendMode>) -> PyResult<()> { graphics!(module).blend_mode(&*mode.extract::>()?) } #[pyfunction] #[pyo3(pass_module, signature = (intensity=processing_render::graphics::DEFAULT_BLOOM_INTENSITY, threshold=0.0))] fn bloom(module: &Bound<'_, PyModule>, intensity: f32, threshold: f32) -> PyResult<()> { graphics!(module).bloom(intensity, threshold) } #[pyfunction] #[pyo3(pass_module)] fn no_bloom(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_bloom() } #[pyfunction] #[pyo3(pass_module, signature = (mode=processing::prelude::constants::PBR_NEUTRAL))] fn tonemapping(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).tonemapping(mode) } #[pyfunction] #[pyo3(pass_module)] fn no_tonemapping(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_tonemapping() } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn rect(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).rect(args) } /// Loads an image from a file and returns an Image object. #[pyfunction] #[pyo3(pass_module, signature = (image_file))] fn load_image(module: &Bound<'_, PyModule>, image_file: &str) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.load_image(image_file) } /// Draws an image to the screen. /// /// Optional `d_width`/`d_height` resize on screen; defaults to the image's /// original dimensions. Optional `sx`/`sy`/`s_width`/`s_height` select a /// sub-region of the source image in pixels. #[pyfunction] #[pyo3(pass_module, signature = (source, dx, dy, d_width=None, d_height=None, sx=None, sy=None, s_width=None, s_height=None))] #[allow(clippy::too_many_arguments)] fn image( module: &Bound<'_, PyModule>, source: graphics::ImageRef, dx: f32, dy: f32, d_width: Option, d_height: Option, sx: Option, sy: Option, s_width: Option, s_height: Option, ) -> PyResult<()> { graphics!(module).image(source, dx, dy, d_width, d_height, sx, sy, s_width, s_height) } /// Sets a tint color applied when drawing images. #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn tint(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).tint(args) } /// Removes the current tint so images draw without color modification. #[pyfunction] #[pyo3(pass_module)] fn no_tint(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_tint() } /// Changes how image position arguments are interpreted. /// /// - `CORNER` (default) — `dx`, `dy` is the top-left corner. /// - `CENTER` — `dx`, `dy` is the center. /// - `CORNERS` — `dx`, `dy` and `d_width`, `d_height` are opposite corners. #[pyfunction] #[pyo3(pass_module)] fn image_mode(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).image_mode(mode) } #[pyfunction] #[pyo3(pass_module)] fn create_image(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.create_image(width, height) } /// Creates an offscreen graphics buffer (Processing `createGraphics`). The /// returned `Graphics` renders in the current app — draw into it, use it as a /// composite source (`blend`/`copy`), blit it into an image (`copy_from`), or /// display it with `image()`. Call `size()` first. #[pyfunction] #[pyo3(pass_module)] fn create_graphics( module: &Bound<'_, PyModule>, width: u32, height: u32, ) -> PyResult { get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; Graphics::wrap_offscreen(width, height, false) } /// Opens an additional window (libprocessing extension; not in Processing). /// Returns a window-backed `Graphics` — draw to it with its own methods /// inside your normal `draw()`, exactly like an offscreen `create_graphics` /// buffer (globals target the primary window). The run loop presents every /// window each frame. Call `size()` first. #[pyfunction] #[pyo3(pass_module, signature = (width, height, title="Processing", *, transparent=false))] fn create_window( module: &Bound<'_, PyModule>, width: u32, height: u32, title: &str, transparent: bool, ) -> PyResult> { // The main surface owns the shared GLFW instance; add a window to it. let surface_entity = { let mut main = get_graphics_mut(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; main.surface.add_window(width, height, transparent, title)? }; // The GLFW window is created with the title, but the Window component // (which the per-frame sync applies) defaults otherwise — set it too. ::processing::prelude::surface_set_title(surface_entity, title.to_string()) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let window = Py::new( module.py(), Graphics::wrap_window(surface_entity, width, height)?, )?; register_window(module, &window)?; Ok(window) } /// Creates a GPU particle system (Processing `createParticles`) from a count or a `Geometry`. /// Attributes are created on first use. #[pyfunction] #[pyo3(signature = (source, attributes=None))] fn create_particles( source: &Bound<'_, PyAny>, attributes: Option>>, ) -> PyResult { super::particles::Particles::create(source, attributes) } /// Creates a compute pass from a shader (Processing-style `createCompute`). #[pyfunction] fn create_compute(shader: &Shader) -> PyResult { Compute::create(shader) } /// Creates a GPU storage buffer, empty (`size` bytes) or from initial `data`. #[pyfunction] #[pyo3(signature = (size=None, data=None))] fn create_buffer(size: Option, data: Option<&Bound<'_, PyAny>>) -> PyResult { Buffer::create(size, data) } /// Creates a mesh builder (Processing `createShape`-style). #[pyfunction] #[pyo3(signature = (**kwargs))] fn create_geometry(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult { Geometry::create(kwargs) } /// Creates a shader from WGSL/WESL source. #[pyfunction] fn create_shader(source: &str) -> PyResult { Shader::from_source(source) } /// Loads a shader from a file (Processing `loadShader`). #[pyfunction] fn load_shader(path: &str) -> PyResult { Shader::from_path(path) } fn apply_light_transform( light: &Light, position: Option, look_at: Option, ) -> PyResult<()> { if let Some(p) = position { ::processing::prelude::transform_set_position(light.entity, p.into_vec3()) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; } if let Some(la) = look_at { ::processing::prelude::transform_look_at(light.entity, la.into_vec3()) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; } Ok(()) } #[pyfunction] #[pyo3(pass_module, signature = (color, illuminance, *, position=None, look_at=None))] fn directional_light( module: &Bound<'_, PyModule>, color: super::color::ColorLike, illuminance: f32, position: Option, look_at: Option, ) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let light = graphics.light_directional(color, illuminance)?; apply_light_transform(&light, position, look_at)?; Ok(light) } #[pyfunction] #[pyo3(pass_module, signature = (color, intensity, range, radius, *, position=None, look_at=None))] fn point_light( module: &Bound<'_, PyModule>, color: super::color::ColorLike, intensity: f32, range: f32, radius: f32, position: Option, look_at: Option, ) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let light = graphics.light_point(color, intensity, range, radius)?; apply_light_transform(&light, position, look_at)?; Ok(light) } #[pyfunction] #[pyo3(pass_module, signature = (color, intensity, range, radius, inner_angle, outer_angle, *, position=None, look_at=None))] fn spot_light( module: &Bound<'_, PyModule>, color: super::color::ColorLike, intensity: f32, range: f32, radius: f32, inner_angle: f32, outer_angle: f32, position: Option, look_at: Option, ) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let light = graphics.light_spot(color, intensity, range, radius, inner_angle, outer_angle)?; apply_light_transform(&light, position, look_at)?; Ok(light) } #[pyfunction(name = "sphere")] #[pyo3(pass_module, signature = (radius, sectors=32, stacks=18))] fn draw_sphere( module: &Bound<'_, PyModule>, radius: f32, sectors: u32, stacks: u32, ) -> PyResult<()> { graphics!(module).draw_sphere(radius, sectors, stacks) } #[pyfunction] #[pyo3(pass_module, signature = (material))] fn material(module: &Bound<'_, PyModule>, material: &Bound<'_, Material>) -> PyResult<()> { graphics!(module).material(&*material.extract::>()?) } /// Creates a new material, mirroring Processing's `createMaterial()`. /// /// With no arguments a PBR material is created; pass a `Shader` for a custom /// material. Any additional keyword arguments (albedo, roughness, metallic, /// emissive, unlit, ...) are applied immediately. #[pyfunction] #[pyo3(signature = (shader=None, **kwargs))] fn create_material( shader: Option<&Shader>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult { Material::new(shader, kwargs) } #[pyfunction] #[pyo3(pass_module, signature = (kind, *args, **kwargs))] fn filter( module: &Bound<'_, PyModule>, kind: Bound<'_, PyAny>, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult<()> { graphics!(module).filter(kind, args, kwargs) } #[pyfunction] #[pyo3(pass_module, signature = (src, mode, *, src_rect=None, dst_rect=None, opacity=1.0))] fn blend( module: &Bound<'_, PyModule>, src: &Bound<'_, PyAny>, mode: &graphics::PyBlendMode, src_rect: Option<[f32; 4]>, dst_rect: Option<[f32; 4]>, opacity: f32, ) -> PyResult<()> { graphics!(module).blend(src, mode, src_rect, dst_rect, opacity) } #[pyfunction] #[pyo3(pass_module, signature = (src, *, src_rect=None, dst_rect=None))] fn copy( module: &Bound<'_, PyModule>, src: &Bound<'_, PyAny>, src_rect: Option<[f32; 4]>, dst_rect: Option<[f32; 4]>, ) -> PyResult<()> { graphics!(module).copy(src, src_rect, dst_rect) } #[pyfunction] #[pyo3(pass_module)] fn mask(module: &Bound<'_, PyModule>, mask: &Bound<'_, PyAny>) -> PyResult<()> { graphics!(module).mask(mask) } #[pyfunction] #[pyo3(pass_module, signature = (*, decay=0.95, zoom=1.0, angle=0.0, offset=(0.0, 0.0)))] fn feedback( module: &Bound<'_, PyModule>, decay: f32, zoom: f32, angle: f32, offset: (f32, f32), ) -> PyResult<()> { graphics!(module).feedback(decay, zoom, angle, offset) } // --- Text --- #[pyfunction] #[pyo3(pass_module, signature = (content, x, y, *args, max_w=None, max_h=None))] fn text( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, args: &Bound<'_, PyTuple>, max_w: Option, max_h: Option, ) -> PyResult<()> { graphics!(module).text(content, x, y, args, max_w, max_h) } #[pyfunction] #[pyo3(pass_module)] fn text_size(module: &Bound<'_, PyModule>, size: f32) -> PyResult<()> { graphics!(module).text_size(size) } #[pyfunction] #[pyo3(pass_module, signature = (h, v=None))] fn text_align(module: &Bound<'_, PyModule>, h: &str, v: Option<&str>) -> PyResult<()> { graphics!(module).text_align(h, v) } #[pyfunction] #[pyo3(pass_module)] fn text_leading(module: &Bound<'_, PyModule>, leading: f32) -> PyResult<()> { graphics!(module).text_leading(leading) } #[pyfunction] #[pyo3(pass_module, signature = (font=None))] fn text_font(module: &Bound<'_, PyModule>, font: Option<&Font>) -> PyResult<()> { graphics!(module).text_font(font) } #[pyfunction] #[pyo3(pass_module)] fn text_style(module: &Bound<'_, PyModule>, style: &str) -> PyResult<()> { graphics!(module).text_style(style) } #[pyfunction] #[pyo3(pass_module)] fn text_wrap(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).text_wrap(mode) } #[pyfunction] #[pyo3(pass_module)] fn text_width(module: &Bound<'_, PyModule>, content: &str) -> PyResult { graphics!(module).text_width(content) } #[pyfunction] #[pyo3(pass_module)] fn text_ascent(module: &Bound<'_, PyModule>) -> PyResult { graphics!(module).text_ascent() } #[pyfunction] #[pyo3(pass_module)] fn text_descent(module: &Bound<'_, PyModule>) -> PyResult { graphics!(module).text_descent() } #[pyfunction] #[pyo3(pass_module, signature = (content, x, y, max_w=None, max_h=None))] fn text_bounds( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, max_w: Option, max_h: Option, ) -> PyResult<(f32, f32, f32, f32)> { graphics!(module).text_bounds(content, x, y, max_w, max_h) } #[pyfunction] #[pyo3(pass_module)] fn text_line_count(module: &Bound<'_, PyModule>, content: &str) -> PyResult { graphics!(module).text_line_count(content) } #[pyfunction] #[pyo3(pass_module)] fn text_weight(module: &Bound<'_, PyModule>, weight: f32) -> PyResult<()> { graphics!(module).text_weight(weight) } #[pyfunction] #[pyo3(pass_module)] fn text_variation(module: &Bound<'_, PyModule>, tag: &str, value: f32) -> PyResult<()> { graphics!(module).text_variation(tag, value) } #[pyfunction] #[pyo3(pass_module)] fn clear_text_variations(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).clear_text_variations() } #[pyfunction] #[pyo3(pass_module, signature = (tag, value=None))] fn text_feature( module: &Bound<'_, PyModule>, tag: &str, value: Option<&Bound<'_, PyAny>>, ) -> PyResult<()> { graphics!(module).text_feature(tag, value) } #[pyfunction] #[pyo3(pass_module)] fn no_text_feature(module: &Bound<'_, PyModule>, tag: &str) -> PyResult<()> { graphics!(module).no_text_feature(tag) } #[pyfunction] #[pyo3(pass_module)] fn clear_text_features(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).clear_text_features() } #[pyfunction] #[pyo3(pass_module)] fn text_glyph_colors( module: &Bound<'_, PyModule>, colors: Vec>, ) -> PyResult<()> { graphics!(module).text_glyph_colors(colors) } #[pyfunction] #[pyo3(pass_module)] fn load_font(module: &Bound<'_, PyModule>, path: &str) -> PyResult { graphics!(module).load_font(path) } #[pyfunction] #[pyo3(pass_module)] fn create_font(module: &Bound<'_, PyModule>, name: &str) -> PyResult { graphics!(module).create_font(name) } #[pyfunction] #[pyo3(pass_module)] fn list_fonts(module: &Bound<'_, PyModule>) -> PyResult> { graphics!(module).list_fonts() } #[pyfunction] #[pyo3(pass_module)] fn text_to_paths( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, ) -> PyResult>>> { graphics!(module).text_to_paths(content, x, y) } #[pyfunction] #[pyo3(pass_module)] fn text_to_contours( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, ) -> PyResult>>> { graphics!(module).text_to_contours(content, x, y) } #[pyfunction] #[pyo3(pass_module, signature = (content, x, y, sample_factor=None))] fn text_to_points( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, sample_factor: Option, ) -> PyResult> { graphics!(module).text_to_points(content, x, y, sample_factor) } #[pyfunction] #[pyo3(pass_module)] fn text_to_model( module: &Bound<'_, PyModule>, content: &str, x: f32, y: f32, depth: f32, ) -> PyResult { graphics!(module).text_to_model(content, x, y, depth) } #[pyfunction] #[pyo3(pass_module)] fn roughness(module: &Bound<'_, PyModule>, value: f32) -> PyResult<()> { graphics!(module).roughness(value) } #[pyfunction] #[pyo3(pass_module)] fn metallic(module: &Bound<'_, PyModule>, value: f32) -> PyResult<()> { graphics!(module).metallic(value) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn emissive(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).emissive(args) } #[pyfunction] #[pyo3(pass_module)] fn texture(module: &Bound<'_, PyModule>, source: graphics::ImageRef) -> PyResult<()> { graphics!(module).texture(source) } #[pyfunction] #[pyo3(pass_module)] fn no_texture(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).no_texture() } #[pyfunction] #[pyo3(pass_module)] fn texture_transform( module: &Bound<'_, PyModule>, transform: crate::math::PyAffine2, ) -> PyResult<()> { graphics!(module).texture_transform(transform) } #[pyfunction] #[pyo3(pass_module)] fn unlit(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).unlit() } #[pyfunction] #[pyo3(pass_module)] fn ellipse(module: &Bound<'_, PyModule>, cx: f32, cy: f32, w: f32, h: f32) -> PyResult<()> { graphics!(module).ellipse(cx, cy, w, h) } #[pyfunction] #[pyo3(pass_module)] fn circle(module: &Bound<'_, PyModule>, cx: f32, cy: f32, d: f32) -> PyResult<()> { graphics!(module).circle(cx, cy, d) } #[pyfunction] #[pyo3(pass_module)] fn line(module: &Bound<'_, PyModule>, x1: f32, y1: f32, x2: f32, y2: f32) -> PyResult<()> { graphics!(module).line(x1, y1, x2, y2) } #[pyfunction] #[pyo3(pass_module)] fn triangle( module: &Bound<'_, PyModule>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, ) -> PyResult<()> { graphics!(module).triangle(x1, y1, x2, y2, x3, y3) } #[pyfunction] #[pyo3(pass_module)] fn quad( module: &Bound<'_, PyModule>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32, ) -> PyResult<()> { graphics!(module).quad(x1, y1, x2, y2, x3, y3, x4, y4) } #[pyfunction] #[pyo3(pass_module)] fn point(module: &Bound<'_, PyModule>, x: f32, y: f32) -> PyResult<()> { graphics!(module).point(x, y) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, s))] fn square(module: &Bound<'_, PyModule>, x: f32, y: f32, s: f32) -> PyResult<()> { graphics!(module).square(x, y, s) } #[pyfunction] #[pyo3(pass_module, signature = (cx, cy, w, h, start, stop, mode=processing::prelude::constants::OPEN))] fn arc( module: &Bound<'_, PyModule>, cx: f32, cy: f32, w: f32, h: f32, start: f32, stop: f32, mode: &str, ) -> PyResult<()> { graphics!(module).arc(cx, cy, w, h, start, stop, mode) } #[pyfunction] #[pyo3(pass_module)] fn bezier( module: &Bound<'_, PyModule>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32, ) -> PyResult<()> { graphics!(module).bezier(x1, y1, x2, y2, x3, y3, x4, y4) } #[pyfunction] #[pyo3(pass_module)] fn curve( module: &Bound<'_, PyModule>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32, ) -> PyResult<()> { graphics!(module).curve(x1, y1, x2, y2, x3, y3, x4, y4) } #[pyfunction] #[pyo3(pass_module, signature = (kind=processing::prelude::constants::POLYGON))] fn begin_shape(module: &Bound<'_, PyModule>, kind: &str) -> PyResult<()> { graphics!(module).begin_shape(kind) } #[pyfunction] #[pyo3(pass_module, signature = (close=false))] fn end_shape(module: &Bound<'_, PyModule>, close: bool) -> PyResult<()> { graphics!(module).end_shape(close) } #[pyfunction] #[pyo3(pass_module)] fn vertex(module: &Bound<'_, PyModule>, x: f32, y: f32) -> PyResult<()> { graphics!(module).vertex(x, y) } #[pyfunction] #[pyo3(pass_module)] fn bezier_vertex( module: &Bound<'_, PyModule>, cx1: f32, cy1: f32, cx2: f32, cy2: f32, x: f32, y: f32, ) -> PyResult<()> { graphics!(module).bezier_vertex(cx1, cy1, cx2, cy2, x, y) } #[pyfunction] #[pyo3(pass_module)] fn quadratic_vertex( module: &Bound<'_, PyModule>, cx: f32, cy: f32, x: f32, y: f32, ) -> PyResult<()> { graphics!(module).quadratic_vertex(cx, cy, x, y) } #[pyfunction] #[pyo3(pass_module)] fn curve_vertex(module: &Bound<'_, PyModule>, x: f32, y: f32) -> PyResult<()> { graphics!(module).curve_vertex(x, y) } #[pyfunction] #[pyo3(pass_module)] fn begin_contour(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).begin_contour() } #[pyfunction] #[pyo3(pass_module)] fn end_contour(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).end_contour() } #[pyfunction] #[pyo3(pass_module)] fn reset_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).reset_matrix() } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn apply_matrix(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).apply_matrix(args) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn set_matrix(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).set_matrix(args) } #[pyfunction] #[pyo3(pass_module)] fn get_matrix(module: &Bound<'_, PyModule>) -> PyResult<[f32; 16]> { graphics!(module).get_matrix() } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn screen_x(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).screen_x(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn screen_y(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).screen_y(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn screen_z(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).screen_z(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn model_x(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).model_x(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn model_y(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).model_y(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, z=0.0))] fn model_z(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult { graphics!(module).model_z(x, y, z) } #[pyfunction] #[pyo3(pass_module, signature = (sx, sy, depth=0.0))] fn world_x(module: &Bound<'_, PyModule>, sx: f32, sy: f32, depth: f32) -> PyResult { graphics!(module).world_x(sx, sy, depth) } #[pyfunction] #[pyo3(pass_module, signature = (sx, sy, depth=0.0))] fn world_y(module: &Bound<'_, PyModule>, sx: f32, sy: f32, depth: f32) -> PyResult { graphics!(module).world_y(sx, sy, depth) } #[pyfunction] #[pyo3(pass_module, signature = (sx, sy, depth=0.0))] fn world_z(module: &Bound<'_, PyModule>, sx: f32, sy: f32, depth: f32) -> PyResult { graphics!(module).world_z(sx, sy, depth) } #[pyfunction] #[pyo3(pass_module)] fn get(module: &Bound<'_, PyModule>, x: u32, y: u32) -> PyResult { graphics!(module).get(x, y) } #[pyfunction] #[pyo3(pass_module, signature = (x, y, *args))] fn set( module: &Bound<'_, PyModule>, x: u32, y: u32, args: &Bound<'_, PyTuple>, ) -> PyResult<()> { graphics!(module).set(x, y, args) } #[pyfunction] #[pyo3(pass_module)] fn load_pixels(module: &Bound<'_, PyModule>, py: Python<'_>) -> PyResult> { graphics!(module).load_pixels(py) } #[pyfunction] #[pyo3(pass_module, signature = (pixels=None))] fn update_pixels( module: &Bound<'_, PyModule>, py: Python<'_>, pixels: Option<&Bound<'_, PyAny>>, ) -> PyResult<()> { graphics!(module).update_pixels(py, pixels) } #[pyfunction] #[pyo3(pass_module)] fn save(module: &Bound<'_, PyModule>, filename: &str) -> PyResult<()> { graphics!(module).save(filename) } #[pyfunction] #[pyo3(pass_module, signature = (filename=None))] fn save_frame(module: &Bound<'_, PyModule>, filename: Option<&str>) -> PyResult<()> { let count = ::processing::prelude::frame_count() .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let name = match filename { Some(f) => substitute_frame_number(f, count), None => format!("screen-{count:04}.png"), }; graphics!(module).save(&name) } #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn scale(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { graphics!(module).scale(args) } #[pyfunction] #[pyo3(pass_module)] fn shear_x(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).shear_x(angle) } #[pyfunction] #[pyo3(pass_module)] fn shear_y(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { graphics!(module).shear_y(angle) } #[pyfunction] #[pyo3(pass_module)] fn rect_mode(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).rect_mode(mode) } #[pyfunction] #[pyo3(pass_module)] fn ellipse_mode(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).ellipse_mode(mode) } #[pyfunction(name = "cylinder")] #[pyo3(pass_module, signature = (radius, height, detail=24))] fn draw_cylinder( module: &Bound<'_, PyModule>, radius: f32, height: f32, detail: u32, ) -> PyResult<()> { graphics!(module).draw_cylinder(radius, height, detail) } #[pyfunction(name = "cone")] #[pyo3(pass_module, signature = (radius, height, detail=24))] fn draw_cone( module: &Bound<'_, PyModule>, radius: f32, height: f32, detail: u32, ) -> PyResult<()> { graphics!(module).draw_cone(radius, height, detail) } #[pyfunction(name = "torus")] #[pyo3(pass_module, signature = (radius, tube_radius, major_segments=24, minor_segments=16))] fn draw_torus( module: &Bound<'_, PyModule>, radius: f32, tube_radius: f32, major_segments: u32, minor_segments: u32, ) -> PyResult<()> { graphics!(module).draw_torus(radius, tube_radius, major_segments, minor_segments) } #[pyfunction(name = "plane")] #[pyo3(pass_module)] fn draw_plane(module: &Bound<'_, PyModule>, width: f32, height: f32) -> PyResult<()> { graphics!(module).draw_plane(width, height) } #[pyfunction(name = "capsule")] #[pyo3(pass_module, signature = (radius, length, detail=24))] fn draw_capsule( module: &Bound<'_, PyModule>, radius: f32, length: f32, detail: u32, ) -> PyResult<()> { graphics!(module).draw_capsule(radius, length, detail) } #[pyfunction(name = "conical_frustum")] #[pyo3(pass_module, signature = (radius_top, radius_bottom, height, detail=24))] fn draw_conical_frustum( module: &Bound<'_, PyModule>, radius_top: f32, radius_bottom: f32, height: f32, detail: u32, ) -> PyResult<()> { graphics!(module).draw_conical_frustum(radius_top, radius_bottom, height, detail) } #[pyfunction(name = "tetrahedron")] #[pyo3(pass_module)] fn draw_tetrahedron(module: &Bound<'_, PyModule>, radius: f32) -> PyResult<()> { graphics!(module).draw_tetrahedron(radius) } #[cfg(feature = "webcam")] #[pyfunction] #[pyo3(signature = (width=None, height=None, framerate=None))] fn create_webcam( width: Option, height: Option, framerate: Option, ) -> PyResult { webcam::Webcam::new(width, height, framerate) } #[pyfunction] fn midi_connect(port: usize) -> PyResult<()> { midi::connect(port) } #[pyfunction] fn midi_disconnect() -> PyResult<()> { midi::disconnect() } #[pyfunction] fn midi_refresh_ports() -> PyResult<()> { midi::refresh_ports() } #[pyfunction] fn midi_list_ports() -> PyResult> { midi::list_ports() } #[pyfunction] fn midi_play_notes(note: u8, duration: u64) -> PyResult<()> { midi::play_notes(note, duration) } #[pyfunction] fn midi_note_on(note: u8, velocity: u8) -> PyResult<()> { midi::note_on(note, velocity) } #[pyfunction] fn midi_note_off(note: u8) -> PyResult<()> { midi::note_off(note) } #[pyfunction] fn key_is_down(key_code: u32) -> PyResult { input::key_is_down(key_code) } #[pyfunction] fn key_just_pressed(key_code: u32) -> PyResult { input::key_just_pressed(key_code) } #[pyfunction] #[pyo3(pass_module, signature = (density=None))] fn pixel_density<'py>( module: &Bound<'py, PyModule>, density: Option, ) -> PyResult> { let py = module.py(); match density { Some(d) => { graphics!(module).surface.set_pixel_density(d)?; Ok(py.None()) } None => { let graphics = get_graphics(module)? .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; let current = graphics.surface.pixel_density()?; Ok(current.into_pyobject(py)?.into_any().unbind()) } } } #[pyfunction] #[pyo3(pass_module)] fn display_density(module: &Bound<'_, PyModule>) -> PyResult { let graphics = get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.surface.display_density() } // private stuff #[pyfunction] fn _dyn_frame_count() -> PyResult { time::frame_count() } #[pyfunction] fn _dyn_delta_time() -> PyResult { time::delta_time() } #[pyfunction] fn _dyn_elapsed_time() -> PyResult { time::elapsed_time() } #[pyfunction] fn monitors() -> PyResult> { monitor::list() } #[pyfunction] fn primary_monitor() -> PyResult> { monitor::primary() } }