diff --git a/crates/processing_input/src/lib.rs b/crates/processing_input/src/lib.rs index 38f74e8..9452d98 100644 --- a/crates/processing_input/src/lib.rs +++ b/crates/processing_input/src/lib.rs @@ -295,3 +295,47 @@ pub fn input_key() -> error::Result> { pub fn input_key_code() -> error::Result> { app_mut(|app| Ok(app.world().resource::().code)) } + +pub fn input_mouse_any_just_pressed() -> error::Result { + app_mut(|app| { + Ok(app + .world() + .resource::>() + .get_just_pressed() + .next() + .is_some()) + }) +} + +pub fn input_mouse_any_just_released() -> error::Result { + app_mut(|app| { + Ok(app + .world() + .resource::>() + .get_just_released() + .next() + .is_some()) + }) +} + +pub fn input_key_any_just_pressed() -> error::Result { + app_mut(|app| Ok(app.world().resource::().just_pressed)) +} + +pub fn input_key_any_just_released() -> error::Result { + app_mut(|app| Ok(app.world().resource::().just_released)) +} + +pub fn input_mouse_moved() -> error::Result { + app_mut(|app| { + let d = app.world().resource::().delta; + Ok(d.x != 0.0 || d.y != 0.0) + }) +} + +pub fn input_mouse_scrolled() -> error::Result { + app_mut(|app| { + let d = app.world().resource::().delta; + Ok(d.x != 0.0 || d.y != 0.0) + }) +} diff --git a/crates/processing_input/src/state.rs b/crates/processing_input/src/state.rs index 1db8f3d..e63995a 100644 --- a/crates/processing_input/src/state.rs +++ b/crates/processing_input/src/state.rs @@ -26,6 +26,8 @@ impl CursorPosition { pub struct LastKey { pub code: Option, pub character: Option, + pub just_pressed: bool, + pub just_released: bool, } #[derive(Resource, Default)] @@ -51,13 +53,21 @@ pub fn track_cursor_position( } pub fn track_last_key(mut reader: MessageReader, mut last: ResMut) { - if let Some(event) = reader - .read() - .filter(|e| e.state == ButtonState::Pressed) - .last() - { - last.code = Some(event.key_code); - last.character = event.text.as_ref().and_then(|t| t.chars().next()); + // our cbs fire on key auto repeats but bevy just_pressed only fires on the initial press + // we track edge state off of the raw input stream + last.just_pressed = false; + last.just_released = false; + for event in reader.read() { + match event.state { + ButtonState::Pressed => { + last.code = Some(event.key_code); + last.character = event.text.as_ref().and_then(|t| t.chars().next()); + last.just_pressed = true; + } + ButtonState::Released => { + last.just_released = true; + } + } } } diff --git a/crates/processing_pyo3/mewnala/__init__.py b/crates/processing_pyo3/mewnala/__init__.py index 7fe6f79..4427ced 100644 --- a/crates/processing_pyo3/mewnala/__init__.py +++ b/crates/processing_pyo3/mewnala/__init__.py @@ -9,4 +9,66 @@ _sub = getattr(_native, _name, None) if _sub is not None: _sys.modules[f"{__name__}.{_name}"] = _sub -del _sys, _native, _name, _sub + +# global var handling. for wildcard import of our module, we copy into globals, otherwise +# we dispatch to get attr and call the underlying getter method + +_DYNAMIC_GRAPHICS_ATTRS = ( + "width", + "height", + "focused", + "pixel_width", + "pixel_height", + "pixel_density", +) +_DYNAMIC_FUNCTIONS = ( + "mouse_x", + "mouse_y", + "pmouse_x", + "pmouse_y", + "frame_count", + "delta_time", + "elapsed_time", +) +_DYNAMIC = ( + _DYNAMIC_GRAPHICS_ATTRS + _DYNAMIC_FUNCTIONS + ( + "mouse_is_pressed", + "mouse_button", + "moved_x", + "moved_y", + "mouse_wheel", + "key", + "key_code", + "key_is_pressed", + "display_width", + "display_height", + ) +) + + +def _get_graphics(): + return getattr(_native, "_graphics", None) + + +def __getattr__(name): + if name in _DYNAMIC_GRAPHICS_ATTRS: + g = _get_graphics() + if g is not None: + return getattr(g, name) + if name in _DYNAMIC_FUNCTIONS: + fn = getattr(_native, name, None) + if callable(fn): + return fn() + if name in ("display_width", "display_height"): + mon = getattr(_native, "primary_monitor", lambda: None)() + if mon is None: + return 0 + return mon.width if name == "display_width" else mon.height + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__(): + return sorted(set(list(globals().keys()) + list(_DYNAMIC))) + + +del _sys, _name, _sub diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 5b0dbc9..bac0137 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -132,40 +132,7 @@ impl PyBlendMode { const OP_MAX: u8 = 4; } -#[pyclass(unsendable)] -pub struct Surface { - pub(crate) entity: Entity, - glfw_ctx: Option, -} - -#[pymethods] -impl Surface { - pub fn poll_events(&mut self) -> bool { - match &mut self.glfw_ctx { - Some(ctx) => ctx.poll_events(), - None => true, // no-op, offscreen surfaces never close - } - } - - #[getter] - pub fn display_density(&self) -> PyResult { - match &self.glfw_ctx { - Some(ctx) => Ok(ctx.content_scale()), - None => Ok(1.0), - } - } - - pub fn set_pixel_density(&self, density: f32) -> PyResult<()> { - surface_set_pixel_density(self.entity, density) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) - } -} - -impl Drop for Surface { - fn drop(&mut self) { - let _ = surface_destroy(self.entity); - } -} +pub use crate::surface::Surface; #[pyclass] #[derive(Debug)] @@ -318,6 +285,10 @@ impl Geometry { pub struct Graphics { pub(crate) entity: Entity, pub surface: Surface, + #[pyo3(get)] + pub width: u32, + #[pyo3(get)] + pub height: u32, } impl Drop for Graphics { @@ -364,6 +335,8 @@ impl Graphics { Ok(Self { entity: graphics, surface, + width, + height, }) } @@ -399,9 +372,31 @@ impl Graphics { Ok(Self { entity: graphics, surface, + width, + height, }) } + #[getter] + pub fn focused(&self) -> PyResult { + self.surface.focused() + } + + #[getter] + pub fn pixel_density(&self) -> PyResult { + self.surface.pixel_density() + } + + #[getter] + pub fn pixel_width(&self) -> PyResult { + self.surface.pixel_width() + } + + #[getter] + pub fn pixel_height(&self) -> PyResult { + self.surface.pixel_height() + } + pub fn readback_png(&self) -> PyResult> { let raw = graphics_readback_raw(self.entity) .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; diff --git a/crates/processing_pyo3/src/input.rs b/crates/processing_pyo3/src/input.rs index fa9feb4..5096d5c 100644 --- a/crates/processing_pyo3/src/input.rs +++ b/crates/processing_pyo3/src/input.rs @@ -80,20 +80,20 @@ pub fn key_code() -> PyResult> { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } -pub fn sync_globals(func: &Bound<'_, PyAny>, surface: Entity) -> PyResult<()> { - let globals = func.getattr("__globals__")?; - globals.set_item("mouse_x", mouse_x(surface)?)?; - globals.set_item("mouse_y", mouse_y(surface)?)?; - globals.set_item("pmouse_x", pmouse_x(surface)?)?; - globals.set_item("pmouse_y", pmouse_y(surface)?)?; - globals.set_item("mouse_is_pressed", mouse_is_pressed()?)?; - globals.set_item("mouse_button", mouse_button()?)?; - globals.set_item("moved_x", moved_x()?)?; - globals.set_item("moved_y", moved_y()?)?; - globals.set_item("mouse_wheel", mouse_wheel()?)?; - globals.set_item("key", key()?)?; - globals.set_item("key_code", key_code()?)?; - globals.set_item("key_is_pressed", key_is_pressed()?)?; +pub fn sync_globals(globals: &Bound<'_, PyAny>, surface: Entity) -> PyResult<()> { + use crate::set_tracked; + set_tracked(globals, "mouse_x", mouse_x(surface)?)?; + set_tracked(globals, "mouse_y", mouse_y(surface)?)?; + set_tracked(globals, "pmouse_x", pmouse_x(surface)?)?; + set_tracked(globals, "pmouse_y", pmouse_y(surface)?)?; + set_tracked(globals, "mouse_is_pressed", mouse_is_pressed()?)?; + set_tracked(globals, "mouse_button", mouse_button()?)?; + set_tracked(globals, "moved_x", moved_x()?)?; + set_tracked(globals, "moved_y", moved_y()?)?; + set_tracked(globals, "mouse_wheel", mouse_wheel()?)?; + set_tracked(globals, "key", key()?)?; + set_tracked(globals, "key_code", key_code()?)?; + set_tracked(globals, "key_is_pressed", key_is_pressed()?)?; Ok(()) } diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 01ddce9..7797510 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -18,7 +18,10 @@ mod input; pub(crate) mod material; pub(crate) mod math; mod midi; +mod monitor; pub(crate) mod shader; +mod surface; +mod time; #[cfg(feature = "webcam")] mod webcam; @@ -28,6 +31,7 @@ use graphics::{ use material::Material; use pyo3::{ + BoundObject, exceptions::PyRuntimeError, prelude::*, types::{PyDict, PyTuple}, @@ -37,8 +41,104 @@ use std::ffi::{CStr, CString}; use bevy::log::warn; use gltf::Gltf; +use std::cell::RefCell; +use std::collections::HashMap; use std::env; +thread_local! { + static LAST_GLOBALS: RefCell>> = RefCell::new(HashMap::new()); +} + + +/// 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 sync_globals(module: &Bound<'_, PyModule>, globals: &Bound<'_, PyAny>) -> PyResult<()> { + let graphics = + get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; + input::sync_globals(globals, graphics.surface.entity)?; + surface::sync_globals(globals, &graphics.surface, graphics.width, graphics.height)?; + time::sync_globals(globals)?; + Ok(()) +} + +fn try_call(locals: &Bound<'_, PyAny>, name: &str) -> PyResult<()> { + if let Ok(cb) = locals.get_item(name) + && cb.is_callable() + { + cb.call0() + .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + } + Ok(()) +} + +fn dispatch_event_callbacks(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)? { + try_call(locals, "mouse_pressed")?; + } + if input_mouse_any_just_released().map_err(err)? { + try_call(locals, "mouse_released")?; + } + if input_mouse_moved().map_err(err)? { + if input_mouse_is_pressed().map_err(err)? { + try_call(locals, "mouse_dragged")?; + } else { + try_call(locals, "mouse_moved")?; + } + } + if input_mouse_scrolled().map_err(err)? { + try_call(locals, "mouse_wheel")?; + } + if input_key_any_just_pressed().map_err(err)? { + try_call(locals, "key_pressed")?; + } + if input_key_any_just_released().map_err(err)? { + try_call(locals, "key_released")?; + } + Ok(()) +} + /// Get a shared ref to the Graphics context, or return Ok(()) if not yet initialized. macro_rules! graphics { ($module:expr) => { @@ -139,6 +239,10 @@ mod mewnala { #[cfg(feature = "cuda")] #[pymodule_export] use super::cuda::CudaImage; + #[pymodule_export] + use super::monitor::Monitor; + #[pymodule_export] + use super::surface::Surface; // Stroke cap/join #[pymodule_export] @@ -595,6 +699,17 @@ mod mewnala { 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(ns)?; + Ok(()) + } + #[pyfunction] #[pyo3(pass_module)] fn redraw(module: &Bound<'_, PyModule>) -> PyResult<()> { @@ -703,12 +818,10 @@ mod mewnala { // call setup setup_fn.call0()?; - let mut frame_count: u64 = 0; - { - let graphics = get_graphics(module)? - .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; - input::sync_globals(&draw_fn, graphics.surface.entity)?; - } + let mut globals = draw_fn.getattr("__globals__")?; + sync_globals(module, &globals)?; + + // start draw loop loop { { let mut graphics = get_graphics_mut(module)? @@ -730,6 +843,10 @@ mod mewnala { } draw_fn = locals.get_item("draw").unwrap().unwrap(); + globals = draw_fn.getattr("__globals__")?; + reset_tracked_globals(); + + dbg!(locals); } if !graphics.surface.poll_events() { @@ -738,14 +855,8 @@ mod mewnala { graphics.begin_draw()?; } - { - let graphics = get_graphics(module)? - .ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; - input::sync_globals(&draw_fn, graphics.surface.entity)?; - let globals = draw_fn.getattr("__globals__")?; - globals.set_item("frame_count", frame_count)?; - frame_count += 1; - } + sync_globals(module, &globals)?; + dispatch_event_callbacks(&locals)?; draw_fn .call0() @@ -1446,4 +1557,29 @@ mod mewnala { get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?; graphics.surface.display_density() } + + #[pyfunction] + fn frame_count() -> PyResult { + time::frame_count() + } + + #[pyfunction] + fn delta_time() -> PyResult { + time::delta_time() + } + + #[pyfunction] + fn elapsed_time() -> PyResult { + time::elapsed_time() + } + + #[pyfunction] + fn monitors() -> PyResult> { + monitor::list() + } + + #[pyfunction] + fn primary_monitor() -> PyResult> { + monitor::primary() + } } diff --git a/crates/processing_pyo3/src/monitor.rs b/crates/processing_pyo3/src/monitor.rs new file mode 100644 index 0000000..6c72f86 --- /dev/null +++ b/crates/processing_pyo3/src/monitor.rs @@ -0,0 +1,50 @@ +use bevy::prelude::Entity; +use processing::prelude::*; +use pyo3::{exceptions::PyRuntimeError, prelude::*}; + +#[pyclass(unsendable)] +pub struct Monitor { + pub(crate) entity: Entity, +} + +#[pymethods] +impl Monitor { + #[getter] + pub fn width(&self) -> PyResult { + monitor_width(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn height(&self) -> PyResult { + monitor_height(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn scale_factor(&self) -> PyResult { + monitor_scale_factor(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn refresh_rate_millihertz(&self) -> PyResult> { + monitor_refresh_rate_millihertz(self.entity) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn name(&self) -> PyResult> { + monitor_name(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } +} + +pub fn primary() -> PyResult> { + let entity = monitor_primary().map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + Ok(entity.map(|entity| Monitor { entity })) +} + +pub fn list() -> PyResult> { + let entities = monitor_list().map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + Ok(entities + .into_iter() + .map(|entity| Monitor { entity }) + .collect()) +} diff --git a/crates/processing_pyo3/src/python/ipython_post_execute.py b/crates/processing_pyo3/src/python/ipython_post_execute.py index 79f9829..29f0344 100644 --- a/crates/processing_pyo3/src/python/ipython_post_execute.py +++ b/crates/processing_pyo3/src/python/ipython_post_execute.py @@ -1,8 +1,10 @@ -import processing +import mewnala def _processing_post_execute(result): - processing._present() - if not processing._poll_events(): - processing._graphics = None + mewnala._present() + if not mewnala._poll_events(): + mewnala._graphics = None + return + mewnala._tick(get_ipython().user_ns) get_ipython().events.register('post_run_cell', _processing_post_execute) diff --git a/crates/processing_pyo3/src/python/jupyter_post_execute.py b/crates/processing_pyo3/src/python/jupyter_post_execute.py index a0067bd..9a78472 100644 --- a/crates/processing_pyo3/src/python/jupyter_post_execute.py +++ b/crates/processing_pyo3/src/python/jupyter_post_execute.py @@ -1,9 +1,10 @@ -import processing +import mewnala import IPython.display as _ipy_display def _processing_post_execute(result): - processing._present() - png_data = processing._readback_png() + mewnala._present() + mewnala._tick(get_ipython().user_ns) + png_data = mewnala._readback_png() if png_data is not None: _ipy_display.display(_ipy_display.Image(data=bytes(png_data))) diff --git a/crates/processing_pyo3/src/python/register_inputhook.py b/crates/processing_pyo3/src/python/register_inputhook.py index 0409864..aa11a17 100644 --- a/crates/processing_pyo3/src/python/register_inputhook.py +++ b/crates/processing_pyo3/src/python/register_inputhook.py @@ -1,12 +1,17 @@ -import processing +import mewnala import time +import traceback from IPython.terminal.pt_inputhooks import register def _processing_inputhook(context): while not context.input_is_ready(): - if not processing._poll_events(): - processing._graphics = None + if not mewnala._poll_events(): + mewnala._graphics = None break + try: + mewnala._tick(get_ipython().user_ns) + except Exception: + traceback.print_exc() time.sleep(1.0 / 60.0) register('processing', _processing_inputhook) diff --git a/crates/processing_pyo3/src/surface.rs b/crates/processing_pyo3/src/surface.rs new file mode 100644 index 0000000..98877f8 --- /dev/null +++ b/crates/processing_pyo3/src/surface.rs @@ -0,0 +1,84 @@ +use bevy::prelude::Entity; +use processing::prelude::*; +use pyo3::{exceptions::PyRuntimeError, prelude::*}; + +use crate::glfw::GlfwContext; +use crate::monitor; +use crate::set_tracked; + +#[pyclass(unsendable)] +pub struct Surface { + pub(crate) entity: Entity, + pub(crate) glfw_ctx: Option, +} + +#[pymethods] +impl Surface { + pub fn poll_events(&mut self) -> bool { + match &mut self.glfw_ctx { + Some(ctx) => ctx.poll_events(), + None => true, // no-op, offscreen surfaces never close + } + } + + #[getter] + pub fn focused(&self) -> PyResult { + surface_focused(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn pixel_density(&self) -> PyResult { + surface_scale_factor(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn pixel_width(&self) -> PyResult { + surface_physical_width(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn pixel_height(&self) -> PyResult { + surface_physical_height(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[getter] + pub fn display_density(&self) -> PyResult { + match &self.glfw_ctx { + Some(ctx) => Ok(ctx.content_scale()), + None => Ok(1.0), + } + } + + pub fn set_pixel_density(&self, density: f32) -> PyResult<()> { + surface_set_pixel_density(self.entity, density) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } +} + +impl Drop for Surface { + fn drop(&mut self) { + let _ = surface_destroy(self.entity); + } +} + +pub fn sync_globals( + globals: &Bound<'_, PyAny>, + surface: &Surface, + canvas_width: u32, + canvas_height: u32, +) -> PyResult<()> { + set_tracked(globals, "width", canvas_width)?; + set_tracked(globals, "height", canvas_height)?; + set_tracked(globals, "focused", surface.focused()?)?; + set_tracked(globals, "pixel_density", surface.pixel_density()?)?; + set_tracked(globals, "pixel_width", surface.pixel_width()?)?; + set_tracked(globals, "pixel_height", surface.pixel_height()?)?; + + let (dw, dh) = match monitor::primary()? { + Some(m) => (m.width()?, m.height()?), + None => (0, 0), + }; + set_tracked(globals, "display_width", dw)?; + set_tracked(globals, "display_height", dh)?; + Ok(()) +} diff --git a/crates/processing_pyo3/src/time.rs b/crates/processing_pyo3/src/time.rs new file mode 100644 index 0000000..d638a56 --- /dev/null +++ b/crates/processing_pyo3/src/time.rs @@ -0,0 +1,20 @@ +use pyo3::{exceptions::PyRuntimeError, prelude::*}; + +pub fn frame_count() -> PyResult { + processing::prelude::frame_count().map_err(|e| PyRuntimeError::new_err(format!("{e}"))) +} + +pub fn delta_time() -> PyResult { + processing::prelude::delta_time().map_err(|e| PyRuntimeError::new_err(format!("{e}"))) +} + +pub fn elapsed_time() -> PyResult { + processing::prelude::elapsed_time().map_err(|e| PyRuntimeError::new_err(format!("{e}"))) +} + +pub fn sync_globals(globals: &Bound<'_, PyAny>) -> PyResult<()> { + crate::set_tracked(globals, "frame_count", frame_count()?)?; + crate::set_tracked(globals, "delta_time", delta_time()?)?; + crate::set_tracked(globals, "elapsed_time", elapsed_time()?)?; + Ok(()) +} diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index f2dd767..dc188dc 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -8,9 +8,11 @@ pub mod graphics; pub mod image; pub mod light; pub mod material; +pub mod monitor; pub mod render; pub mod sketch; pub(crate) mod surface; +pub mod time; pub mod transform; use std::path::PathBuf; @@ -1307,6 +1309,117 @@ pub fn material_destroy(entity: Entity) -> error::Result<()> { }) } +pub fn surface_focused(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(surface::focused, entity) + .unwrap()) + }) +} + +pub fn surface_scale_factor(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(surface::scale_factor, entity) + .unwrap()) + }) +} + +pub fn surface_physical_width(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(surface::physical_width, entity) + .unwrap()) + }) +} + +pub fn surface_physical_height(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(surface::physical_height, entity) + .unwrap()) + }) +} + +pub fn monitor_list() -> error::Result> { + app_mut(|app| Ok(app.world_mut().run_system_cached(monitor::list).unwrap())) +} + +pub fn monitor_primary() -> error::Result> { + app_mut(|app| Ok(app.world_mut().run_system_cached(monitor::primary).unwrap())) +} + +pub fn monitor_width(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(monitor::width, entity) + .unwrap()) + }) +} + +pub fn monitor_height(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(monitor::height, entity) + .unwrap()) + }) +} + +pub fn monitor_scale_factor(entity: Entity) -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(monitor::scale_factor, entity) + .unwrap()) + }) +} + +pub fn monitor_refresh_rate_millihertz(entity: Entity) -> error::Result> { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(monitor::refresh_rate_millihertz, entity) + .unwrap()) + }) +} + +pub fn monitor_name(entity: Entity) -> error::Result> { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(monitor::name, entity) + .unwrap()) + }) +} + +pub fn frame_count() -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached(time::frame_count) + .unwrap()) + }) +} + +pub fn delta_time() -> error::Result { + app_mut(|app| Ok(app.world_mut().run_system_cached(time::delta_secs).unwrap())) +} + +pub fn elapsed_time() -> error::Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached(time::elapsed_secs) + .unwrap()) + }) +} + #[cfg(not(target_arch = "wasm32"))] pub fn gltf_load(graphics_entity: Entity, path: &str) -> error::Result { app_mut(|app| { diff --git a/crates/processing_render/src/monitor.rs b/crates/processing_render/src/monitor.rs new file mode 100644 index 0000000..845d018 --- /dev/null +++ b/crates/processing_render/src/monitor.rs @@ -0,0 +1,33 @@ +use bevy::prelude::*; +use bevy::window::{Monitor, PrimaryMonitor}; + +pub fn list(query: Query>) -> Vec { + query.iter().collect() +} + +pub fn primary(query: Query>) -> Option { + query.iter().next() +} + +pub fn width(In(entity): In, query: Query<&Monitor>) -> u32 { + query.get(entity).map(|m| m.physical_width).unwrap_or(0) +} + +pub fn height(In(entity): In, query: Query<&Monitor>) -> u32 { + query.get(entity).map(|m| m.physical_height).unwrap_or(0) +} + +pub fn scale_factor(In(entity): In, query: Query<&Monitor>) -> f64 { + query.get(entity).map(|m| m.scale_factor).unwrap_or(1.0) +} + +pub fn refresh_rate_millihertz(In(entity): In, query: Query<&Monitor>) -> Option { + query + .get(entity) + .ok() + .and_then(|m| m.refresh_rate_millihertz) +} + +pub fn name(In(entity): In, query: Query<&Monitor>) -> Option { + query.get(entity).ok().and_then(|m| m.name.clone()) +} diff --git a/crates/processing_render/src/surface.rs b/crates/processing_render/src/surface.rs index 20f46d3..e19f007 100644 --- a/crates/processing_render/src/surface.rs +++ b/crates/processing_render/src/surface.rs @@ -392,3 +392,28 @@ pub fn set_pixel_density( Err(error::ProcessingError::SurfaceNotFound) } } + +pub fn focused(In(entity): In, query: Query<&Window>) -> bool { + query.get(entity).map(|w| w.focused).unwrap_or(false) +} + +pub fn scale_factor(In(entity): In, query: Query<&Window>) -> f32 { + query + .get(entity) + .map(|w| w.resolution.scale_factor()) + .unwrap_or(1.0) +} + +pub fn physical_width(In(entity): In, query: Query<&Window>) -> u32 { + query + .get(entity) + .map(|w| w.resolution.physical_width()) + .unwrap_or(0) +} + +pub fn physical_height(In(entity): In, query: Query<&Window>) -> u32 { + query + .get(entity) + .map(|w| w.resolution.physical_height()) + .unwrap_or(0) +} diff --git a/crates/processing_render/src/time.rs b/crates/processing_render/src/time.rs new file mode 100644 index 0000000..4f7bcbd --- /dev/null +++ b/crates/processing_render/src/time.rs @@ -0,0 +1,15 @@ +use bevy::diagnostic::FrameCount; +use bevy::prelude::*; +use bevy::time::Time; + +pub fn frame_count(count: Option>) -> u32 { + count.map(|c| c.0).unwrap_or(0) +} + +pub fn delta_secs(time: Option>) -> f32 { + time.map(|t| t.delta_secs()).unwrap_or(0.0) +} + +pub fn elapsed_secs(time: Option>) -> f32 { + time.map(|t| t.elapsed_secs()).unwrap_or(0.0) +}