mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Light Settings: test Environment lights and the other types through the dialog (#1588)
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
//! Light Settings dialog (Layer ▸ New ▸ Light, Layer Settings on a light): every light type the
|
||||
//! engine has, Environment included, and an existing light's type kept when it's reopened (#1588).
|
||||
|
||||
use effectcraft_engine::Session;
|
||||
use effectcraft_engine::project::{Layer, LayerId, LayerSource, LightKind};
|
||||
use effectcraft_ui_egui::{Dialog, EffectcraftApp};
|
||||
use egui_kittest::Harness;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn harness() -> Harness<'static, EffectcraftApp> {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Lights", "width": 640, "height": 360, "duration": 2})).unwrap();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
|
||||
h.step();
|
||||
h
|
||||
}
|
||||
|
||||
/// An automation element's rect in the last frame drawn (a dialog settles its place in its
|
||||
/// first frames, so the frame before can be stale).
|
||||
fn element(h: &Harness<'_, EffectcraftApp>, id: &str) -> Option<effectcraft_ui_egui::automation::Element> {
|
||||
let auto = &h.state().auto;
|
||||
auto.elements.iter().chain(auto.previous.iter()).find(|e| e.id == id).cloned()
|
||||
}
|
||||
|
||||
fn click_id(h: &mut Harness<'_, EffectcraftApp>, id: &str) {
|
||||
let [x, y, w, hh] = element(h, id).unwrap_or_else(|| panic!("{id} is registered")).rect;
|
||||
let p = egui::pos2(x + w / 2.0, y + hh / 2.0);
|
||||
h.input_mut().events.push(egui::Event::PointerMoved(p));
|
||||
h.step();
|
||||
h.input_mut().events.push(egui::Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE });
|
||||
h.step();
|
||||
h.input_mut().events.push(egui::Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::NONE });
|
||||
h.run_steps(3);
|
||||
}
|
||||
|
||||
fn invoke(h: &mut Harness<'_, EffectcraftApp>, id: &str, params: Value) {
|
||||
let ctx = h.ctx.clone();
|
||||
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, id, params).unwrap();
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
/// Picks the dialog's Light Type `kind` from its menu.
|
||||
fn choose(h: &mut Harness<'_, EffectcraftApp>, kind: LightKind) {
|
||||
click_id(h, "dialog.light.type");
|
||||
let i = LightKind::ALL.iter().position(|k| *k == kind).unwrap();
|
||||
click_id(h, &format!("dialog.light.typeItem.{i}"));
|
||||
}
|
||||
|
||||
fn ok(h: &mut Harness<'_, EffectcraftApp>) {
|
||||
click_id(h, "dialog.light.ok");
|
||||
assert_eq!(h.state().dialog, None);
|
||||
}
|
||||
|
||||
fn layer(h: &Harness<'_, EffectcraftApp>, id: u64) -> Layer {
|
||||
h.state().session.active_comp().unwrap().layer(LayerId(id)).unwrap().clone()
|
||||
}
|
||||
|
||||
fn kind(h: &Harness<'_, EffectcraftApp>, id: u64) -> LightKind {
|
||||
match layer(h, id).source {
|
||||
LayerSource::Light { kind } => kind,
|
||||
other => panic!("not a light: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn last_layer(h: &Harness<'_, EffectcraftApp>) -> u64 {
|
||||
h.state().session.active_comp().unwrap().layers.iter().map(|l| l.id.0).max().unwrap()
|
||||
}
|
||||
|
||||
/// Opens Light Settings for an existing light, as Layer Settings does.
|
||||
fn reopen(h: &mut Harness<'_, EffectcraftApp>, id: u64) {
|
||||
invoke(h, "layer.settings", json!({"layer": id}));
|
||||
assert_eq!(h.state().dialog, Some(Dialog::LightSettings));
|
||||
}
|
||||
|
||||
/// An Environment light lit from a solid, its image turned 30°.
|
||||
fn environment_light(h: &mut Harness<'_, EffectcraftApp>) -> (u64, u64) {
|
||||
let s = &mut h.state_mut().session;
|
||||
let sky = s.execute("layer.newSolid", json!({"width": 256, "height": 128, "name": "Sky"})).unwrap()["layer"].as_u64().unwrap();
|
||||
let env = s.execute("layer.newLight", json!({"kind": "Environment", "name": "Env", "intensity": 80})).unwrap()["layer"].as_u64().unwrap();
|
||||
s.execute("prop.set", json!({"layer": env, "path": "lightOptions/source", "value": sky})).unwrap();
|
||||
s.execute("prop.set", json!({"layer": env, "path": "lightOptions/rotation", "value": 30})).unwrap();
|
||||
h.run_steps(2);
|
||||
(env, sky)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_light_type_menu_lists_every_light_type() {
|
||||
let mut h = harness();
|
||||
invoke(&mut h, "layer.newLight", json!({}));
|
||||
click_id(&mut h, "dialog.light.type");
|
||||
for (i, k) in LightKind::ALL.iter().enumerate() {
|
||||
let item = element(&h, &format!("dialog.light.typeItem.{i}")).map(|e| e.label);
|
||||
assert_eq!(item.as_deref(), Some(k.label()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_light_creates_an_environment_light() {
|
||||
let mut h = harness();
|
||||
invoke(&mut h, "layer.newLight", json!({}));
|
||||
choose(&mut h, LightKind::Environment);
|
||||
ok(&mut h);
|
||||
let id = last_layer(&h);
|
||||
assert_eq!(kind(&h, id), LightKind::Environment);
|
||||
let l = layer(&h, id);
|
||||
assert!(l.props.prop("lightOptions/source").is_some() && l.props.prop("lightOptions/falloff").is_none());
|
||||
// One undoable step, redone as Environment.
|
||||
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
|
||||
assert!(h.state().session.active_comp().unwrap().layer(LayerId(id)).is_none());
|
||||
h.state_mut().session.execute("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(kind(&h, id), LightKind::Environment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reopening_an_environment_light_and_pressing_ok_keeps_it() {
|
||||
let mut h = harness();
|
||||
let (env, sky) = environment_light(&mut h);
|
||||
let before = layer(&h, env);
|
||||
reopen(&mut h, env);
|
||||
ok(&mut h);
|
||||
let after = layer(&h, env);
|
||||
assert_eq!(kind(&h, env), LightKind::Environment, "not turned into a Spot light");
|
||||
assert_eq!(after.props.prop("lightOptions/source").unwrap().value.as_layer(), Some(sky));
|
||||
for path in ["lightOptions/rotation", "lightOptions/intensity"] {
|
||||
assert_eq!(after.props.prop(path).unwrap().value, before.props.prop(path).unwrap().value, "{path}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_light_settings_changes_nothing() {
|
||||
let mut h = harness();
|
||||
let (env, _) = environment_light(&mut h);
|
||||
let before = serde_json::to_value(layer(&h, env)).unwrap();
|
||||
let steps = h.state().session.history.undo.len();
|
||||
reopen(&mut h, env);
|
||||
choose(&mut h, LightKind::Spot);
|
||||
click_id(&mut h, "dialog.light.cancel");
|
||||
assert_eq!(h.state().dialog, None);
|
||||
assert_eq!(serde_json::to_value(layer(&h, env)).unwrap(), before);
|
||||
assert_eq!(h.state().session.history.undo.len(), steps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_a_light_to_environment_undoes_and_redoes() {
|
||||
let mut h = harness();
|
||||
let id = h.state_mut().session.execute("layer.newLight", json!({"kind": "Spot", "coneAngle": 45})).unwrap()["layer"].as_u64().unwrap();
|
||||
h.run_steps(2);
|
||||
reopen(&mut h, id);
|
||||
choose(&mut h, LightKind::Environment);
|
||||
ok(&mut h);
|
||||
assert_eq!(kind(&h, id), LightKind::Environment);
|
||||
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(kind(&h, id), LightKind::Spot);
|
||||
assert_eq!(layer(&h, id).props.prop("lightOptions/coneAngle").unwrap().value.as_f64(), 45.0);
|
||||
h.state_mut().session.execute("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(kind(&h, id), LightKind::Environment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_other_light_types_are_created_and_kept() {
|
||||
let mut h = harness();
|
||||
for k in [LightKind::Parallel, LightKind::Spot, LightKind::Point, LightKind::Ambient] {
|
||||
invoke(&mut h, "layer.newLight", json!({}));
|
||||
choose(&mut h, k);
|
||||
ok(&mut h);
|
||||
let id = last_layer(&h);
|
||||
assert_eq!(kind(&h, id), k);
|
||||
reopen(&mut h, id);
|
||||
ok(&mut h);
|
||||
assert_eq!(kind(&h, id), k, "{} kept on reopening", k.label());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user