mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Reconcile main 991904888a and preserve the reviewed lifecycle, IME, rendering and lockfile changes.
Formatting, strict lint, layers, assets and WASM passed. Full CI retained 3,961 passes, 14 baseline-matching GPU failures and 31 ignored executions. The local review commit has explicit approval to retain these documented failures; native/browser acceptance and paired app performance remain open.
84 lines
3.8 KiB
Rust
84 lines
3.8 KiB
Rust
//! Control-channel clicks and drags hold their modifiers for the whole gesture: egui takes
|
|
//! `i.modifiers` from `ModifiersChanged` events, not from the pointer events, and synthetic
|
|
//! events arrive one per frame, so a Shift-click must hold Shift from its press to its release.
|
|
|
|
use effectcraft_engine::Session;
|
|
use effectcraft_ui_egui::EffectcraftApp;
|
|
use effectcraft_ui_egui::control::{self, ControlRequest};
|
|
use serde_json::{Value, json};
|
|
|
|
/// Feed the queued synthetic input frame by frame; per frame: (has a press/release, the
|
|
/// modifiers egui holds in that frame, which only `ModifiersChanged` events change).
|
|
fn frames(app: &mut EffectcraftApp) -> Vec<(bool, egui::Modifiers)> {
|
|
let mut out = vec![];
|
|
let mut held = egui::Modifiers::NONE;
|
|
for _ in 0..64 {
|
|
let mut raw = egui::RawInput::default();
|
|
app.feed_synthetic(&mut raw);
|
|
if raw.events.is_empty() {
|
|
break;
|
|
}
|
|
// The harness feeds the batch itself; a real UI pass would apply it now.
|
|
app.mark_synthetic_applied();
|
|
let mut button = false;
|
|
for e in &raw.events {
|
|
match e {
|
|
egui::Event::ModifiersChanged(m) => held = *m,
|
|
egui::Event::PointerButton { .. } => button = true,
|
|
_ => {}
|
|
}
|
|
}
|
|
out.push((button, held));
|
|
}
|
|
out
|
|
}
|
|
|
|
fn send(app: &mut EffectcraftApp, method: &str, params: Value) {
|
|
let ctx = egui::Context::default();
|
|
let (req, _rx) = ControlRequest::new(method, params);
|
|
let now = app.now();
|
|
let _ = control::handle(app, &ctx, &req, now);
|
|
}
|
|
|
|
#[test]
|
|
fn control_gestures_reject_excessive_counts_without_queue_mutation() {
|
|
let mut app = EffectcraftApp::new(Session::default());
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10, "count": u64::MAX}));
|
|
assert!(app.take_synthetic_input().is_empty(), "an oversized click must not partially queue input");
|
|
|
|
send(&mut app, "ui.drag", json!({"from": {"x": 10, "y": 10}, "to": {"x": 90, "y": 40}, "steps": u64::MAX}));
|
|
assert!(app.take_synthetic_input().is_empty(), "an oversized drag must not partially queue input");
|
|
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10, "count": 512}));
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10, "count": 512}));
|
|
assert_eq!(app.take_synthetic_input().len(), 2054, "two maximum accepted clicks fit under the aggregate cap");
|
|
for _ in 0..4 {
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10, "count": 512}));
|
|
}
|
|
assert_eq!(app.take_synthetic_input().len(), 3081, "the pending event limit is enforced before requests can exceed it");
|
|
}
|
|
|
|
#[test]
|
|
fn shift_click_holds_shift_from_press_to_release() {
|
|
let mut app = EffectcraftApp::new(Session::default());
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10, "modifiers": {"shift": true}}));
|
|
let f = frames(&mut app);
|
|
let buttons: Vec<_> = f.iter().filter(|(b, _)| *b).collect();
|
|
assert_eq!(buttons.len(), 2, "press and release: {f:?}");
|
|
assert!(buttons.iter().all(|(_, m)| m.shift), "Shift on the press and the release: {f:?}");
|
|
// Afterwards the modifiers are released again.
|
|
send(&mut app, "ui.click", json!({"x": 10, "y": 10}));
|
|
assert!(frames(&mut app).iter().all(|(_, m)| !m.shift), "a plain click after it has no Shift");
|
|
}
|
|
|
|
#[test]
|
|
fn shift_drag_holds_shift_through_the_moves() {
|
|
let mut app = EffectcraftApp::new(Session::default());
|
|
send(&mut app, "ui.drag", json!({"from": {"x": 10, "y": 10}, "to": {"x": 90, "y": 40}, "steps": 4, "modifiers": {"shift": true}}));
|
|
let f = frames(&mut app);
|
|
let first = f.iter().position(|(b, _)| *b).expect("press");
|
|
let last = f.iter().rposition(|(b, _)| *b).expect("release");
|
|
assert!(last > first + 1, "moves between press and release: {f:?}");
|
|
assert!(f[first..=last].iter().all(|(_, m)| m.shift), "Shift held for the whole drag: {f:?}");
|
|
}
|