mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
503 lines
23 KiB
Rust
503 lines
23 KiB
Rust
//! Real pointer interaction with Revolve's compact controls.
|
|
use super::*;
|
|
use crate::{dialogs, theme};
|
|
use egui::{Event, PointerButton, Rect, Vec2, pos2};
|
|
use vectorcraft_engine::Session;
|
|
|
|
struct Harness {
|
|
app: VectorcraftApp,
|
|
ctx: egui::Context,
|
|
screen: Vec2,
|
|
texts: Vec<(String, Rect)>,
|
|
}
|
|
|
|
impl Harness {
|
|
fn new(screen: Vec2) -> Self {
|
|
Self::with("M150 40 L180 60 L180 140 L150 160", REVOLVE, screen)
|
|
}
|
|
|
|
/// The dialog of 3D effect `effect` on a path drawn from SVG path data `d`.
|
|
fn with(d: &str, effect: &str, screen: Vec2) -> Self {
|
|
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
|
app.run("file.new", json!({"width":300,"height":300})).unwrap();
|
|
app.run("path.create", json!({"d": d})).unwrap();
|
|
app.run("paint.setFill", json!({"color":"#338ad6"})).unwrap();
|
|
app.run("effect.dialog", json!({"effect":effect,"item":null})).unwrap();
|
|
let ctx = egui::Context::default();
|
|
theme::install_fonts(&ctx);
|
|
theme::apply(&ctx, Default::default());
|
|
Self { app, ctx, screen, texts: vec![] }
|
|
}
|
|
|
|
fn frame(&mut self, events: Vec<Event>) {
|
|
let input = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, self.screen)), events, ..Default::default() };
|
|
let mut out = self.ctx.run_ui(input, |ui| dialogs::show(&mut self.app, ui.ctx()));
|
|
out.textures_delta.clear();
|
|
fn collect(shape: &egui::Shape, clip: Rect, texts: &mut Vec<(String, Rect)>) {
|
|
match shape {
|
|
egui::Shape::Text(t) if clip.contains_rect(t.visual_bounding_rect()) => {
|
|
texts.push((t.galley.text().to_string(), t.visual_bounding_rect()))
|
|
}
|
|
egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| collect(s, clip, texts)),
|
|
_ => {}
|
|
}
|
|
}
|
|
self.texts.clear();
|
|
out.shapes.iter().for_each(|s| collect(&s.shape, s.clip_rect, &mut self.texts));
|
|
}
|
|
|
|
fn settle(&mut self) {
|
|
for _ in 0..5 {
|
|
self.frame(vec![]);
|
|
}
|
|
}
|
|
|
|
fn rect(&self, text: &str) -> Rect {
|
|
self.texts.iter().find(|(s, _)| s == text).map(|(_, r)| *r).unwrap_or_else(|| panic!("missing text {text:?}"))
|
|
}
|
|
|
|
/// Return the rectangle of the widget drawn under `Id::unique(id)` in the revolve test harness.
|
|
fn response(&self, id: impl std::hash::Hash + std::fmt::Debug) -> Rect {
|
|
self.ctx.read_response(Id::unique(id)).unwrap().rect
|
|
}
|
|
|
|
fn click(&mut self, p: Pos2) {
|
|
self.frame(vec![Event::PointerMoved(p), button(p, true)]);
|
|
self.frame(vec![button(p, false)]);
|
|
self.settle();
|
|
}
|
|
|
|
fn drag(&mut self, p: Pos2, delta: Vec2) {
|
|
self.frame(vec![Event::PointerMoved(p), button(p, true)]);
|
|
for i in 1..=5 {
|
|
self.frame(vec![Event::PointerMoved(p + delta * (i as f32 / 5.0))]);
|
|
}
|
|
self.frame(vec![button(p + delta, false)]);
|
|
self.settle();
|
|
}
|
|
|
|
fn value(&self, key: &str) -> f64 {
|
|
self.app.ui.dialog.as_ref().unwrap().f64(key, 0.0)
|
|
}
|
|
}
|
|
|
|
fn button(pos: Pos2, pressed: bool) -> Event {
|
|
Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Default::default() }
|
|
}
|
|
|
|
#[test]
|
|
fn values_labels_sliders_and_reset_dots_change_the_live_effect_in_one_undo_step() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
assert!(!h.rect("Advanced").is_negative(), "Advanced is visible without scrolling in a normal window");
|
|
let panel = h.ctx.memory(|m| m.area_rect(Id::unique(("dialog", "effect")).with("revolve"))).unwrap();
|
|
// Cap adds one 28-point row to the shared dialog's previous 540-point bound.
|
|
assert!(panel.width() < 420.0 && panel.height() < 570.0, "compact controls with Cap: {panel:?}");
|
|
let label = h.rect("Rotation X");
|
|
h.drag(pos2(label.left() + LABEL_W + 6.0 + VALUE_W / 2.0, label.center().y), vec2(40.0, 0.0));
|
|
assert!(h.value("rotationX") > 0.0, "the value itself drags");
|
|
let label = h.rect("Rotation Y");
|
|
h.drag(label.center(), vec2(40.0, 0.0));
|
|
assert!(h.value("rotationY") > 0.0, "the label scrubs");
|
|
let label = h.rect("Revolve angle");
|
|
h.click(pos2(label.left() + LABEL_W + VALUE_W + 24.0, label.center().y));
|
|
assert!(h.value("angle") < 180.0, "the slider changes the sweep");
|
|
h.click(h.response(("revolve-reset", "rotationX")).center());
|
|
assert_eq!(h.value("rotationX"), 0.0);
|
|
assert!(h.value("angle") < 180.0, "a dot resets only its own setting");
|
|
let node = h.app.session.doc().unwrap().doc.node(h.app.session.doc().unwrap().selection.objects[0]).unwrap();
|
|
assert_eq!(node.appearance.effects[0].params["angle"], json!(h.value("angle")));
|
|
h.click(h.response("revolve-reset-all").center());
|
|
let defaults = vectorcraft_effects::default_params(REVOLVE).unwrap();
|
|
let dialog = h.app.ui.dialog.as_ref().unwrap();
|
|
for (key, value) in defaults.as_object().unwrap() {
|
|
assert_eq!(dialog.fields.get(key), Some(value), "reset all: {key}");
|
|
}
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
h.app.run("edit.undo", json!({})).unwrap();
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
|
|
#[test]
|
|
fn preview_off_changes_fields_without_changing_the_document() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("preview".into(), json!(false));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.drag(h.rect("Rotation Y").center(), vec2(40.0, 0.0));
|
|
assert!(h.value("rotationY") > 0.0);
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
dialogs::cancel(&mut h.app);
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
|
|
#[test]
|
|
fn command_driven_fields_preview_and_small_windows_keep_the_buttons_reachable() {
|
|
let mut h = Harness::new(vec2(640.0, 480.0));
|
|
h.settle();
|
|
let dialog = h.app.ui.dialog.as_mut().unwrap();
|
|
dialog.fields.insert("angle".into(), json!(180));
|
|
h.settle();
|
|
let state = h.app.session.doc().unwrap();
|
|
let node = state.doc.node(state.selection.objects[0]).unwrap();
|
|
assert_eq!(node.appearance.effects[0].params["angle"], json!(180));
|
|
let screen = Rect::from_min_size(Pos2::ZERO, h.screen);
|
|
assert!(screen.contains_rect(h.rect("OK")) && screen.contains_rect(h.rect("Cancel")));
|
|
dialogs::cancel(&mut h.app);
|
|
assert!(!h.app.session.in_interaction());
|
|
}
|
|
|
|
#[test]
|
|
fn lighting_tab_shows_light_fields_and_keeps_the_dialog_compact() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().str("__gizmo"), "light");
|
|
for label in ["Shading", "Color", "Intensity", "Height", "Softness", "Ambient Light", "Light behind the object"] {
|
|
assert!(Rect::from_min_size(Pos2::ZERO, h.screen).contains_rect(h.rect(label)));
|
|
}
|
|
let panel = h.ctx.memory(|m| m.area_rect(Id::unique(("dialog", "effect")).with("revolve"))).unwrap();
|
|
assert!(panel.width() < 420.0 && panel.height() < 570.0, "compact lighting controls with light management: {panel:?}");
|
|
h.drag(h.rect("Height").center(), vec2(24.0, 0.0));
|
|
assert_ne!(h.value("lightHeight"), 30.0);
|
|
h.click(h.response(("revolve-reset", "lightHeight")).center());
|
|
assert_eq!(h.value("lightHeight"), 30.0);
|
|
h.click(h.rect("Rotation").center());
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().str("__gizmo"), "rotation");
|
|
assert!(!h.rect("Rotation X").is_negative());
|
|
}
|
|
|
|
#[test]
|
|
fn automatic_smooth_shading_previews_saves_and_respects_cancel_and_one_step_undo() {
|
|
for confirm in [false, true] {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
assert!(!h.texts.iter().any(|(text, _)| text == "Smooth shading"));
|
|
assert!(!h.app.ui.dialog.as_ref().unwrap().fields.contains_key("smoothShading"));
|
|
let art = |h: &Harness| {
|
|
let state = h.app.session.doc().unwrap();
|
|
vectorcraft_effects::revolve_art(state.doc.node(state.selection.objects[0]).unwrap()).unwrap()
|
|
};
|
|
let gradients = |n: &vectorcraft_doc::Node| {
|
|
let mut count = 0;
|
|
n.walk(&mut |face| {
|
|
count += face.appearance.items.iter().filter(|item| {
|
|
matches!(item, vectorcraft_doc::AppearanceItem::Fill(fill) if matches!(fill.paint, vectorcraft_doc::color::Paint::Gradient(_)))
|
|
}).count();
|
|
});
|
|
count
|
|
};
|
|
assert!(gradients(&art(&h)) > 0, "default rendering uses vector gradients");
|
|
h.click(h.rect("Shading").center());
|
|
assert_eq!(gradients(&art(&h)), 0, "Shading still controls all lighting");
|
|
h.click(h.response(("revolve-reset", "shade")).center());
|
|
assert!(gradients(&art(&h)) > 0);
|
|
h.drag(h.rect("Height").center(), vec2(24.0, 0.0));
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("preview".into(), json!(false));
|
|
h.settle();
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("preview".into(), json!(true));
|
|
h.settle();
|
|
if confirm {
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
let state = h.app.session.doc().unwrap();
|
|
let node = state.doc.node(state.selection.objects[0]).unwrap();
|
|
let loaded = vectorcraft_format::load(&vectorcraft_format::save(&state.doc, false)).unwrap();
|
|
assert!(!node.appearance.effects[0].params.as_object().unwrap().contains_key("smoothShading"));
|
|
assert_eq!(vectorcraft_effects::revolve_art(loaded.node(node.id).unwrap()), vectorcraft_effects::revolve_art(node));
|
|
h.app.run("edit.undo", json!({})).unwrap();
|
|
} else {
|
|
dialogs::cancel(&mut h.app);
|
|
}
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn advanced_visibility_option_is_saved_resettable_and_leaves_preview_geometry_complete() {
|
|
let mut h = Harness::new(vec2(1280.0, 1000.0));
|
|
h.settle();
|
|
h.click(h.rect("Advanced").center());
|
|
let panel = h.ctx.memory(|m| m.area_rect(Id::unique(("dialog", "effect")).with("revolve"))).unwrap();
|
|
h.frame(vec![
|
|
Event::PointerMoved(panel.center()),
|
|
Event::MouseWheel {
|
|
unit: egui::MouseWheelUnit::Point,
|
|
delta: vec2(0.0, -220.0),
|
|
phase: egui::TouchPhase::Move,
|
|
modifiers: Default::default(),
|
|
source: egui::MouseWheelSource::Unknown,
|
|
},
|
|
]);
|
|
h.settle();
|
|
let before = vectorcraft_effects::revolve_art({
|
|
let state = h.app.session.doc().unwrap();
|
|
state.doc.node(state.selection.objects[0]).unwrap()
|
|
})
|
|
.unwrap();
|
|
let checkbox = h.rect("Keep visible surfaces only when expanding");
|
|
h.click(checkbox.center());
|
|
assert!(!h.app.ui.dialog.as_ref().unwrap().bool("expandVisibleOnly"));
|
|
let state = h.app.session.doc().unwrap();
|
|
let node = state.doc.node(state.selection.objects[0]).unwrap();
|
|
assert_eq!(node.appearance.effects[0].params["expandVisibleOnly"], json!(false));
|
|
assert_eq!(vectorcraft_effects::revolve_art(node), Some(before));
|
|
h.click(h.response(("revolve-reset", "expandVisibleOnly")).center());
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().bool("expandVisibleOnly"));
|
|
h.click(h.rect("Keep visible surfaces only when expanding").center());
|
|
h.click(h.response("revolve-reset-all").center());
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().bool("expandVisibleOnly"));
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
let state = h.app.session.doc().unwrap();
|
|
let loaded = vectorcraft_format::load(&vectorcraft_format::save(&state.doc, false)).unwrap();
|
|
assert_eq!(loaded.node(state.selection.objects[0]).unwrap().appearance.effects[0].params["expandVisibleOnly"], json!(true));
|
|
}
|
|
|
|
/// Extrude & Bevel, Inflate and Rotate open the same compact controls, with their own options, the
|
|
/// Position presets and the artboard gizmo turning about the centre of the art.
|
|
#[test]
|
|
fn the_other_3d_effects_show_their_options_and_position_presets() {
|
|
use vectorcraft_effects::{EXTRUDE, INFLATE, ROTATE, ROTATION_PRESETS};
|
|
let square = "M100 100 L200 100 L200 200 L100 200 Z";
|
|
for (effect, own) in [
|
|
(EXTRUDE, &["Depth", "Twist", "Taper", "Cap", "Bevel", "Bevel Shape", "Space", "Bevel Inside", "Bevel both sides"][..]),
|
|
(INFLATE, &["Depth", "Twist", "Taper", "Cap", "Volume", "Both sides"][..]),
|
|
(ROTATE, &[][..]),
|
|
] {
|
|
let mut h = Harness::with(square, effect, vec2(1400.0, 1000.0));
|
|
h.settle();
|
|
// Extrude & Bevel and Inflate open on their Object tab; Rotate on Rotation.
|
|
for text in own.iter() {
|
|
h.rect(text);
|
|
}
|
|
if !own.is_empty() {
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("__tab".into(), json!("rotation"));
|
|
h.settle();
|
|
}
|
|
for text in ["Presets", "Rotation X", "Perspective"] {
|
|
h.rect(text);
|
|
}
|
|
// The defaults are the Off-Axis Front position, shown by name.
|
|
h.rect("Off-Axis Front");
|
|
assert!(!h.texts.iter().any(|(t, _)| t == "Revolve angle"), "{effect}: Revolve's own options stay out");
|
|
let d = h.app.ui.dialog.as_ref().unwrap();
|
|
assert!(dialogs::revolve_gizmo::active(d), "{effect}: the gizmo is on");
|
|
// A preset's angles show its name; other angles are Custom.
|
|
let [x, y, z] = ROTATION_PRESETS[14].1;
|
|
let d = h.app.ui.dialog.as_mut().unwrap();
|
|
d.fields.extend([("rotationX".into(), json!(x)), ("rotationY".into(), json!(y)), ("rotationZ".into(), json!(z))]);
|
|
h.settle();
|
|
h.rect("Isometric Top");
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("rotationZ".into(), json!(12.5));
|
|
h.settle();
|
|
h.rect("Custom");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn extrude_bevel_options_enable_with_a_bevel_shape() {
|
|
let mut h = Harness::with("M100 100 L200 100 L200 200 L100 200 Z", vectorcraft_effects::EXTRUDE, vec2(1400.0, 1000.0));
|
|
h.settle();
|
|
let width = h.response(("revolve-reset", "bevelWidth"));
|
|
assert!(!h.ctx.read_response(Id::unique(("revolve-reset", "bevelWidth"))).unwrap().enabled(), "no bevel: its sizes are off");
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("bevel".into(), json!("rounded"));
|
|
h.settle();
|
|
assert!(h.ctx.read_response(Id::unique(("revolve-reset", "bevelWidth"))).unwrap().enabled());
|
|
assert_eq!(h.response(("revolve-reset", "bevelWidth")), width);
|
|
}
|
|
|
|
#[test]
|
|
fn cap_checkbox_previews_resets_and_respects_cancel_and_one_step_undo() {
|
|
for confirm in [false, true] {
|
|
let mut h = Harness::new(vec2(1280.0, 1000.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
let dialog = h.app.ui.dialog.as_mut().unwrap();
|
|
dialog.fields.insert("offset".into(), json!(20));
|
|
dialog.fields.insert("rotationX".into(), json!(-35));
|
|
h.settle();
|
|
let art = |h: &Harness| {
|
|
let state = h.app.session.doc().unwrap();
|
|
vectorcraft_effects::revolve_art(state.doc.node(state.selection.objects[0]).unwrap()).unwrap()
|
|
};
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().bool("cap"), "Cap starts on");
|
|
let capped = art(&h);
|
|
h.click(h.rect("Cap").center());
|
|
assert!(!h.app.ui.dialog.as_ref().unwrap().bool("cap"));
|
|
assert_ne!(art(&h), capped);
|
|
h.click(h.response(("revolve-reset", "cap")).center());
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().bool("cap"));
|
|
assert_eq!(art(&h), capped);
|
|
h.click(h.rect("Cap").center());
|
|
h.click(h.response("revolve-reset-all").center());
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().bool("cap"));
|
|
h.click(h.rect("Cap").center());
|
|
if confirm {
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
let state = h.app.session.doc().unwrap();
|
|
assert_eq!(state.doc.node(state.selection.objects[0]).unwrap().appearance.effects[0].params["cap"], json!(false));
|
|
let loaded = vectorcraft_format::load(&vectorcraft_format::save(&state.doc, false)).unwrap();
|
|
assert_eq!(loaded.node(state.selection.objects[0]).unwrap().appearance.effects[0].params["cap"], json!(false));
|
|
h.app.run("edit.undo", json!({})).unwrap();
|
|
} else {
|
|
dialogs::cancel(&mut h.app);
|
|
}
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
assert!(!h.app.session.in_interaction());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn light_picker_previews_and_reset_dot_and_cancel_restore_white() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
h.click(pos2(h.rect("#FFFFFF").left() - 18.0, h.rect("Color").center().y));
|
|
let green =
|
|
h.texts.iter().find(|(s, _)| s.starts_with("G ")).map(|(_, r)| *r).unwrap_or_else(|| panic!("the opaque RGB picker opens: {:?}", h.texts));
|
|
h.drag(green.center(), vec2(-100.0, 0.0));
|
|
let color = h.app.ui.dialog.as_ref().unwrap().str("lightColor");
|
|
assert_ne!(color, "#ffffff");
|
|
let st = h.app.session.doc().unwrap();
|
|
assert_eq!(st.doc.node(st.selection.objects[0]).unwrap().appearance.effects[0].params["lightColor"], json!(color));
|
|
h.click(h.rect("Color").center());
|
|
h.click(h.response(("revolve-reset", "lightColor")).center());
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().str("lightColor"), "#ffffff");
|
|
dialogs::cancel(&mut h.app);
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
|
|
#[test]
|
|
fn every_3d_dialog_has_color_and_reset_all_restores_white() {
|
|
for id in [REVOLVE, EXTRUDE, INFLATE, vectorcraft_effects::ROTATE] {
|
|
let mut h = Harness::with("M100 100L200 100L200 200L100 200Z", id, vec2(1280.0, 900.0));
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("lightColor".into(), json!("blue"));
|
|
h.settle();
|
|
assert!(!h.rect("#FFFFFF").is_negative(), "invalid stored colors show the renderer's white fallback");
|
|
h.app.ui.dialog.as_mut().unwrap().fields.insert("lightColor".into(), json!("#ff8000"));
|
|
h.settle();
|
|
assert!(!h.rect("Color").is_negative());
|
|
assert!(!h.rect("#FF8000").is_negative());
|
|
h.click(h.response("revolve-reset-all").center());
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().str("lightColor"), "#ffffff");
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn light_list_add_select_edit_delete_and_reset_keep_each_light_independent() {
|
|
for effect in [REVOLVE, EXTRUDE, INFLATE, vectorcraft_effects::ROTATE] {
|
|
let mut h = Harness::with("M100 100L200 100L200 200L100 200Z", effect, vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
let one = h.app.session.doc().unwrap().doc.clone();
|
|
h.click(h.rect("Delete Light").center());
|
|
assert_eq!(h.app.session.doc().unwrap().doc, one, "retain at least one light");
|
|
h.click(h.rect("Add Light").center());
|
|
let d = h.app.ui.dialog.as_ref().unwrap();
|
|
let first = d.fields["lights"][0].clone();
|
|
assert_eq!(d.fields["lights"].as_array().unwrap().len(), 2);
|
|
assert_eq!(d.fields["__light"], 1);
|
|
assert_eq!(selected_light(d).f64("lightHeight", 0.0), 90.0);
|
|
h.drag(h.rect("Height").center(), vec2(-35.0, 0.0));
|
|
let d = h.app.ui.dialog.as_ref().unwrap();
|
|
assert_ne!(d.fields["lights"][1]["height"], 90);
|
|
assert_eq!(d.fields["lights"][0], first);
|
|
let two = h.app.session.doc().unwrap().doc.clone();
|
|
h.click(h.rect("Light 2").center());
|
|
h.click(h.rect("Light 1").center());
|
|
assert_eq!(h.app.session.doc().unwrap().doc, two, "selection changes no effect params");
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().fields["__light"], 0);
|
|
h.click(h.rect("Delete Light").center());
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().fields["lights"].as_array().unwrap().len(), 1);
|
|
h.click(h.response("revolve-reset-all").center());
|
|
assert!(h.app.ui.dialog.as_ref().unwrap().fields["lights"].is_null());
|
|
h.click(h.rect("Add Light").center());
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
h.app.run("edit.undo", json!({})).unwrap();
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before, "whole dialog is one undo step");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn light_list_stops_at_ten_and_cancel_restores_the_source() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
for _ in 0..11 {
|
|
h.click(h.rect("Add Light").center());
|
|
}
|
|
let d = h.app.ui.dialog.as_ref().unwrap();
|
|
assert_eq!(d.fields["lights"].as_array().unwrap().len(), 10);
|
|
assert_eq!(d.fields["__light"], 9);
|
|
dialogs::cancel(&mut h.app);
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
|
|
#[test]
|
|
fn lighting_presets_preview_custom_edits_reset_and_commit_as_one_undo_step() {
|
|
for effect in [REVOLVE, EXTRUDE, INFLATE, vectorcraft_effects::ROTATE] {
|
|
let mut h = Harness::with("M100 100L200 100L200 200L100 200Z", effect, vec2(1280.0, 900.0));
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
let view = h.app.ui.dialog.as_ref().unwrap().fields["rotationX"].clone();
|
|
h.click(h.rect("Standard").center());
|
|
h.click(h.rect("Right").center());
|
|
assert_eq!(h.value("lightRotation"), 0.0);
|
|
assert_eq!(h.app.ui.dialog.as_ref().unwrap().fields["rotationX"], view);
|
|
h.drag(h.rect("Height").center(), vec2(20.0, 0.0));
|
|
assert!(!h.rect("Custom").is_negative());
|
|
h.click(h.rect("Custom").center());
|
|
h.click(h.rect("Diffuse").center());
|
|
assert_eq!(h.value("lightSoftness"), 100.0);
|
|
assert_eq!(h.value("ambient"), 35.0);
|
|
h.click(h.rect("Add Light").center());
|
|
assert!(!h.rect("Custom").is_negative());
|
|
h.click(h.rect("Custom").center());
|
|
h.click(h.rect("Top Left").center());
|
|
let d = h.app.ui.dialog.as_ref().unwrap();
|
|
assert!(d.fields["lights"].is_null());
|
|
assert_eq!(d.fields["__light"], 0);
|
|
assert_eq!(d.f64("lightRotation", 0.0), 135.0);
|
|
h.click(h.response("revolve-reset-all").center());
|
|
assert!(!h.rect("Standard").is_negative());
|
|
h.click(h.rect("Standard").center());
|
|
h.click(h.rect("Right").center());
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
h.app.run("edit.undo", json!({})).unwrap();
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn lighting_presets_cancel_restores_an_existing_custom_light_rig() {
|
|
let mut h = Harness::new(vec2(1280.0, 900.0));
|
|
h.settle();
|
|
dialogs::confirm(&mut h.app).unwrap();
|
|
h.app
|
|
.run(
|
|
"effect.setParams",
|
|
json!({"index":0,"item":null,"params":{
|
|
"ambient":19,"lights":[{"color":"#ff8040"},{"color":"#4080ff"}]
|
|
}}),
|
|
)
|
|
.unwrap();
|
|
let before = h.app.session.doc().unwrap().doc.clone();
|
|
h.app.run("effect.dialog", json!({"effect":REVOLVE,"index":0,"item":null})).unwrap();
|
|
h.settle();
|
|
h.click(h.rect("Lighting").center());
|
|
h.click(h.rect("Custom").center());
|
|
h.click(h.rect("Right").center());
|
|
assert_ne!(h.app.session.doc().unwrap().doc, before);
|
|
dialogs::cancel(&mut h.app);
|
|
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
|
}
|