Skip to content

Commit 467a53d

Browse files
committed
.
1 parent 6f20708 commit 467a53d

33 files changed

Lines changed: 1925 additions & 217 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/processing_core/src/constants.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ pub const CLOSE: bool = true;
2727
pub const LEFT: &str = "left";
2828
pub const RIGHT: &str = "right";
2929

30+
pub const TOP: &str = "top"; // vertical text align
31+
pub const BOTTOM: &str = "bottom";
32+
pub const BASELINE: &str = "baseline";
33+
34+
pub const NORMAL: &str = "normal"; // text style
35+
pub const ITALIC: &str = "italic";
36+
pub const BOLD: &str = "bold";
37+
pub const BOLD_ITALIC: &str = "bold_italic";
38+
39+
pub const WORD: &str = "word"; // text wrap mode
40+
pub const CHAR: &str = "char";
41+
3042
pub const NEAREST: &str = "nearest";
3143
pub const CLAMP: &str = "clamp";
3244
pub const REPEAT: &str = "repeat";

crates/processing_glfw/src/lib.rs

Lines changed: 129 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ use processing_input::{
1717
};
1818
use processing_render::surface::{MonitorWorkarea, WindowControls};
1919

20+
/// A single GLFW instance drives every window (GLFW's event pump is global). The
21+
/// main window is `windows[0]`; `create_window` appends more.
2022
pub struct GlfwContext {
2123
glfw: Glfw,
24+
windows: Vec<ManagedWindow>,
25+
}
26+
27+
/// Per-window state. One `GlfwContext` owns many of these on the shared instance.
28+
struct ManagedWindow {
2229
window: PWindow,
2330
events: GlfwReceiver<(f64, WindowEvent)>,
2431
surface: Option<Entity>,
@@ -58,15 +65,31 @@ impl Default for AppliedWindow {
5865
}
5966

6067
impl GlfwContext {
61-
pub fn new(width: u32, height: u32) -> Result<Self> {
68+
pub fn new(width: u32, height: u32, transparent: bool) -> Result<Self> {
6269
let mut glfw = glfw::init(glfw::fail_on_errors).unwrap();
70+
let main = Self::spawn_window(&mut glfw, width, height, transparent, "Processing");
71+
Ok(Self {
72+
glfw,
73+
windows: vec![main],
74+
})
75+
}
6376

77+
/// Create a GLFW window on the shared instance and return its per-window state.
78+
fn spawn_window(
79+
glfw: &mut Glfw,
80+
width: u32,
81+
height: u32,
82+
transparent: bool,
83+
title: &str,
84+
) -> ManagedWindow {
6485
glfw.window_hint(glfw::WindowHint::ClientApi(glfw::ClientApiHint::NoApi));
6586
glfw.window_hint(glfw::WindowHint::Visible(false));
66-
glfw.window_hint(glfw::WindowHint::TransparentFramebuffer(true));
87+
// Window transparency is an explicit opt-in; an opaque framebuffer is the
88+
// default (a transparent-by-default window is surprising and platform-flaky).
89+
glfw.window_hint(glfw::WindowHint::TransparentFramebuffer(transparent));
6790

6891
let (mut window, events) = glfw
69-
.create_window(width, height, "Processing", WindowMode::Windowed)
92+
.create_window(width, height, title, WindowMode::Windowed)
7093
.unwrap();
7194

7295
window.set_all_polling(true);
@@ -101,14 +124,13 @@ impl GlfwContext {
101124

102125
window.show();
103126

104-
Ok(Self {
105-
glfw,
127+
ManagedWindow {
106128
window,
107129
events,
108130
surface: None,
109131
last_applied: AppliedWindow::default(),
110132
windowed_geometry: None,
111-
})
133+
}
112134
}
113135

114136
fn sync_monitors(&mut self) {
@@ -207,67 +229,112 @@ impl GlfwContext {
207229
});
208230
}
209231

210-
#[cfg(target_os = "macos")]
211-
pub fn create_surface(&mut self, width: u32, height: u32) -> Result<Entity> {
212-
use processing_render::surface_create_macos;
213-
let (scale_factor, _) = self.window.get_content_scale();
214-
let entity = surface_create_macos(
215-
self.window.get_cocoa_window() as u64,
216-
width,
217-
height,
218-
scale_factor,
219-
)?;
220-
self.surface = Some(entity);
221-
Ok(entity)
232+
/// Create the render surface for the main window (index 0).
233+
pub fn create_surface(&mut self, width: u32, height: u32, transparent: bool) -> Result<Entity> {
234+
self.create_surface_for(0, width, height, transparent)
222235
}
223236

224-
#[cfg(target_os = "windows")]
225-
pub fn create_surface(&mut self, width: u32, height: u32) -> Result<Entity> {
226-
use processing_render::surface_create_windows;
227-
let (scale_factor, _) = self.window.get_content_scale();
228-
let entity = surface_create_windows(
229-
self.window.get_win32_window() as u64,
230-
width,
231-
height,
232-
scale_factor,
233-
)?;
234-
self.surface = Some(entity);
235-
Ok(entity)
237+
/// Create an additional window on the shared GLFW instance plus its render
238+
/// surface, and return the new window's surface entity.
239+
pub fn add_window(
240+
&mut self,
241+
width: u32,
242+
height: u32,
243+
transparent: bool,
244+
title: &str,
245+
) -> Result<Entity> {
246+
let mw = Self::spawn_window(&mut self.glfw, width, height, transparent, title);
247+
self.windows.push(mw);
248+
let idx = self.windows.len() - 1;
249+
self.create_surface_for(idx, width, height, transparent)
236250
}
237251

238-
#[cfg(all(target_os = "linux", feature = "wayland"))]
239-
pub fn create_surface(&mut self, width: u32, height: u32) -> Result<Entity> {
240-
use processing_render::surface_create_wayland;
241-
let (scale_factor, _) = self.window.get_content_scale();
242-
let entity = surface_create_wayland(
243-
self.window.get_wayland_window() as u64,
244-
self.glfw.get_wayland_display() as u64,
245-
width,
246-
height,
247-
scale_factor,
248-
)?;
249-
self.surface = Some(entity);
250-
Ok(entity)
252+
/// The surface entity of the main window, if created.
253+
pub fn main_surface(&self) -> Option<Entity> {
254+
self.windows.first().and_then(|w| w.surface)
251255
}
252256

253-
#[cfg(all(target_os = "linux", feature = "x11"))]
254-
pub fn create_surface(&mut self, width: u32, height: u32) -> Result<Entity> {
255-
use processing_render::surface_create_x11;
256-
let (scale_factor, _) = self.window.get_content_scale();
257-
let entity = surface_create_x11(
258-
self.window.get_x11_window() as u64,
259-
self.glfw.get_x11_display() as u64,
260-
width,
261-
height,
262-
scale_factor,
263-
)?;
264-
self.surface = Some(entity);
257+
fn create_surface_for(
258+
&mut self,
259+
idx: usize,
260+
width: u32,
261+
height: u32,
262+
transparent: bool,
263+
) -> Result<Entity> {
264+
let (scale_factor, _) = self.windows[idx].window.get_content_scale();
265+
266+
#[cfg(target_os = "macos")]
267+
let entity = {
268+
use processing_render::surface_create_macos;
269+
let handle = self.windows[idx].window.get_cocoa_window() as u64;
270+
surface_create_macos(handle, width, height, scale_factor, transparent)?
271+
};
272+
#[cfg(target_os = "windows")]
273+
let entity = {
274+
use processing_render::surface_create_windows;
275+
let handle = self.windows[idx].window.get_win32_window() as u64;
276+
surface_create_windows(handle, width, height, scale_factor, transparent)?
277+
};
278+
#[cfg(all(target_os = "linux", feature = "wayland"))]
279+
let entity = {
280+
use processing_render::surface_create_wayland;
281+
let wh = self.windows[idx].window.get_wayland_window() as u64;
282+
let dh = self.glfw.get_wayland_display() as u64;
283+
surface_create_wayland(wh, dh, width, height, scale_factor, transparent)?
284+
};
285+
#[cfg(all(target_os = "linux", feature = "x11", not(feature = "wayland")))]
286+
let entity = {
287+
use processing_render::surface_create_x11;
288+
let wh = self.windows[idx].window.get_x11_window() as u64;
289+
let dh = self.glfw.get_x11_display() as u64;
290+
surface_create_x11(wh, dh, width, height, scale_factor, transparent)?
291+
};
292+
293+
self.windows[idx].surface = Some(entity);
265294
Ok(entity)
266295
}
267296

268297
pub fn poll_events(&mut self) -> bool {
269298
self.glfw.poll_events();
299+
self.sync_monitors();
270300

301+
// GLFW's pump is global; flush + sync each window on the shared instance.
302+
// A closed secondary window is dropped; a closed main window ends the loop.
303+
let GlfwContext { glfw, windows } = self;
304+
let mut main_open = true;
305+
let mut i = 0;
306+
while i < windows.len() {
307+
if windows[i].poll(glfw) {
308+
i += 1;
309+
} else if i == 0 {
310+
main_open = false;
311+
i += 1;
312+
} else {
313+
windows[i].window.hide();
314+
windows.remove(i);
315+
}
316+
}
317+
318+
// Input is accumulated per-window above; commit it once for the frame.
319+
if input_flush().is_err() {
320+
return false;
321+
}
322+
main_open
323+
}
324+
325+
/// Content scale (DPI) of the main window.
326+
pub fn content_scale(&self) -> f32 {
327+
self.windows
328+
.first()
329+
.map(|w| w.window.get_content_scale().0)
330+
.unwrap_or(1.0)
331+
}
332+
}
333+
334+
impl ManagedWindow {
335+
/// Flush this window's events and sync its OS state; returns whether it's
336+
/// still open. Input is committed once per frame by the caller.
337+
fn poll(&mut self, glfw: &mut Glfw) -> bool {
271338
let surface = match self.surface {
272339
Some(s) => s,
273340
None => {
@@ -349,22 +416,18 @@ impl GlfwContext {
349416
processing_render::surface_resize(surface, width as u32, height as u32).unwrap();
350417
}
351418

352-
let Ok(_) = input_flush() else {
353-
return false;
354-
};
355419
self.sync_cursor(surface);
356-
self.sync_monitors();
357-
self.sync_window(surface);
420+
self.sync_window(glfw, surface);
358421

359422
true
360423
}
361424

362-
fn sync_window(&mut self, surface: Entity) {
425+
fn sync_window(&mut self, glfw: &mut Glfw, surface: Entity) {
363426
let Some(desired) = read_desired_window(surface) else {
364427
return;
365428
};
366429

367-
self.apply_window(&desired);
430+
self.apply_window(glfw, &desired);
368431

369432
if desired.iconify {
370433
self.window.iconify();
@@ -412,7 +475,7 @@ impl GlfwContext {
412475
self.last_applied.position
413476
}
414477

415-
fn apply_window(&mut self, desired: &DesiredWindow) {
478+
fn apply_window(&mut self, glfw: &mut Glfw, desired: &DesiredWindow) {
416479
let last = &mut self.last_applied;
417480

418481
if desired.title != last.title {
@@ -462,11 +525,11 @@ impl GlfwContext {
462525
last.opacity = opacity;
463526
}
464527
if desired.fullscreen_on != last.fullscreen_on {
465-
self.apply_fullscreen(desired.fullscreen_on);
528+
self.apply_fullscreen(glfw, desired.fullscreen_on);
466529
}
467530
}
468531

469-
fn apply_fullscreen(&mut self, target: Option<Entity>) {
532+
fn apply_fullscreen(&mut self, glfw: &mut Glfw, target: Option<Entity>) {
470533
match target {
471534
Some(monitor_entity) => {
472535
if self.last_applied.fullscreen_on.is_none() {
@@ -476,7 +539,7 @@ impl GlfwContext {
476539
}
477540
let target_name = monitor_name(monitor_entity);
478541
let window = &mut self.window;
479-
let applied = self.glfw.with_connected_monitors(|_, monitors| {
542+
let applied = glfw.with_connected_monitors(|_, monitors| {
480543
let Some(monitor) = monitors
481544
.iter()
482545
.find(|m| m.get_name() == target_name)
@@ -506,11 +569,6 @@ impl GlfwContext {
506569
}
507570
}
508571

509-
pub fn content_scale(&self) -> f32 {
510-
let (s, _) = self.window.get_content_scale();
511-
s
512-
}
513-
514572
fn sync_cursor(&mut self, surface: Entity) {
515573
use bevy::window::CursorGrabMode;
516574

crates/processing_pyo3/examples/custom_material.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def draw():
1515
camera_look_at(0.0, 0.0, 0.0)
1616
background(12, 12, 18)
1717

18-
use_material(mat)
18+
material(mat)
1919
box(80.0, 80.0, 80.0)
2020

2121
run()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Classic TouchDesigner-style feedback patch.
2+
3+
The TD network is:
4+
5+
source (this frame) ─┐
6+
├─► Composite ─┬─► Out (display)
7+
Feedback ─► Level ───┘ │
8+
▲ (decay) │
9+
└───────────────────────────────┘ feedback taps the composite output
10+
11+
i.e. out(t) = composite( source(t), decay * out(t-1) ).
12+
13+
Here the sketch canvas *is* the composite output. Because it isn't cleared each
14+
frame, it persists and feeds back into itself. So:
15+
16+
- feedback(decay=...) is the Feedback TOP + Level (samples the previous
17+
composite output and fades it, optionally zooming/rotating it);
18+
- the shapes drawn afterwards are this frame's source, composited *over* the
19+
faded feedback;
20+
- the canvas persists, closing the loop.
21+
"""
22+
from mewnala import *
23+
from math import sin, cos
24+
25+
t = 0.0
26+
27+
28+
def setup():
29+
size(800, 600)
30+
background(0, 0, 0) # clear once; draw() never clears, so the canvas accumulates
31+
32+
33+
def draw():
34+
global t
35+
36+
# --- feedback line: previous composite output, decayed + slowly zoomed/rotated ---
37+
feedback(decay=0.94, zoom=1.008, angle=0.006)
38+
39+
# --- this frame's source, composited over the feedback (drawn on top) ---
40+
no_stroke()
41+
42+
x = width / 2 + cos(t) * 230
43+
y = height / 2 + sin(t * 1.3) * 170
44+
fill(0, 220, 255)
45+
circle(x, y, 44)
46+
47+
x2 = width / 2 + cos(t * 0.7 + 2.0) * 150
48+
y2 = height / 2 + sin(t * 1.1) * 120
49+
fill(255, 90, 200)
50+
circle(x2, y2, 28)
51+
52+
t += 0.03
53+
54+
55+
# TODO: this should happen implicitly on module load somehow
56+
run()

0 commit comments

Comments
 (0)