diff --git a/Cargo.lock b/Cargo.lock index eb171a6..7f01ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3051,6 +3051,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "inflections" version = "1.1.1" @@ -3433,6 +3442,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.32.0" @@ -4369,6 +4387,15 @@ dependencies = [ "processing", ] +[[package]] +name = "processing_pyo3" +version = "0.1.0" +dependencies = [ + "glfw", + "processing", + "pyo3", +] + [[package]] name = "processing_render" version = "0.1.0" @@ -4414,6 +4441,67 @@ dependencies = [ "num-traits", ] +[[package]] +name = "pyo3" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "qoi" version = "0.4.1" @@ -5072,6 +5160,12 @@ dependencies = [ "slotmap", ] +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + [[package]] name = "tempfile" version = "3.23.0" @@ -5401,6 +5495,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 5557513..045e1c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ bevy = { git = "https://github.com/bevyengine/bevy", branch = "main", no-default "bevy_color", ] } processing = { path = "." } +processing_pyo3 = { path = "crates/processing_pyo3" } processing_render = { path = "crates/processing_render" } [dependencies] diff --git a/crates/processing_pyo3/.gitignore b/crates/processing_pyo3/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/crates/processing_pyo3/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/crates/processing_pyo3/Cargo.toml b/crates/processing_pyo3/Cargo.toml new file mode 100644 index 0000000..9f732d4 --- /dev/null +++ b/crates/processing_pyo3/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "processing_pyo3" +version = "0.1.0" +edition = "2024" + +[lints] +workspace = true + +[lib] +name = "pycessing" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.27.0" +processing = { workspace = true } +glfw = "0.60.0" + +[target.'cfg(target_os = "linux")'.dependencies] +glfw = { version = "0.60.0", features = ["wayland"] } diff --git a/crates/processing_pyo3/README.md b/crates/processing_pyo3/README.md new file mode 100644 index 0000000..f94d27d --- /dev/null +++ b/crates/processing_pyo3/README.md @@ -0,0 +1,19 @@ +# Pycessing + +Prototype for python bindings to libprocessing + +## To Get Started + +### Install venv and maturin +Follow these [installation instructions](https://pyo3.rs/v0.27.2/getting-started.html) + +### Running code +``` +$ maturin develop +# +# ... +# +$ python +>>> import pycessing +>>> pycessing.size(500, 500) +``` diff --git a/crates/processing_pyo3/pyproject.toml b/crates/processing_pyo3/pyproject.toml new file mode 100644 index 0000000..0a5b09c --- /dev/null +++ b/crates/processing_pyo3/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["maturin>=1.10,<2.0"] +build-backend = "maturin" + +[project] +name = "pycessing" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] + +[tool.maturin] +manifest-path = "Cargo.toml" diff --git a/crates/processing_pyo3/src/glfw.rs b/crates/processing_pyo3/src/glfw.rs new file mode 100644 index 0000000..ba56a55 --- /dev/null +++ b/crates/processing_pyo3/src/glfw.rs @@ -0,0 +1,69 @@ +/// Minimal GLFW helper for Processing examples +use glfw::{Glfw, GlfwReceiver, PWindow, WindowEvent, WindowMode}; +use processing::prelude::error::Result; + +pub struct GlfwContext { + glfw: Glfw, + window: PWindow, + events: GlfwReceiver<(f64, WindowEvent)>, +} + +impl GlfwContext { + pub fn new(width: u32, height: u32) -> Result { + let mut glfw = glfw::init(glfw::fail_on_errors).unwrap(); + + glfw.window_hint(glfw::WindowHint::ClientApi(glfw::ClientApiHint::NoApi)); + glfw.window_hint(glfw::WindowHint::Visible(false)); + + let (mut window, events) = glfw + .create_window(width, height, "Processing", WindowMode::Windowed) + .unwrap(); + + window.set_all_polling(true); + window.show(); + + Ok(Self { + glfw, + window, + events, + }) + } + + #[cfg(target_os = "macos")] + pub fn get_window(&self) -> u64 { + self.window.get_cocoa_window() as u64 + } + + #[cfg(target_os = "windows")] + pub fn get_window(&self) -> u64 { + self.window.get_win32_window() as u64 + } + + #[cfg(target_os = "linux")] + pub fn get_window(&self) -> u64 { + self.window.get_wayland_window() as u64 + } + + #[cfg(not(target_os = "linux"))] + pub fn get_display(&self) -> u64 { + 0 + } + + #[cfg(target_os = "linux")] + pub fn get_display(&self) -> u64 { + self.glfw.get_wayland_display() as u64 + } + + pub fn poll_events(&mut self) -> bool { + self.glfw.poll_events(); + + for (_, event) in glfw::flush_messages(&self.events) { + match event { + WindowEvent::Close => return false, + _ => {} + } + } + + !self.window.should_close() + } +} diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs new file mode 100644 index 0000000..6b253cd --- /dev/null +++ b/crates/processing_pyo3/src/lib.rs @@ -0,0 +1,40 @@ +mod glfw; +use pyo3::prelude::*; + +#[pymodule] +mod pycessing { + use crate::glfw::GlfwContext; + use processing::prelude::*; + use pyo3::prelude::*; + + /// create surface + #[pyfunction] + fn size(width: u32, height: u32) -> PyResult { + let mut glfw_ctx = GlfwContext::new(400, 400).unwrap(); + init().unwrap(); + + let window_handle = glfw_ctx.get_window(); + let display_handle = glfw_ctx.get_display(); + let surface = surface_create(window_handle, display_handle, width, height, 1.0).unwrap(); + + while glfw_ctx.poll_events() { + begin_draw(surface).unwrap(); + + record_command( + surface, + DrawCommand::Rect { + x: 10.0, + y: 10.0, + w: 100.0, + h: 100.0, + radii: [0.0, 0.0, 0.0, 0.0], + }, + ) + .unwrap(); + + end_draw(surface).unwrap(); + } + + Ok("OK".to_string()) + } +}