Skip to content

Commit 5cc5656

Browse files
authored
Merge pull request #21 from mgth/feat/mpv-overlay-controls
feat(overlay): expose per-control toggles over the FFI for mpv keybinds
2 parents 0efd37f + e0f72ca commit 5cc5656

3 files changed

Lines changed: 247 additions & 1 deletion

File tree

omniphony-renderer/orender_engine/src/overlay.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,96 @@ pub fn set_trail_config(enabled: bool, ttl_ms: u32, diffuse: bool, teleport_thre
346346
save_prefs();
347347
}
348348

349+
// ── toggles (host keybinds: flip current state, return the new one) ──────────
350+
//
351+
// These exist so the mpv shim can bind a key to "flip" a control and show the
352+
// resulting state in the OSD, without keeping its own mirror that could drift
353+
// from Studio's OSC pushes. Each flips the *current* value atomically and
354+
// returns the new one; persistence mirrors the matching `set_*` (enable / labels
355+
// / trails persist; objects / heatmap / bands / colormap are transient, owned by
356+
// Studio and re-pushed on connect).
357+
358+
/// Flip the master enable and return the new state. Persisted.
359+
pub fn toggle_enabled() -> bool {
360+
let o = overlay();
361+
let new = !o.enabled.load(Ordering::Relaxed);
362+
o.enabled.store(new, Ordering::Relaxed);
363+
save_prefs();
364+
new
365+
}
366+
367+
/// Flip object-label visibility and return the new state. Persisted.
368+
pub fn toggle_labels() -> bool {
369+
let new = {
370+
let Ok(mut s) = overlay().state.lock() else {
371+
return false;
372+
};
373+
s.labels_enabled = !s.labels_enabled;
374+
s.labels_enabled
375+
};
376+
save_prefs();
377+
new
378+
}
379+
380+
/// Flip object visibility (markers + labels + trails + depth lines) and return
381+
/// the new state. Transient (display-only), like [`set_objects_visible`].
382+
pub fn toggle_objects() -> bool {
383+
let Ok(mut s) = overlay().state.lock() else {
384+
return false;
385+
};
386+
s.objects_visible = !s.objects_visible;
387+
s.objects_visible
388+
}
389+
390+
/// Flip whether the trails are drawn and return the new state. Clears the trail
391+
/// buffers when disabling (so they don't reappear on re-enable). Persisted, like
392+
/// [`set_trail_config`].
393+
pub fn toggle_trails() -> bool {
394+
let new = {
395+
let Ok(mut s) = overlay().state.lock() else {
396+
return false;
397+
};
398+
s.cfg.enabled = !s.cfg.enabled;
399+
if !s.cfg.enabled {
400+
s.trails.clear();
401+
}
402+
s.cfg.enabled
403+
};
404+
save_prefs();
405+
new
406+
}
407+
408+
/// Flip the energy heatmap and return the new state. Transient, like
409+
/// [`set_heatmap_enabled`].
410+
pub fn toggle_heatmap() -> bool {
411+
let Ok(mut s) = overlay().state.lock() else {
412+
return false;
413+
};
414+
s.heatmap_enabled = !s.heatmap_enabled;
415+
s.heatmap_enabled
416+
}
417+
418+
/// Advance the heatmap colour gradient to the next index (wraps 0..=4, mirroring
419+
/// `OBJECT_ENERGY_COLORMAPS`) and return the new index. Transient.
420+
pub fn cycle_heatmap_colormap() -> usize {
421+
let Ok(mut s) = overlay().state.lock() else {
422+
return 0;
423+
};
424+
s.heatmap_colormap = (s.heatmap_colormap + 1) % 5;
425+
s.heatmap_colormap as usize
426+
}
427+
428+
/// Step the heatmap depth-plane count by `delta` (clamped to 1..=12) and return
429+
/// the new count. Transient, like [`set_heatmap_bands`].
430+
pub fn adjust_heatmap_bands(delta: i32) -> usize {
431+
let Ok(mut s) = overlay().state.lock() else {
432+
return FIELD_BANDS;
433+
};
434+
let next = (s.heatmap_bands as i32 + delta).clamp(1, 12) as usize;
435+
s.heatmap_bands = next;
436+
next
437+
}
438+
349439
// ── persistence (orender-owned, real-time, separate from the savable config) ─
350440

351441
/// Point the overlay at its prefs file and load it. Called once by the host at
@@ -1259,6 +1349,47 @@ mod tests {
12591349
assert!(build_ass(0, 0).is_empty());
12601350
}
12611351

1352+
#[test]
1353+
fn toggles_flip_and_return_new_state() {
1354+
let _g = guard();
1355+
// guard() leaves enable/labels/objects/heatmap on; each toggle flips and
1356+
// reports the resulting value.
1357+
assert!(!toggle_enabled());
1358+
assert!(toggle_enabled());
1359+
assert!(!toggle_labels());
1360+
assert!(toggle_labels());
1361+
assert!(!toggle_objects());
1362+
assert!(toggle_objects());
1363+
assert!(!toggle_heatmap());
1364+
assert!(toggle_heatmap());
1365+
}
1366+
1367+
#[test]
1368+
fn toggle_trails_clears_buffers_when_disabling() {
1369+
let _g = guard();
1370+
// Build up a trail, then disable: the buffer must be dropped so it can't
1371+
// reappear on re-enable.
1372+
update_positions(vec![(0, 0.0, 0.0, 0.5, String::new())]);
1373+
let now = now_secs();
1374+
trail_append(&mut overlay().state.lock().unwrap(), 0, 0.0, 0.0, 0.5, now);
1375+
assert!(!overlay().state.lock().unwrap().trails.is_empty());
1376+
assert!(!toggle_trails(), "trails start enabled (default) → off");
1377+
assert!(overlay().state.lock().unwrap().trails.is_empty());
1378+
assert!(toggle_trails(), "back on");
1379+
}
1380+
1381+
#[test]
1382+
fn heatmap_bands_clamp_and_colormap_wraps() {
1383+
let _g = guard();
1384+
set_heatmap_bands(3);
1385+
assert_eq!(adjust_heatmap_bands(2), 5);
1386+
assert_eq!(adjust_heatmap_bands(-10), 1, "clamps at 1");
1387+
assert_eq!(adjust_heatmap_bands(100), 12, "clamps at 12");
1388+
set_heatmap_colormap(3);
1389+
assert_eq!(cycle_heatmap_colormap(), 4);
1390+
assert_eq!(cycle_heatmap_colormap(), 0, "wraps 4 → 0");
1391+
}
1392+
12621393
// The energy heatmap is now a separate BGRA bitmap (drawn under the ASS via
12631394
// mpv's overlay-add), not part of the ASS string.
12641395
#[test]

omniphony-renderer/orender_ffi/include/orender.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,45 @@ uintptr_t orender_overlay_ass(uint32_t res_x, uint32_t res_y, uint8_t *out, uint
159159
*/
160160
void orender_overlay_set_enabled(int enabled);
161161

162+
/**
163+
* Flip the master enable and return the new state (1 = on, 0 = off).
164+
*/
165+
int orender_overlay_toggle(void);
166+
167+
/**
168+
* Flip object-label visibility and return the new state (1 = on, 0 = off).
169+
*/
170+
int orender_overlay_toggle_labels(void);
171+
172+
/**
173+
* Flip object visibility (markers + labels + trails + depth lines) and return
174+
* the new state (1 = on, 0 = off).
175+
*/
176+
int orender_overlay_toggle_objects(void);
177+
178+
/**
179+
* Flip whether motion trails are drawn and return the new state (1 = on,
180+
* 0 = off). Clears the trail buffers when disabling.
181+
*/
182+
int orender_overlay_toggle_trails(void);
183+
184+
/**
185+
* Flip the object energy heatmap and return the new state (1 = on, 0 = off).
186+
*/
187+
int orender_overlay_toggle_heatmap(void);
188+
189+
/**
190+
* Advance the heatmap colour gradient to the next index (wraps 0..=4) and return
191+
* the new index.
192+
*/
193+
uint32_t orender_overlay_cycle_heatmap_colormap(void);
194+
195+
/**
196+
* Step the heatmap depth-plane count by `delta` (clamped to 1..=12) and return
197+
* the new count.
198+
*/
199+
uint32_t orender_overlay_adjust_heatmap_bands(int32_t delta);
200+
162201
/**
163202
* Render the object energy heatmap as a single flattened BGRA bitmap
164203
* (premultiplied alpha) for mpv's `overlay-add`, drawn *under* the ASS overlay.

omniphony-renderer/orender_ffi/src/lib.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ pub struct OrenderConfig {
145145
const VERSION_MAJOR: u32 = 0;
146146
// 2: added orender_overlay_ass / orender_overlay_set_enabled (in-process overlay).
147147
// 3: added orender_overlay_heatmap_bgra (BGRA energy-field bitmap for overlay-add).
148-
const VERSION_MINOR: u32 = 3;
148+
// 4: added overlay toggles (labels/objects/trails/heatmap) + heatmap band/colormap cycling.
149+
const VERSION_MINOR: u32 = 4;
149150

150151
unsafe fn opt_str<'a>(p: *const c_char) -> Option<&'a str> {
151152
if p.is_null() {
@@ -484,6 +485,81 @@ pub extern "C" fn orender_overlay_set_enabled(enabled: c_int) {
484485
}));
485486
}
486487

488+
// ── overlay toggles (host keybinds) ──────────────────────────────────────────
489+
//
490+
// Each flips the matching control inside the renderer and returns the *new*
491+
// state (1 = on, 0 = off; the heatmap band/colormap variants return the new
492+
// numeric value). Returning the result lets the mpv shim show it in the OSD
493+
// without keeping a mirror that could drift from Studio's OSC pushes. On a panic
494+
// the catch returns a safe default (0).
495+
496+
/// Flip the master enable and return the new state (1 = on, 0 = off).
497+
#[no_mangle]
498+
pub extern "C" fn orender_overlay_toggle() -> c_int {
499+
catch_unwind(AssertUnwindSafe(|| {
500+
orender_engine::overlay::toggle_enabled() as c_int
501+
}))
502+
.unwrap_or(0)
503+
}
504+
505+
/// Flip object-label visibility and return the new state (1 = on, 0 = off).
506+
#[no_mangle]
507+
pub extern "C" fn orender_overlay_toggle_labels() -> c_int {
508+
catch_unwind(AssertUnwindSafe(|| {
509+
orender_engine::overlay::toggle_labels() as c_int
510+
}))
511+
.unwrap_or(0)
512+
}
513+
514+
/// Flip object visibility (markers + labels + trails + depth lines) and return
515+
/// the new state (1 = on, 0 = off).
516+
#[no_mangle]
517+
pub extern "C" fn orender_overlay_toggle_objects() -> c_int {
518+
catch_unwind(AssertUnwindSafe(|| {
519+
orender_engine::overlay::toggle_objects() as c_int
520+
}))
521+
.unwrap_or(0)
522+
}
523+
524+
/// Flip whether motion trails are drawn and return the new state (1 = on,
525+
/// 0 = off). Clears the trail buffers when disabling.
526+
#[no_mangle]
527+
pub extern "C" fn orender_overlay_toggle_trails() -> c_int {
528+
catch_unwind(AssertUnwindSafe(|| {
529+
orender_engine::overlay::toggle_trails() as c_int
530+
}))
531+
.unwrap_or(0)
532+
}
533+
534+
/// Flip the object energy heatmap and return the new state (1 = on, 0 = off).
535+
#[no_mangle]
536+
pub extern "C" fn orender_overlay_toggle_heatmap() -> c_int {
537+
catch_unwind(AssertUnwindSafe(|| {
538+
orender_engine::overlay::toggle_heatmap() as c_int
539+
}))
540+
.unwrap_or(0)
541+
}
542+
543+
/// Advance the heatmap colour gradient to the next index (wraps 0..=4) and return
544+
/// the new index.
545+
#[no_mangle]
546+
pub extern "C" fn orender_overlay_cycle_heatmap_colormap() -> u32 {
547+
catch_unwind(AssertUnwindSafe(|| {
548+
orender_engine::overlay::cycle_heatmap_colormap() as u32
549+
}))
550+
.unwrap_or(0)
551+
}
552+
553+
/// Step the heatmap depth-plane count by `delta` (clamped to 1..=12) and return
554+
/// the new count.
555+
#[no_mangle]
556+
pub extern "C" fn orender_overlay_adjust_heatmap_bands(delta: i32) -> u32 {
557+
catch_unwind(AssertUnwindSafe(|| {
558+
orender_engine::overlay::adjust_heatmap_bands(delta) as u32
559+
}))
560+
.unwrap_or(0)
561+
}
562+
487563
/// Render the object energy heatmap as a single flattened BGRA bitmap
488564
/// (premultiplied alpha) for mpv's `overlay-add`, drawn *under* the ASS overlay.
489565
///

0 commit comments

Comments
 (0)