//! Perspective Grid dialogs: Define Grid, the presets manager and the tool options. use serde_json::{Value, json}; use vectorcraft_engine::Session; use vectorcraft_tools::distort::perspective::PerspectiveGrid; use crate::{VectorcraftApp, theme}; fn frame(app: &mut VectorcraftApp) { let ctx = egui::Context::default(); theme::install_fonts(&ctx); let mut out = ctx.run_ui(egui::RawInput::default(), |ui| super::show(app, ui.ctx())); out.textures_delta.clear(); } fn app() -> VectorcraftApp { let mut app = VectorcraftApp::new(Session::new(), Default::default()); app.run("file.new", json!({"width": 800, "height": 600})).unwrap(); app } fn kind(app: &VectorcraftApp) -> Option<&str> { app.ui.dialog.as_ref().map(|d| d.kind.as_str()) } fn field(app: &VectorcraftApp, k: &str) -> Value { app.ui.dialog.as_ref().and_then(|d| d.fields.get(k).cloned()).unwrap_or_default() } fn set(app: &mut VectorcraftApp, k: &str, v: Value) { app.ui.dialog.as_mut().expect("a dialog is open").fields.insert(k.into(), v); } fn grid(app: &VectorcraftApp) -> PerspectiveGrid { PerspectiveGrid::effective(&app.session.active().unwrap().doc) } #[test] fn define_grid_opens_on_the_grid_and_applies_its_fields() { let mut app = app(); app.run("perspective.grid.preset", json!({"kind": 3})).unwrap(); let g = grid(&app); app.run("ui.perspectiveGridDialog", json!({})).unwrap(); assert_eq!(kind(&app), Some(super::perspective_grid::KIND)); assert_eq!(field(&app, "kind"), json!(3), "prefilled from the grid, not a fixed type"); assert!((field(&app, "gridline").as_f64().unwrap() - g.cell).abs() < 1e-9); frame(&mut app); // A new unit converts the lengths shown. set(&mut app, "units", json!("inches")); frame(&mut app); assert!((field(&app, "gridline").as_f64().unwrap() - g.cell / 72.0).abs() < 1e-9); // OK with nothing changed leaves the grid as it was. super::confirm(&mut app).unwrap(); assert_eq!(kind(&app), None); assert_eq!((grid(&app).cell, grid(&app).vp_left, grid(&app).kind), (g.cell, g.vp_left, 3)); // A new viewing angle and colour apply in one undo step. let undo = app.session.active().unwrap().history.undo.len(); app.run("ui.perspectiveGridDialog", json!({})).unwrap(); set(&mut app, "angle", json!(30)); set(&mut app, "rightColor", json!("#112233")); frame(&mut app); super::confirm(&mut app).unwrap(); let n = grid(&app); assert!((n.viewing_angle() - 30.0).abs() < 1e-9); assert_eq!(n.right_color.hex(), "#112233"); assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1); // A refused value keeps the dialog open. app.run("ui.perspectiveGridDialog", json!({})).unwrap(); set(&mut app, "distance", json!(-5)); assert!(super::confirm(&mut app).is_err()); assert_eq!(kind(&app), Some(super::perspective_grid::KIND)); } #[test] fn define_grid_is_a_view_menu_item() { let app = app(); let entries = crate::menus::menu_entries(&app); let e = entries.iter().find(|e| e.command.as_deref() == Some("ui.perspectiveGridDialog")).expect("View › Perspective Grid › Define Grid…"); assert!(e.path.iter().any(|p| p == "Perspective Grid"), "{:?}", e.path); assert!(e.enabled); } #[test] fn define_grid_loads_and_saves_presets() { let mut app = app(); app.run("ui.perspectiveGridDialog", json!({})).unwrap(); frame(&mut app); assert_eq!(field(&app, "name"), json!("[2P-Normal View]")); // Choosing a preset (an agent sets its fields; the menu loads them) keeps its name while the // fields are its; a changed field makes the fields [Custom]. set(&mut app, "angle", json!(20)); frame(&mut app); assert_eq!(field(&app, "name"), json!("")); // Save Preset… asks for a name; OK saves the fields and comes back to Define Grid on it. app.ui.dialog.as_mut().unwrap().fields.insert("__mode".into(), json!("save")); set(&mut app, "__from", json!("define")); set(&mut app, "name", json!("Shallow")); frame(&mut app); super::confirm(&mut app).unwrap(); assert_eq!(kind(&app), Some(super::perspective_grid::KIND)); assert_eq!((field(&app, "name"), field(&app, "__mode")), (json!("Shallow"), Value::Null)); assert_eq!(app.session.prefs.perspective_presets[0].angle, 20.0); frame(&mut app); assert_eq!(field(&app, "name"), json!("Shallow"), "the fields are the saved preset's"); super::confirm(&mut app).unwrap(); assert_eq!(grid(&app).name, "Shallow"); // View › Perspective Grid › Save Grid as Preset…: the grid under a new name. app.run("ui.savePerspectivePreset", json!({})).unwrap(); assert_eq!(field(&app, "name"), json!("Perspective Preset 1")); frame(&mut app); super::confirm(&mut app).unwrap(); assert_eq!(kind(&app), None); assert_eq!(app.session.prefs.perspective_presets.len(), 2); } #[test] fn the_presets_manager_creates_edits_and_deletes_presets() { use super::perspective_presets::{self, Action}; let mut app = app(); app.run("ui.perspectivePresetsDialog", json!({"selected": "[3P-Normal View]"})).unwrap(); frame(&mut app); let press = |app: &mut VectorcraftApp, act: Action, current: &str| { let mut d = app.ui.dialog.take().unwrap(); perspective_presets::run(app, &mut d, act, current).unwrap(); app.ui.dialog = Some(d); frame(app); }; // New… opens the editor on a copy of the selected preset; its OK comes back here. press(&mut app, Action::New, "[3P-Normal View]"); assert_eq!((kind(&app), field(&app, "kind")), (Some(super::perspective_grid::KIND), json!(3))); set(&mut app, "name", json!("Canyon")); set(&mut app, "opacity", json!(90)); super::confirm(&mut app).unwrap(); assert_eq!((kind(&app), field(&app, "selected")), (Some(perspective_presets::KIND), json!("Canyon"))); // Edit… renames it. press(&mut app, Action::Edit, "Canyon"); set(&mut app, "name", json!("Gorge")); super::confirm(&mut app).unwrap(); assert_eq!(field(&app, "selected"), json!("Gorge")); assert_eq!(app.session.prefs.perspective_presets.iter().map(|p| p.name.as_str()).collect::>(), ["Gorge"]); assert_eq!(app.session.prefs.perspective_presets[0].opacity, 90.0); // Its type's menu lists it in a slot, which applies it. assert_eq!(crate::menus::dynamic_label(&app, "ui.perspectiveUserPreset3.1", ""), "Gorge"); assert!(crate::menus::hidden_slot(&app, "ui.perspectiveUserPreset3.2") && crate::menus::hidden_slot(&app, "ui.perspectiveUserPreset2.1")); app.run("ui.perspectiveUserPreset3.1", json!({})).unwrap(); assert_eq!(grid(&app).name, "Gorge"); // Delete selects the one above it. press(&mut app, Action::Delete, "Gorge"); assert!(app.session.prefs.perspective_presets.is_empty()); assert_eq!(field(&app, "selected"), json!("[3P-Low View]")); assert!(app.run("ui.perspectiveUserPreset3.1", json!({})).is_err()); } #[test] fn perspective_preset_menus_list_the_views_of_each_type() { let app = app(); let entries = crate::menus::menu_entries(&app); let under = |sub: &str| entries.iter().filter(|e| e.path.iter().any(|p| p == sub)).map(|e| e.label.clone()).collect::>(); assert_eq!(under("One Point Perspective"), ["[1P-Normal View]", "[1P-Low View]", "[1P-High View]"]); assert_eq!(under("Three Point Perspective"), ["[3P-Normal View]", "[3P-Low View]"]); let presets = entries.iter().find(|e| e.command.as_deref() == Some("ui.perspectivePresetsDialog")).unwrap(); assert_eq!((presets.label.as_str(), presets.path.first().map(String::as_str)), ("Perspective Grid Presets…", Some("Edit"))); }