diff --git a/crates/processing_core/src/constants.rs b/crates/processing_core/src/constants.rs new file mode 100644 index 0000000..0e0413b --- /dev/null +++ b/crates/processing_core/src/constants.rs @@ -0,0 +1,138 @@ +pub const CORNER: &str = "corner"; +pub const CORNERS: &str = "corners"; +pub const RADIUS: &str = "radius"; +pub const CENTER: &str = "center"; // also the middle mouse button + +pub const ROUND: &str = "round"; // caps and joins +pub const SQUARE: &str = "square"; +pub const PROJECT: &str = "project"; +pub const MITER: &str = "miter"; +pub const BEVEL: &str = "bevel"; + +pub const POLYGON: &str = "polygon"; +pub const POINTS: &str = "points"; +pub const LINES: &str = "lines"; +pub const TRIANGLES: &str = "triangles"; +pub const TRIANGLE_FAN: &str = "triangle_fan"; +pub const TRIANGLE_STRIP: &str = "triangle_strip"; +pub const QUADS: &str = "quads"; +pub const QUAD_STRIP: &str = "quad_strip"; +pub const LINE_STRIP: &str = "line_strip"; // geometry topology; no begin_shape equivalent + +pub const OPEN: &str = "open"; +pub const CHORD: &str = "chord"; +pub const PIE: &str = "pie"; +pub const CLOSE: bool = true; + +pub const LEFT: &str = "left"; +pub const RIGHT: &str = "right"; + +pub const NEAREST: &str = "nearest"; +pub const CLAMP: &str = "clamp"; +pub const REPEAT: &str = "repeat"; +pub const MIRROR: &str = "mirror"; + +pub const SRGB: &str = "srgb"; +pub const LINEAR: &str = "linear"; // also the Sampler filter +pub const HSL: &str = "hsl"; +pub const HSV: &str = "hsv"; +pub const HWB: &str = "hwb"; +pub const OKLAB: &str = "oklab"; +pub const OKLCH: &str = "oklch"; +pub const LAB: &str = "lab"; +pub const LCH: &str = "lch"; +pub const XYZ: &str = "xyz"; + +pub const PI: f32 = std::f32::consts::PI; +pub const TWO_PI: f32 = std::f32::consts::TAU; +pub const HALF_PI: f32 = std::f32::consts::FRAC_PI_2; +pub const QUARTER_PI: f32 = std::f32::consts::FRAC_PI_4; +pub const TAU: f32 = std::f32::consts::TAU; +pub const DEG_TO_RAD: f32 = std::f32::consts::PI / 180.0; +pub const RAD_TO_DEG: f32 = 180.0 / std::f32::consts::PI; + +// Key codes stay numeric (compared against `key_code`); values must match `key_code_to_u32`. + +pub const KEY_A: u32 = 65; +pub const KEY_B: u32 = 66; +pub const KEY_C: u32 = 67; +pub const KEY_D: u32 = 68; +pub const KEY_E: u32 = 69; +pub const KEY_F: u32 = 70; +pub const KEY_G: u32 = 71; +pub const KEY_H: u32 = 72; +pub const KEY_I: u32 = 73; +pub const KEY_J: u32 = 74; +pub const KEY_K: u32 = 75; +pub const KEY_L: u32 = 76; +pub const KEY_M: u32 = 77; +pub const KEY_N: u32 = 78; +pub const KEY_O: u32 = 79; +pub const KEY_P: u32 = 80; +pub const KEY_Q: u32 = 81; +pub const KEY_R: u32 = 82; +pub const KEY_S: u32 = 83; +pub const KEY_T: u32 = 84; +pub const KEY_U: u32 = 85; +pub const KEY_V: u32 = 86; +pub const KEY_W: u32 = 87; +pub const KEY_X: u32 = 88; +pub const KEY_Y: u32 = 89; +pub const KEY_Z: u32 = 90; + +pub const KEY_0: u32 = 48; +pub const KEY_1: u32 = 49; +pub const KEY_2: u32 = 50; +pub const KEY_3: u32 = 51; +pub const KEY_4: u32 = 52; +pub const KEY_5: u32 = 53; +pub const KEY_6: u32 = 54; +pub const KEY_7: u32 = 55; +pub const KEY_8: u32 = 56; +pub const KEY_9: u32 = 57; + +pub const SPACE: u32 = 32; +pub const QUOTE: u32 = 39; +pub const COMMA: u32 = 44; +pub const MINUS: u32 = 45; +pub const PERIOD: u32 = 46; +pub const SLASH: u32 = 47; +pub const SEMICOLON: u32 = 59; +pub const EQUAL: u32 = 61; +pub const BRACKET_LEFT: u32 = 91; +pub const BACKSLASH: u32 = 92; +pub const BRACKET_RIGHT: u32 = 93; +pub const BACKQUOTE: u32 = 96; + +pub const ESCAPE: u32 = 256; +pub const ENTER: u32 = 257; +pub const TAB: u32 = 258; +pub const BACKSPACE: u32 = 259; +pub const INSERT: u32 = 260; +pub const DELETE: u32 = 261; +pub const UP: u32 = 265; +pub const DOWN: u32 = 264; +pub const LEFT_ARROW: u32 = 263; +pub const RIGHT_ARROW: u32 = 262; +pub const PAGE_UP: u32 = 266; +pub const PAGE_DOWN: u32 = 267; +pub const HOME: u32 = 268; +pub const END: u32 = 269; + +pub const SHIFT: u32 = 340; +pub const CONTROL: u32 = 341; +pub const ALT: u32 = 342; +pub const SUPER: u32 = 343; + +pub const F1: u32 = 290; +pub const F2: u32 = 291; +pub const F3: u32 = 292; +pub const F4: u32 = 293; +pub const F5: u32 = 294; +pub const F6: u32 = 295; +pub const F7: u32 = 296; +pub const F8: u32 = 297; +pub const F9: u32 = 298; +pub const F10: u32 = 299; +pub const F11: u32 = 300; +pub const F12: u32 = 301; diff --git a/crates/processing_core/src/lib.rs b/crates/processing_core/src/lib.rs index b15688b..489754e 100644 --- a/crates/processing_core/src/lib.rs +++ b/crates/processing_core/src/lib.rs @@ -1,4 +1,5 @@ pub mod config; +pub mod constants; pub mod error; use std::cell::RefCell; diff --git a/crates/processing_pyo3/src/constants.rs b/crates/processing_pyo3/src/constants.rs new file mode 100644 index 0000000..872c91c --- /dev/null +++ b/crates/processing_pyo3/src/constants.rs @@ -0,0 +1,96 @@ +use processing::prelude::BlendMode; +use processing::prelude::constants as c; +use pyo3::prelude::*; +use pyo3::types::PyModule; + +use crate::PyBlendMode; + +macro_rules! add { + ($m:expr, $($name:ident),+ $(,)?) => { + $( $m.add(stringify!($name), c::$name)?; )+ + }; +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + add!(m, ROUND, SQUARE, PROJECT, MITER, BEVEL); + add!( + m, + POLYGON, + POINTS, + LINES, + TRIANGLES, + TRIANGLE_FAN, + TRIANGLE_STRIP, + QUADS, + QUAD_STRIP, + LINE_STRIP + ); + add!(m, CORNER, CORNERS, CENTER, RADIUS); + add!(m, OPEN, CHORD, PIE, CLOSE); + add!(m, LEFT, RIGHT); + add!(m, NEAREST, CLAMP, REPEAT, MIRROR); + add!(m, SRGB, LINEAR, HSL, HSV, HWB, OKLAB, OKLCH, LAB, LCH, XYZ); + add!( + m, PI, TWO_PI, HALF_PI, QUARTER_PI, TAU, DEG_TO_RAD, RAD_TO_DEG + ); + + add!( + m, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, + KEY_Z + ); + add!( + m, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 + ); + add!( + m, + SPACE, + QUOTE, + COMMA, + MINUS, + PERIOD, + SLASH, + SEMICOLON, + EQUAL, + BRACKET_LEFT, + BACKSLASH, + BRACKET_RIGHT, + BACKQUOTE + ); + add!( + m, + ESCAPE, + ENTER, + TAB, + BACKSPACE, + INSERT, + DELETE, + UP, + DOWN, + LEFT_ARROW, + RIGHT_ARROW, + PAGE_UP, + PAGE_DOWN, + HOME, + END + ); + add!(m, SHIFT, CONTROL, ALT, SUPER); + add!(m, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12); + + // Objects rather than strings, passed straight to blend_mode(). + m.add("BLEND", PyBlendMode::from_preset(BlendMode::Blend))?; + m.add("ADD", PyBlendMode::from_preset(BlendMode::Add))?; + m.add("SUBTRACT", PyBlendMode::from_preset(BlendMode::Subtract))?; + m.add("DARKEST", PyBlendMode::from_preset(BlendMode::Darkest))?; + m.add("LIGHTEST", PyBlendMode::from_preset(BlendMode::Lightest))?; + m.add( + "DIFFERENCE", + PyBlendMode::from_preset(BlendMode::Difference), + )?; + m.add("EXCLUSION", PyBlendMode::from_preset(BlendMode::Exclusion))?; + m.add("MULTIPLY", PyBlendMode::from_preset(BlendMode::Multiply))?; + m.add("SCREEN", PyBlendMode::from_preset(BlendMode::Screen))?; + m.add("REPLACE", PyBlendMode::from_preset(BlendMode::Replace))?; + + Ok(()) +} diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 1ac95ab..959496b 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -10,7 +10,7 @@ use bevy::{ }; use processing::prelude::*; use pyo3::{ - exceptions::PyRuntimeError, + exceptions::{PyRuntimeError, PyValueError}, prelude::*, types::{PyDict, PyTuple}, }; @@ -136,8 +136,8 @@ impl PyBlendMode { /// /// Controls texture filtering and edge wrapping behavior. /// -/// - `filter` — `Sampler.LINEAR` (smooth) or `Sampler.NEAREST` (pixelated). -/// - `wrap` — `Sampler.CLAMP` (default), `Sampler.REPEAT`, or `Sampler.MIRROR`. +/// - `filter` — `LINEAR` (smooth, default) or `NEAREST` (pixelated). +/// - `wrap` — `CLAMP` (default), `REPEAT`, or `MIRROR`. /// Use `wrap_x`/`wrap_y` to set each axis independently. #[pyclass(from_py_object)] #[derive(Clone)] @@ -147,49 +147,78 @@ pub struct Sampler { pub(crate) wrap_y: u8, } -#[pymethods] impl Sampler { - #[new] - #[pyo3(signature = (*, filter=0, wrap=0, wrap_x=None, wrap_y=None))] - fn new(filter: u8, wrap: u8, wrap_x: Option, wrap_y: Option) -> Self { - Self { - filter, - wrap_x: wrap_x.unwrap_or(wrap), - wrap_y: wrap_y.unwrap_or(wrap), + fn parse_filter(s: &str) -> PyResult { + match () { + _ if s.eq_ignore_ascii_case(constants::LINEAR) => Ok(0), + _ if s.eq_ignore_ascii_case(constants::NEAREST) => Ok(1), + _ => Err(PyValueError::new_err(format!( + "unknown filter: {s:?} (expected {:?} or {:?})", + constants::LINEAR, + constants::NEAREST + ))), } } - fn __repr__(&self) -> String { - let filter_name = match self.filter { - 0 => "LINEAR", - 1 => "NEAREST", + fn parse_wrap(s: &str) -> PyResult { + match () { + _ if s.eq_ignore_ascii_case(constants::CLAMP) => Ok(0), + _ if s.eq_ignore_ascii_case(constants::REPEAT) => Ok(1), + _ if s.eq_ignore_ascii_case(constants::MIRROR) => Ok(2), + _ => Err(PyValueError::new_err(format!( + "unknown wrap: {s:?} (expected {:?}, {:?}, or {:?})", + constants::CLAMP, + constants::REPEAT, + constants::MIRROR + ))), + } + } + + fn filter_name(filter: u8) -> &'static str { + match filter { + 0 => constants::LINEAR, + 1 => constants::NEAREST, _ => "?", - }; - let wrap_name = |v: u8| match v { - 0 => "CLAMP", - 1 => "REPEAT", - 2 => "MIRROR", + } + } + + fn wrap_name(wrap: u8) -> &'static str { + match wrap { + 0 => constants::CLAMP, + 1 => constants::REPEAT, + 2 => constants::MIRROR, _ => "?", - }; - format!( - "Sampler(filter={}, wrap_x={}, wrap_y={})", - filter_name, - wrap_name(self.wrap_x), - wrap_name(self.wrap_y) - ) + } } +} - #[classattr] - const LINEAR: u8 = 0; - #[classattr] - const NEAREST: u8 = 1; +#[pymethods] +impl Sampler { + #[new] + #[pyo3(signature = (*, filter=constants::LINEAR, wrap=constants::CLAMP, wrap_x=None, wrap_y=None))] + fn new(filter: &str, wrap: &str, wrap_x: Option<&str>, wrap_y: Option<&str>) -> PyResult { + let wrap_default = Self::parse_wrap(wrap)?; + Ok(Self { + filter: Self::parse_filter(filter)?, + wrap_x: wrap_x + .map(Self::parse_wrap) + .transpose()? + .unwrap_or(wrap_default), + wrap_y: wrap_y + .map(Self::parse_wrap) + .transpose()? + .unwrap_or(wrap_default), + }) + } - #[classattr] - const CLAMP: u8 = 0; - #[classattr] - const REPEAT: u8 = 1; - #[classattr] - const MIRROR: u8 = 2; + fn __repr__(&self) -> String { + format!( + "Sampler(filter={:?}, wrap_x={:?}, wrap_y={:?})", + Self::filter_name(self.filter), + Self::wrap_name(self.wrap_x), + Self::wrap_name(self.wrap_y) + ) + } } pub use crate::surface::Surface; @@ -325,7 +354,7 @@ impl Image { /// Applies a `Sampler` to this image, controlling filtering and wrapping. /// /// ```python - /// s = Sampler(filter=Sampler.NEAREST, wrap=Sampler.REPEAT) + /// s = Sampler(filter=NEAREST, wrap=REPEAT) /// img.sampler(s) /// ``` fn sampler(&self, sampler: &Sampler) -> PyResult<()> { @@ -359,27 +388,6 @@ pub struct Geometry { pub(crate) entity: Entity, } -#[pyclass] -pub enum Topology { - PointList = 0, - LineList = 1, - LineStrip = 2, - TriangleList = 3, - TriangleStrip = 4, -} - -impl Topology { - pub fn as_u8(&self) -> u8 { - match self { - Self::PointList => 0, - Self::LineList => 1, - Self::LineStrip => 2, - Self::TriangleList => 3, - Self::TriangleStrip => 4, - } - } -} - #[pyclass] pub struct Sketch { pub source: String, @@ -390,11 +398,14 @@ impl Geometry { #[new] #[pyo3(signature = (**kwargs))] pub fn new(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult { - let topology = kwargs - .and_then(|k| k.get_item("topology").ok().flatten()) - .and_then(|t| t.cast_into::().ok()) - .and_then(|t| geometry::Topology::from_u8(t.borrow().as_u8())) - .unwrap_or(geometry::Topology::TriangleList); + let topology = match kwargs.and_then(|k| k.get_item("topology").ok().flatten()) { + Some(t) => { + let s = t.extract::()?; + geometry::Topology::parse(&s) + .ok_or_else(|| PyValueError::new_err(format!("unknown topology: {s:?}")))? + } + None => geometry::Topology::TriangleList, + }; let geometry = geometry_create(topology).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; @@ -454,7 +465,7 @@ impl Geometry { Ok(Self { entity }) } - /// lattice centered at the origin; topology is `PointList`, intended as a + /// lattice centered at the origin; topology is `POINTS`, intended as a /// position source for `Particles(geometry=...)` rather than rasterized. #[staticmethod] #[pyo3(signature = (nx, ny, nz, spacing=1.0))] @@ -696,36 +707,32 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn rect_mode(&self, mode: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::RectMode(processing::prelude::ShapeMode::from(mode)), - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn rect_mode(&self, mode: &str) -> PyResult<()> { + let mode = ShapeMode::parse(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown rect mode: {mode:?}")))?; + graphics_record_command(self.entity, DrawCommand::RectMode(mode)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn ellipse_mode(&self, mode: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::EllipseMode(processing::prelude::ShapeMode::from(mode)), - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn ellipse_mode(&self, mode: &str) -> PyResult<()> { + let mode = ShapeMode::parse(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown ellipse mode: {mode:?}")))?; + graphics_record_command(self.entity, DrawCommand::EllipseMode(mode)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn stroke_cap(&self, cap: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::StrokeCap(processing::prelude::StrokeCapMode::from(cap)), - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn stroke_cap(&self, cap: &str) -> PyResult<()> { + let cap = StrokeCapMode::parse(cap) + .ok_or_else(|| PyValueError::new_err(format!("unknown stroke cap: {cap:?}")))?; + graphics_record_command(self.entity, DrawCommand::StrokeCap(cap)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn stroke_join(&self, join: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::StrokeJoin(processing::prelude::StrokeJoinMode::from(join)), - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn stroke_join(&self, join: &str) -> PyResult<()> { + let join = StrokeJoinMode::parse(join) + .ok_or_else(|| PyValueError::new_err(format!("unknown stroke join: {join:?}")))?; + graphics_record_command(self.entity, DrawCommand::StrokeJoin(join)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } pub fn rect( @@ -836,8 +843,10 @@ impl Graphics { h: f32, start: f32, stop: f32, - mode: u8, + mode: &str, ) -> PyResult<()> { + let mode = ArcMode::parse(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown arc mode: {mode:?}")))?; graphics_record_command( self.entity, DrawCommand::Arc { @@ -847,7 +856,7 @@ impl Graphics { h, start, stop, - mode: processing::prelude::ArcMode::from(mode), + mode, }, ) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) @@ -909,14 +918,11 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - pub fn begin_shape(&self, kind: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::BeginShape { - kind: processing::prelude::ShapeKind::from(kind), - }, - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn begin_shape(&self, kind: &str) -> PyResult<()> { + let kind = ShapeKind::parse(kind) + .ok_or_else(|| PyValueError::new_err(format!("unknown shape kind: {kind:?}")))?; + graphics_record_command(self.entity, DrawCommand::BeginShape { kind }) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } pub fn end_shape(&self, close: bool) -> PyResult<()> { @@ -1331,12 +1337,11 @@ impl Graphics { /// - `CORNER` (default) — `dx`, `dy` is the top-left corner. /// - `CORNERS` — `dx`, `dy` and `d_width`, `d_height` are opposite corners. /// - `CENTER` — `dx`, `dy` is the center of the image. - pub fn image_mode(&self, mode: u8) -> PyResult<()> { - graphics_record_command( - self.entity, - DrawCommand::ImageMode(processing::prelude::ShapeMode::from(mode)), - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + pub fn image_mode(&self, mode: &str) -> PyResult<()> { + let mode = ShapeMode::parse(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown image mode: {mode:?}")))?; + graphics_record_command(self.entity, DrawCommand::ImageMode(mode)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } pub fn create_image(&self, width: u32, height: u32) -> PyResult { @@ -1582,14 +1587,14 @@ impl Graphics { #[pyo3(name = "color_mode", signature = (mode, max1=None, max2=None, max3=None, max_alpha=None))] pub fn set_color_mode<'py>( &self, - mode: u8, + 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 space = crate::color::ColorSpace::from_u8(mode) - .ok_or_else(|| PyRuntimeError::new_err(format!("unknown color space: {mode}")))?; + let space = crate::color::ColorSpace::parse(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown color space: {mode:?}")))?; let parse = |obj: &Bound<'py, PyAny>, ch: usize| crate::color::parse_numeric(&space, obj, ch); let new_mode = match (max1, max2, max3, max_alpha) { diff --git a/crates/processing_pyo3/src/input.rs b/crates/processing_pyo3/src/input.rs index de40a2e..1126d3f 100644 --- a/crates/processing_pyo3/src/input.rs +++ b/crates/processing_pyo3/src/input.rs @@ -38,10 +38,10 @@ pub fn mouse_button() -> PyResult> { processing::prelude::input_mouse_button() .map(|opt| { opt.map(|b| match b { - MouseButton::Left => "LEFT".to_string(), - MouseButton::Right => "RIGHT".to_string(), - MouseButton::Middle => "CENTER".to_string(), - _ => format!("{b:?}"), + MouseButton::Left => constants::LEFT.to_string(), + MouseButton::Right => constants::RIGHT.to_string(), + MouseButton::Middle => constants::CENTER.to_string(), + _ => format!("{b:?}").to_lowercase(), }) }) .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 691776f..dfc0fce 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -10,6 +10,7 @@ //! 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; mod glfw; @@ -29,8 +30,7 @@ mod webcam; use compute::{Buffer, Compute}; use graphics::{ - Font, Geometry, Graphics, Image, Light, PyBlendMode, Sampler, Topology, get_graphics, - get_graphics_mut, + Font, Geometry, Graphics, Image, Light, PyBlendMode, Sampler, get_graphics, get_graphics_mut, }; use material::Material; @@ -353,8 +353,6 @@ mod mewnala { #[pymodule_export] use super::Shader; #[pymodule_export] - use super::Topology; - #[pymodule_export] use super::color::PyColor; #[cfg(feature = "cuda")] #[pymodule_export] @@ -378,288 +376,9 @@ mod mewnala { #[pymodule_export] use super::surface::Surface; - // Stroke cap/join - #[pymodule_export] - const ROUND: u8 = 0; - #[pymodule_export] - const SQUARE: u8 = 1; - #[pymodule_export] - const PROJECT: u8 = 2; - #[pymodule_export] - const MITER: u8 = 1; - #[pymodule_export] - const BEVEL: u8 = 2; - - // Shape kinds - #[pymodule_export] - const POLYGON: u8 = 0; - #[pymodule_export] - const POINTS: u8 = 1; - #[pymodule_export] - const LINES: u8 = 2; - #[pymodule_export] - const TRIANGLES: u8 = 3; - #[pymodule_export] - const TRIANGLE_FAN: u8 = 4; - #[pymodule_export] - const TRIANGLE_STRIP: u8 = 5; - #[pymodule_export] - const QUADS: u8 = 6; - #[pymodule_export] - const QUAD_STRIP: u8 = 7; - - // Shape modes - #[pymodule_export] - const CORNER: u8 = 0; - #[pymodule_export] - const CORNERS: u8 = 1; - // CENTER = 1 - #[pymodule_export] - const RADIUS: u8 = 3; - - // Arc modes - #[pymodule_export] - const OPEN: u8 = 0; - #[pymodule_export] - const CHORD: u8 = 1; - #[pymodule_export] - const PIE: u8 = 2; - - #[pymodule_export] - const CLOSE: bool = true; - - // Mouse buttons - #[pymodule_export] - const LEFT: u8 = 0; - #[pymodule_export] - const CENTER: u8 = 1; - #[pymodule_export] - const RIGHT: u8 = 2; - - // Letters - #[pymodule_export] - const KEY_A: u32 = 65; - #[pymodule_export] - const KEY_B: u32 = 66; - #[pymodule_export] - const KEY_C: u32 = 67; - #[pymodule_export] - const KEY_D: u32 = 68; - #[pymodule_export] - const KEY_E: u32 = 69; - #[pymodule_export] - const KEY_F: u32 = 70; - #[pymodule_export] - const KEY_G: u32 = 71; - #[pymodule_export] - const KEY_H: u32 = 72; - #[pymodule_export] - const KEY_I: u32 = 73; - #[pymodule_export] - const KEY_J: u32 = 74; - #[pymodule_export] - const KEY_K: u32 = 75; - #[pymodule_export] - const KEY_L: u32 = 76; - #[pymodule_export] - const KEY_M: u32 = 77; - #[pymodule_export] - const KEY_N: u32 = 78; - #[pymodule_export] - const KEY_O: u32 = 79; - #[pymodule_export] - const KEY_P: u32 = 80; - #[pymodule_export] - const KEY_Q: u32 = 81; - #[pymodule_export] - const KEY_R: u32 = 82; - #[pymodule_export] - const KEY_S: u32 = 83; - #[pymodule_export] - const KEY_T: u32 = 84; - #[pymodule_export] - const KEY_U: u32 = 85; - #[pymodule_export] - const KEY_V: u32 = 86; - #[pymodule_export] - const KEY_W: u32 = 87; - #[pymodule_export] - const KEY_X: u32 = 88; - #[pymodule_export] - const KEY_Y: u32 = 89; - #[pymodule_export] - const KEY_Z: u32 = 90; - - // Digits - #[pymodule_export] - const KEY_0: u32 = 48; - #[pymodule_export] - const KEY_1: u32 = 49; - #[pymodule_export] - const KEY_2: u32 = 50; - #[pymodule_export] - const KEY_3: u32 = 51; - #[pymodule_export] - const KEY_4: u32 = 52; - #[pymodule_export] - const KEY_5: u32 = 53; - #[pymodule_export] - const KEY_6: u32 = 54; - #[pymodule_export] - const KEY_7: u32 = 55; - #[pymodule_export] - const KEY_8: u32 = 56; - #[pymodule_export] - const KEY_9: u32 = 57; - - // Punctuation/symbols - #[pymodule_export] - const SPACE: u32 = 32; - #[pymodule_export] - const QUOTE: u32 = 39; - #[pymodule_export] - const COMMA: u32 = 44; - #[pymodule_export] - const MINUS: u32 = 45; - #[pymodule_export] - const PERIOD: u32 = 46; - #[pymodule_export] - const SLASH: u32 = 47; - #[pymodule_export] - const SEMICOLON: u32 = 59; - #[pymodule_export] - const EQUAL: u32 = 61; - #[pymodule_export] - const BRACKET_LEFT: u32 = 91; - #[pymodule_export] - const BACKSLASH: u32 = 92; - #[pymodule_export] - const BRACKET_RIGHT: u32 = 93; - #[pymodule_export] - const BACKQUOTE: u32 = 96; - - // Navigation/editing - #[pymodule_export] - const ESCAPE: u32 = 256; - #[pymodule_export] - const ENTER: u32 = 257; - #[pymodule_export] - const TAB: u32 = 258; - #[pymodule_export] - const BACKSPACE: u32 = 259; - #[pymodule_export] - const INSERT: u32 = 260; - #[pymodule_export] - const DELETE: u32 = 261; - #[pymodule_export] - const UP: u32 = 265; - #[pymodule_export] - const DOWN: u32 = 264; - #[pymodule_export] - const LEFT_ARROW: u32 = 263; - #[pymodule_export] - const RIGHT_ARROW: u32 = 262; - #[pymodule_export] - const PAGE_UP: u32 = 266; - #[pymodule_export] - const PAGE_DOWN: u32 = 267; - #[pymodule_export] - const HOME: u32 = 268; - #[pymodule_export] - const END: u32 = 269; - - // Modifiers - #[pymodule_export] - const SHIFT: u32 = 340; - #[pymodule_export] - const CONTROL: u32 = 341; - #[pymodule_export] - const ALT: u32 = 342; - #[pymodule_export] - const SUPER: u32 = 343; - - // Function keys - #[pymodule_export] - const F1: u32 = 290; - #[pymodule_export] - const F2: u32 = 291; - #[pymodule_export] - const F3: u32 = 292; - #[pymodule_export] - const F4: u32 = 293; - #[pymodule_export] - const F5: u32 = 294; - #[pymodule_export] - const F6: u32 = 295; - #[pymodule_export] - const F7: u32 = 296; - #[pymodule_export] - const F8: u32 = 297; - #[pymodule_export] - const F9: u32 = 298; - #[pymodule_export] - const F10: u32 = 299; - #[pymodule_export] - const F11: u32 = 300; - #[pymodule_export] - const F12: u32 = 301; - - // Math constants - #[pymodule_export] - const PI: f32 = std::f32::consts::PI; - #[pymodule_export] - const TWO_PI: f32 = std::f32::consts::TAU; - #[pymodule_export] - const HALF_PI: f32 = std::f32::consts::FRAC_PI_2; - #[pymodule_export] - const QUARTER_PI: f32 = std::f32::consts::FRAC_PI_4; - #[pymodule_export] - const TAU: f32 = std::f32::consts::TAU; - #[pymodule_export] - const DEG_TO_RAD: f32 = std::f32::consts::PI / 180.0; - #[pymodule_export] - const RAD_TO_DEG: f32 = 180.0 / std::f32::consts::PI; - - // color space constants for color_mode() - #[pymodule_export] - const SRGB: u8 = 0; - #[pymodule_export] - const LINEAR: u8 = 1; - #[pymodule_export] - const HSL: u8 = 2; - #[pymodule_export] - const HSV: u8 = 3; - #[pymodule_export] - const HWB: u8 = 4; - #[pymodule_export] - const OKLAB: u8 = 5; - #[pymodule_export] - const OKLCH: u8 = 6; - #[pymodule_export] - const LAB: u8 = 7; - #[pymodule_export] - const LCH: u8 = 8; - #[pymodule_export] - const XYZ: u8 = 9; - #[pymodule_init] fn init(module: &Bound<'_, PyModule>) -> PyResult<()> { - use processing::prelude::BlendMode; - - module.add("BLEND", PyBlendMode::from_preset(BlendMode::Blend))?; - module.add("ADD", PyBlendMode::from_preset(BlendMode::Add))?; - module.add("SUBTRACT", PyBlendMode::from_preset(BlendMode::Subtract))?; - module.add("DARKEST", PyBlendMode::from_preset(BlendMode::Darkest))?; - module.add("LIGHTEST", PyBlendMode::from_preset(BlendMode::Lightest))?; - module.add( - "DIFFERENCE", - PyBlendMode::from_preset(BlendMode::Difference), - )?; - module.add("EXCLUSION", PyBlendMode::from_preset(BlendMode::Exclusion))?; - module.add("MULTIPLY", PyBlendMode::from_preset(BlendMode::Multiply))?; - module.add("SCREEN", PyBlendMode::from_preset(BlendMode::Screen))?; - module.add("REPLACE", PyBlendMode::from_preset(BlendMode::Replace))?; - Ok(()) + super::constants::register(module) } #[pymodule] @@ -1384,7 +1103,7 @@ mod mewnala { #[pyo3(pass_module, signature = (mode, max1=None, max2=None, max3=None, max_alpha=None))] fn color_mode<'py>( module: &Bound<'py, PyModule>, - mode: u8, + mode: &str, max1: Option<&Bound<'py, PyAny>>, max2: Option<&Bound<'py, PyAny>>, max3: Option<&Bound<'py, PyAny>>, @@ -1427,13 +1146,13 @@ mod mewnala { #[pyfunction] #[pyo3(pass_module)] - fn stroke_cap(module: &Bound<'_, PyModule>, cap: u8) -> PyResult<()> { + 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: u8) -> PyResult<()> { + fn stroke_join(module: &Bound<'_, PyModule>, join: &str) -> PyResult<()> { graphics!(module).stroke_join(join) } @@ -1535,7 +1254,7 @@ mod mewnala { /// - `CORNERS` — `dx`, `dy` and `d_width`, `d_height` are opposite corners. #[pyfunction] #[pyo3(pass_module)] - fn image_mode(module: &Bound<'_, PyModule>, mode: u8) -> PyResult<()> { + fn image_mode(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).image_mode(mode) } @@ -1720,7 +1439,7 @@ mod mewnala { } #[pyfunction] - #[pyo3(pass_module, signature = (cx, cy, w, h, start, stop, mode=0))] + #[pyo3(pass_module, signature = (cx, cy, w, h, start, stop, mode=processing::prelude::constants::OPEN))] fn arc( module: &Bound<'_, PyModule>, cx: f32, @@ -1729,7 +1448,7 @@ mod mewnala { h: f32, start: f32, stop: f32, - mode: u8, + mode: &str, ) -> PyResult<()> { graphics!(module).arc(cx, cy, w, h, start, stop, mode) } @@ -1767,8 +1486,8 @@ mod mewnala { } #[pyfunction] - #[pyo3(pass_module, signature = (kind=0))] - fn begin_shape(module: &Bound<'_, PyModule>, kind: u8) -> PyResult<()> { + #[pyo3(pass_module, signature = (kind=processing::prelude::constants::POLYGON))] + fn begin_shape(module: &Bound<'_, PyModule>, kind: &str) -> PyResult<()> { graphics!(module).begin_shape(kind) } @@ -1854,13 +1573,13 @@ mod mewnala { #[pyfunction] #[pyo3(pass_module)] - fn rect_mode(module: &Bound<'_, PyModule>, mode: u8) -> PyResult<()> { + 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: u8) -> PyResult<()> { + fn ellipse_mode(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { graphics!(module).ellipse_mode(mode) } diff --git a/crates/processing_render/src/color.rs b/crates/processing_render/src/color.rs index 932fc8d..4883c6d 100644 --- a/crates/processing_render/src/color.rs +++ b/crates/processing_render/src/color.rs @@ -33,6 +33,23 @@ impl ColorSpace { } } + pub fn parse(s: &str) -> Option { + use processing_core::constants as consts; + match () { + _ if s.eq_ignore_ascii_case(consts::SRGB) => Some(Self::Srgb), + _ if s.eq_ignore_ascii_case(consts::LINEAR) => Some(Self::Linear), + _ if s.eq_ignore_ascii_case(consts::HSL) => Some(Self::Hsl), + _ if s.eq_ignore_ascii_case(consts::HSV) => Some(Self::Hsv), + _ if s.eq_ignore_ascii_case(consts::HWB) => Some(Self::Hwb), + _ if s.eq_ignore_ascii_case(consts::OKLAB) => Some(Self::Oklab), + _ if s.eq_ignore_ascii_case(consts::OKLCH) => Some(Self::Oklch), + _ if s.eq_ignore_ascii_case(consts::LAB) => Some(Self::Lab), + _ if s.eq_ignore_ascii_case(consts::LCH) => Some(Self::Lch), + _ if s.eq_ignore_ascii_case(consts::XYZ) => Some(Self::Xyz), + _ => None, + } + } + pub fn default_maxes(&self) -> [f32; 4] { match self { Self::Srgb | Self::Linear | Self::Oklab | Self::Xyz => [1.0, 1.0, 1.0, 1.0], diff --git a/crates/processing_render/src/geometry/mod.rs b/crates/processing_render/src/geometry/mod.rs index 1424f22..041193c 100644 --- a/crates/processing_render/src/geometry/mod.rs +++ b/crates/processing_render/src/geometry/mod.rs @@ -59,6 +59,19 @@ impl Topology { _ => None, } } + + /// Accepts the shape names (`triangles`, `lines`, ...), not the variant names. + pub fn parse(s: &str) -> Option { + use processing_core::constants as consts; + match () { + _ if s.eq_ignore_ascii_case(consts::POINTS) => Some(Self::PointList), + _ if s.eq_ignore_ascii_case(consts::LINES) => Some(Self::LineList), + _ if s.eq_ignore_ascii_case(consts::LINE_STRIP) => Some(Self::LineStrip), + _ if s.eq_ignore_ascii_case(consts::TRIANGLES) => Some(Self::TriangleList), + _ if s.eq_ignore_ascii_case(consts::TRIANGLE_STRIP) => Some(Self::TriangleStrip), + _ => None, + } + } } #[derive(Component)] diff --git a/crates/processing_render/src/render/command.rs b/crates/processing_render/src/render/command.rs index 1d8f308..6fecfac 100644 --- a/crates/processing_render/src/render/command.rs +++ b/crates/processing_render/src/render/command.rs @@ -1,5 +1,6 @@ use bevy::prelude::*; use bevy::render::render_resource::{BlendComponent, BlendFactor, BlendOperation, BlendState}; +use processing_core::constants as consts; use processing_core::error::{self, ProcessingError}; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -104,6 +105,17 @@ impl From for StrokeCapMode { } } +impl StrokeCapMode { + pub fn parse(s: &str) -> Option { + match () { + _ if s.eq_ignore_ascii_case(consts::ROUND) => Some(Self::Round), + _ if s.eq_ignore_ascii_case(consts::SQUARE) => Some(Self::Square), + _ if s.eq_ignore_ascii_case(consts::PROJECT) => Some(Self::Project), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[repr(u8)] pub enum StrokeJoinMode { @@ -124,6 +136,17 @@ impl From for StrokeJoinMode { } } +impl StrokeJoinMode { + pub fn parse(s: &str) -> Option { + match () { + _ if s.eq_ignore_ascii_case(consts::ROUND) => Some(Self::Round), + _ if s.eq_ignore_ascii_case(consts::MITER) => Some(Self::Miter), + _ if s.eq_ignore_ascii_case(consts::BEVEL) => Some(Self::Bevel), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[repr(u8)] pub enum ArcMode { @@ -144,6 +167,17 @@ impl From for ArcMode { } } +impl ArcMode { + pub fn parse(s: &str) -> Option { + match () { + _ if s.eq_ignore_ascii_case(consts::OPEN) => Some(Self::Open), + _ if s.eq_ignore_ascii_case(consts::CHORD) => Some(Self::Chord), + _ if s.eq_ignore_ascii_case(consts::PIE) => Some(Self::Pie), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[repr(u8)] pub enum ShapeMode { @@ -166,6 +200,18 @@ impl From for ShapeMode { } } +impl ShapeMode { + pub fn parse(s: &str) -> Option { + match () { + _ if s.eq_ignore_ascii_case(consts::CORNER) => Some(Self::Corner), + _ if s.eq_ignore_ascii_case(consts::CORNERS) => Some(Self::Corners), + _ if s.eq_ignore_ascii_case(consts::CENTER) => Some(Self::Center), + _ if s.eq_ignore_ascii_case(consts::RADIUS) => Some(Self::Radius), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[repr(u8)] pub enum ShapeKind { @@ -196,6 +242,22 @@ impl From for ShapeKind { } } +impl ShapeKind { + pub fn parse(s: &str) -> Option { + match () { + _ if s.eq_ignore_ascii_case(consts::POLYGON) => Some(Self::Polygon), + _ if s.eq_ignore_ascii_case(consts::POINTS) => Some(Self::Points), + _ if s.eq_ignore_ascii_case(consts::LINES) => Some(Self::Lines), + _ if s.eq_ignore_ascii_case(consts::TRIANGLES) => Some(Self::Triangles), + _ if s.eq_ignore_ascii_case(consts::TRIANGLE_FAN) => Some(Self::TriangleFan), + _ if s.eq_ignore_ascii_case(consts::TRIANGLE_STRIP) => Some(Self::TriangleStrip), + _ if s.eq_ignore_ascii_case(consts::QUADS) => Some(Self::Quads), + _ if s.eq_ignore_ascii_case(consts::QUAD_STRIP) => Some(Self::QuadStrip), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] #[repr(u8)] pub enum BlendMode { diff --git a/src/prelude.rs b/src/prelude.rs index a1809b9..4e72ec9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,7 @@ pub use bevy::input::keyboard::KeyCode; pub use bevy::input::mouse::MouseButton; pub use bevy::prelude::default; pub use bevy::render::render_resource::TextureFormat; -pub use processing_core::{config::*, error}; +pub use processing_core::{config::*, constants, error}; pub use processing_input::*; #[cfg(not(target_arch = "wasm32"))] pub use processing_midi::{