Skip to main content

ui_panel/
ui_panel.rs

1//! A small UI panel, a toggle and a slider, whose every frame is computed by
2//! a circuit compiled from one ordinary function: pointer handling, the
3//! toggle's arming and flipping, slider dragging with clamping and snapping,
4//! and the toggle's easing animation. Each frame's state is checked against
5//! the function, then printed as CSV, one line per frame.
6//!
7//! cargo run --release --example ui_panel > frames.csv
8
9use handwired_rs::TensorExecutor;
10use handwired_rs::dsl::Params;
11use handwired_rs::handwired;
12
13// The panel is 480 × 200 px. The toggle's track spans x 40..144, y 40..96, and
14// the slider's track runs from x 40 to 440 at y 150.
15const TOGGLE: (i16, i16, i16, i16) = (40, 40, 144, 96);
16const SLIDER_LEFT: i16 = 40;
17
18/// One frame: the pointer's position and whether its button went down or up
19/// this frame, applied to the panel's state. Returns the next state:
20/// (on, armed, dragging, level in 0..=100, the toggle thumb's position 0..1).
21#[handwired]
22#[allow(clippy::too_many_arguments)]
23fn ui_frame(
24    on: bool,
25    armed: bool,
26    dragging: bool,
27    #[range(0, 100)] level: u8,
28    #[range(0, 1)] thumb: f32,
29    #[range(0, 480)] x: i16,
30    #[range(0, 200)] y: i16,
31    pressed: bool,
32    released: bool,
33) -> (bool, bool, bool, u8, f32) {
34    let mut on = on;
35    let mut armed = armed;
36    let mut dragging = dragging;
37    let mut level = level;
38    let mut thumb = thumb;
39    let on_toggle = x >= 40 && x < 144 && y >= 40 && y < 96;
40    let on_slider = x >= 28 && x < 452 && y >= 134 && y < 166;
41
42    if pressed {
43        armed = on_toggle;
44        if on_slider {
45            dragging = true;
46        }
47    }
48    if dragging {
49        // 400 px of track for 0..=100, snapped to steps of 5.
50        let raw = ((x - 40) / 4).clamp(0, 100);
51        level = ((raw + 2) / 5 * 5) as u8;
52    }
53    if released {
54        if armed && on_toggle {
55            on = !on;
56        }
57        armed = false;
58        dragging = false;
59    }
60
61    // The thumb eases a third of the way to its side each frame, and snaps when close.
62    let target: f32 = if on { 1.0 } else { 0.0 };
63    thumb = (thumb + (target - thumb) * 0.3).clamp(0.0, 1.0);
64    if (target - thumb).abs() < 0.01 {
65        thumb = target;
66    }
67    (on, armed, dragging, level, thumb)
68}
69
70/// A scripted pointer: straight moves at a steady speed, presses and releases.
71fn script() -> Vec<(i16, i16, bool, bool, bool)> {
72    let mut frames = Vec::new();
73    let mut at = (300i16, 20i16);
74    let mut held = false;
75    let move_to = |frames: &mut Vec<_>, at: &mut (i16, i16), held: bool, target: (i16, i16), steps: i16| {
76        let start = *at;
77        for step in 1..=steps {
78            let x = start.0 + (target.0 - start.0) * step / steps;
79            let y = start.1 + (target.1 - start.1) * step / steps;
80            frames.push((x, y, false, false, held));
81        }
82        *at = target;
83    };
84    let pause = |frames: &mut Vec<_>, at: (i16, i16), held: bool, count: usize| {
85        for _ in 0..count {
86            frames.push((at.0, at.1, false, false, held));
87        }
88    };
89    let press = |frames: &mut Vec<_>, at: (i16, i16), held: &mut bool| {
90        *held = true;
91        frames.push((at.0, at.1, true, false, true));
92    };
93    let release = |frames: &mut Vec<_>, at: (i16, i16), held: &mut bool| {
94        *held = false;
95        frames.push((at.0, at.1, false, true, false));
96    };
97    pause(&mut frames, at, held, 8);
98    // Click the toggle on.
99    move_to(&mut frames, &mut at, held, (92, 68), 14);
100    press(&mut frames, at, &mut held);
101    pause(&mut frames, at, held, 4);
102    release(&mut frames, at, &mut held);
103    pause(&mut frames, at, held, 14);
104    // Drag the slider from 0 up to 75.
105    move_to(&mut frames, &mut at, held, (SLIDER_LEFT, 150), 12);
106    press(&mut frames, at, &mut held);
107    move_to(&mut frames, &mut at, held, (346, 152), 26);
108    pause(&mut frames, at, held, 4);
109    release(&mut frames, at, &mut held);
110    pause(&mut frames, at, held, 8);
111    // Press the toggle, slide off it, and release: armed, then nothing happens.
112    move_to(&mut frames, &mut at, held, (100, 70), 14);
113    press(&mut frames, at, &mut held);
114    pause(&mut frames, at, held, 4);
115    move_to(&mut frames, &mut at, held, (220, 80), 8);
116    release(&mut frames, at, &mut held);
117    pause(&mut frames, at, held, 8);
118    // Drag the slider back to 20, then click the toggle off.
119    move_to(&mut frames, &mut at, held, (340, 150), 12);
120    press(&mut frames, at, &mut held);
121    move_to(&mut frames, &mut at, held, (120, 148), 20);
122    release(&mut frames, at, &mut held);
123    pause(&mut frames, at, held, 6);
124    move_to(&mut frames, &mut at, held, (70, 66), 14);
125    press(&mut frames, at, &mut held);
126    pause(&mut frames, at, held, 3);
127    release(&mut frames, at, &mut held);
128    pause(&mut frames, at, held, 20);
129    frames
130}
131
132fn main() {
133    let compiled = ui_frame_circuit();
134    let report = compiled.verify(20_000).unwrap();
135    eprintln!(
136        "ui_frame: {} live operations in {} layers; verified on {} sampled inputs",
137        compiled.program.live_operation_count(),
138        compiled.program.layers.len(),
139        report.checked
140    );
141    let _ = (TOGGLE, SLIDER_LEFT);
142    let mut executor = TensorExecutor::new(&compiled.program);
143    let mut source = Vec::new();
144    let mut state = (false, false, false, 0u8, 0.0f32);
145    println!("frame,x,y,held,pressed,released,on,armed,dragging,level,thumb");
146    for (frame, (x, y, pressed, released, held)) in script().into_iter().enumerate() {
147        let args = (state.0, state.1, state.2, state.3, state.4, x, y, pressed, released);
148        source.clear();
149        args.encode(&mut source);
150        let outputs = executor.run(&source);
151        let next = (outputs[0] == 1.0, outputs[1] == 1.0, outputs[2] == 1.0, outputs[3] as u8, outputs[4]);
152        let expected = ui_frame(state.0, state.1, state.2, state.3, state.4, x, y, pressed, released);
153        assert_eq!(next, expected, "frame {frame}: the circuit disagrees with ui_frame()");
154        state = next;
155        println!(
156            "{frame},{x},{y},{},{},{},{},{},{},{},{}",
157            held as u8, pressed as u8, released as u8, state.0 as u8, state.1 as u8, state.2 as u8, state.3, state.4
158        );
159    }
160}