use funct::{Funct, Value};
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
#[derive(Default)]
struct Surface {
uniforms: BTreeMap<String, String>,
paints: Vec<String>,
events: Vec<String>,
}
fn main() {
let mut vm = Funct::new();
vm.set_module_root(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/widgets"));
let surface: Arc<Mutex<Surface>> = Arc::default();
let animating = Arc::new(AtomicBool::new(false));
let s = surface.clone();
vm.register2("uniform_set", move |name: String, value: Value| {
s.lock()
.unwrap()
.uniforms
.insert(name, format!("{}", value));
});
let s = surface.clone();
vm.register5(
"mask_paint",
move |name: String, x: f64, y: f64, radius: f64, value: f64| {
s.lock().unwrap().paints.push(format!(
"{name}: brush @ ({x:.0},{y:.0}) r={radius:.0} v={value:.2}"
));
},
);
vm.register3("oklch", |l: f64, c: f64, h: f64| {
format!("oklch({l:.2} {c:.2} {h:.0})")
});
let s = surface.clone();
vm.register2("emit", move |kind: String, payload: Value| {
s.lock().unwrap().events.push(format!("{kind} {payload}"));
});
vm.register1("host_log", |msg: String| println!(" [script] {msg}"));
let an = animating.clone();
vm.register1("set_animating", move |on: bool| {
an.store(on, Ordering::SeqCst)
});
vm.set_global("canvas_w", Value::Float(800.0));
vm.set_global("canvas_h", Value::Float(480.0));
let state = vm.make_atom(Value::Unit);
vm.set_global("state", state);
let src = include_str!("widgets/glow.ft");
println!("== load glow.ft ==");
vm.eval(src).expect("glow.ft compiles + runs top-level");
vm.call("on_init", vec![]).expect("on_init");
println!(" host: animating = {}\n", animating.load(Ordering::SeqCst));
println!("== run 5 frames ==");
let dt = Value::Float(1.0 / 60.0);
for _ in 0..5 {
vm.call("tick", vec![dt.clone()]).expect("tick");
}
println!(
" host: frame_count = {}",
vm.call("frame_count", vec![]).unwrap()
);
report(&surface, "after 5 frames");
println!("\n== hot reload (brighter pulse, bigger brush) ==");
let patched = src
.replace("48.0", "96.0")
.replace("0.5 + 0.5 * sin", "0.6 + 0.4 * sin");
vm.eval(&patched).expect("patched glow.ft re-evals");
surface.lock().unwrap().paints.clear();
for _ in 0..3 {
vm.call("tick", vec![dt.clone()])
.expect("tick after reload");
}
println!(
" host: frame_count = {} (kept counting across the reload)",
vm.call("frame_count", vec![]).unwrap()
);
report(&surface, "after reload (state persisted)");
}
fn report(surface: &Arc<Mutex<Surface>>, when: &str) {
let s = surface.lock().unwrap();
println!("\n-- host surface {when} --");
println!(" uniforms:");
for (k, v) in &s.uniforms {
println!(" {k} = {v}");
}
println!(" events: {:?}", s.events);
println!(" mask paints ({}):", s.paints.len());
for p in s.paints.iter().take(3) {
println!(" {p}");
}
if s.paints.len() > 3 {
println!(" … {} more", s.paints.len() - 3);
}
}