Files
vectorcraft/crates/ui-egui/src/panels/tests_units.rs
Hamza c58c642f07 Rulers: the left ruler leaves out a label that would run into the one above
A label on the left ruler is spelled one character per line, so a long one is
tall: "-0.0625" is seven characters and reaches 60.5 pt down the ruler, while a
sixteenth-inch step leaves 54 pt at zoom 12. Binary steps made such labels
reachable -- the decimal ladder's "-0.1" was four characters in an 86 pt gap --
so the ruler could draw one label over another.

A label that would start inside the one above it is left out. Every tick still
draws, as do the labels that fit, and `vertical_label_height` gives the reach
of a label so the decision is one comparison.
2026-10-19 23:47:04 +07:00

174 lines
8.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Headless frames of Preferences ▸ Units: the General unit in the Transform panel, Properties,
//! the Control bar, the Info panel, dialogs, the Preferences dialog itself and New Document; the
//! Type unit in the Character panel; the change showing in the next frame.
use serde_json::json;
use vectorcraft_doc::Unit;
use super::tests_appearance::{app_with_rect, frame_events, run};
use super::*;
/// Every text drawn in the second headless frame of `f` (windows size themselves in the first).
fn texts(app: &mut VectorcraftApp, mut f: impl FnMut(&mut VectorcraftApp, &mut Ui)) -> Vec<String> {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
frame_events(&ctx, app, vec![], &mut f);
frame_events(&ctx, app, vec![], f).into_iter().map(|(s, _)| s).collect()
}
/// Every text drawn by the open dialog in one headless frame.
fn dialog_texts(app: &mut VectorcraftApp) -> Vec<String> {
texts(app, |app, ui| crate::dialogs::show(app, ui.ctx()))
}
fn shows(t: &[String], s: &str) -> bool {
t.iter().any(|x| x == s)
}
/// The 100 pt square of [`app_with_rect`] in millimetres.
fn side_mm() -> String {
Unit::Millimeters.format(100.0)
}
fn general_mm(app: &mut VectorcraftApp) {
app.run("prefs.set", json!({"key": "unitsGeneral", "value": "Millimeters"})).unwrap();
}
#[test]
fn panels_show_millimetres_after_the_preference_changes() {
let mut app = app_with_rect();
assert_eq!(side_mm(), "35.2778 mm");
assert!(shows(&texts(&mut app, transform::show), "100 pt"), "points first");
general_mm(&mut app);
// The next frame: no restart, nothing cached.
let t = texts(&mut app, transform::show);
assert!(shows(&t, &side_mm()) && !shows(&t, "100 pt"), "Transform panel: {t:?}");
assert!(shows(&texts(&mut app, properties::show), &side_mm()), "Properties panel");
assert!(shows(&texts(&mut app, crate::chrome::control_bar), &side_mm()), "Control bar");
assert!(shows(&texts(&mut app, info::show), &format!("W: {}", side_mm())), "Info panel");
// Document Setup back to points: everything follows the document.
run(&mut app, "document.setUnits", json!({"units": "Points"}));
assert!(shows(&texts(&mut app, transform::show), "100 pt"));
}
#[test]
fn dialog_lengths_show_in_the_general_unit() {
let mut app = app_with_rect();
general_mm(&mut app);
crate::menus::invoke(&mut app, "object.move", json!({}));
let t = dialog_texts(&mut app);
assert!(shows(&t, "0 mm"), "Move: {t:?}");
// A typed unit wins; the move is in points.
app.ui.dialog.as_mut().unwrap().fields.insert("dx".into(), json!("10 mm"));
assert!(shows(&dialog_texts(&mut app), "10 mm"));
crate::dialogs::confirm(&mut app).unwrap();
let st = app.session.active().unwrap();
let b = st.doc.bounds_of(&st.selection.objects, false).unwrap();
assert!((b.x0 - (10.0 + 10.0 * 72.0 / 25.4)).abs() < 1e-6, "{b:?}");
// A shape tool's size dialog and an effect's distances.
crate::dialogs::open_tool_dialog(&mut app, "rectangle", json!({"x": 0, "y": 0}));
assert!(shows(&dialog_texts(&mut app), &side_mm()), "Rectangle");
app.run("effect.dialog", json!({"effect": "stylize.dropShadow"})).unwrap();
let x = vectorcraft_effects::default_params("stylize.dropShadow").unwrap()["x"].as_f64().unwrap();
let t = dialog_texts(&mut app);
assert!(shows(&t, &Unit::Millimeters.format(x)), "Drop Shadow: {t:?}");
crate::dialogs::cancel(&mut app);
}
#[test]
fn type_sizes_follow_units_type() {
let mut app = app_with_rect();
let id = run(&mut app, "text.create", json!({"x": 10, "y": 150, "text": "Hi", "size": 24}))["id"].clone();
run(&mut app, "select.set", json!({ "ids": [id] }));
general_mm(&mut app);
assert!(shows(&texts(&mut app, character::show), "24 pt"), "General leaves type alone");
app.run("prefs.set", json!({"key": "unitsType", "value": "inches"})).unwrap();
let t = texts(&mut app, character::show);
assert!(shows(&t, &Unit::Inches.format(24.0)), "Character panel: {t:?}");
}
#[test]
fn the_preferences_dialog_shows_the_documents_units() {
let mut app = app_with_rect();
run(&mut app, "document.setUnits", json!({"units": "Millimeters"}));
crate::prefs_dialog::open(&mut app, Some("General"));
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["unitsGeneral"], json!("millimeters"));
let inc = app.session.prefs.keyboard_increment;
let frame = |app: &mut VectorcraftApp| texts(app, |app, ui| crate::prefs_dialog::show(app, ui.ctx()));
assert!(shows(&frame(&mut app), &Unit::Millimeters.format(inc)), "Keyboard Increment in mm");
// Choosing another General unit in the dialog shows at once, and OK applies it.
app.ui.dialog.as_mut().unwrap().fields.insert("unitsGeneral".into(), json!("inches"));
assert!(shows(&frame(&mut app), &Unit::Inches.format(inc)));
crate::prefs_dialog::confirm(&mut app).unwrap();
assert_eq!(app.session.active().unwrap().doc.units, Unit::Inches);
assert_eq!(app.session.prefs.units_general, "inches");
}
#[test]
fn new_documents_start_in_the_general_preference() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
general_mm(&mut app);
app.run("file.newDialog", json!({})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["units"], json!("Millimeters"));
let t = dialog_texts(&mut app);
assert!(shows(&t, &Unit::Millimeters.format(612.0)), "Letter in mm: {t:?}");
crate::dialogs::confirm(&mut app).unwrap();
let d = &app.session.active().unwrap().doc;
assert_eq!((d.units, d.artboards[0].rect.width()), (Unit::Millimeters, 612.0));
}
/// A label spelled down the left ruler is tall, and a sixteenth-inch step does not always leave
/// room for a long one: at zoom 12 the labels are 54 pt apart while "-0.0625" reaches 60.5 pt, so
/// the ruler leaves that label out rather than drawing it over its neighbour. Every tick stays.
#[test]
fn a_long_vertical_label_does_not_fit_a_sixteenth_inch_step() {
use crate::canvas::vertical_label_height;
use vectorcraft_doc::Unit;
// One character is one line; each further character adds a line.
assert_eq!(vertical_label_height(1), 9.5);
assert_eq!(vertical_label_height(2), 18.0);
assert_eq!(vertical_label_height(7), 60.5);
// The gap a label has to fit into, at the zoom where a sixteenth is first chosen and beyond.
let gap = |zoom: f64| Unit::Inches.ruler_step(zoom) * Unit::Inches.points() * zoom;
assert!((gap(12.0) - 54.0).abs() < 1e-9, "{}", gap(12.0));
assert!(vertical_label_height(7) > gap(12.0) as f32, "a seven-character label has to be left out");
// The labels this ruler actually writes below an inch are six characters, which do fit.
assert!(vertical_label_height("0.0625".chars().count()) < gap(12.0) as f32);
// Zoomed further in the gap grows and even a long label fits.
assert!(vertical_label_height(7) < gap(20.0) as f32, "{}", gap(20.0));
}
#[test]
fn ruler_labels_have_the_steps_decimals() {
use crate::canvas::ruler_label;
assert_eq!(ruler_label(100.0000001, 100.0), "100");
assert_eq!(ruler_label(0.30000000004, 0.1), "0.3");
assert_eq!(ruler_label(1.25, 0.05), "1.25");
assert_eq!(ruler_label(-0.0000001, 0.5), "0");
assert_eq!(ruler_label(-2.5, 0.5), "-2.5");
// An inch ruler's binary steps keep their exact value: rounding the step's magnitude wrote
// 1/16 as "0.06" and 1/8 as "0.13" (#2827).
assert_eq!(ruler_label(0.0625, 0.0625), "0.0625");
assert_eq!(ruler_label(0.125, 0.125), "0.125");
assert_eq!(ruler_label(0.75, 0.25), "0.75");
assert_eq!(ruler_label(1.0, 0.0625), "1");
assert_eq!(ruler_label(1.1875, 0.0625), "1.1875");
}
#[test]
fn new_document_cards_follow_the_chosen_units() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
general_mm(&mut app);
app.run("file.newDialog", json!({})).unwrap();
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("category".into(), json!("Art & Illustration"));
d.fields.insert("preset".into(), json!("Postcard"));
let t = dialog_texts(&mut app);
assert!(shows(&t, "101.6 × 152.4 mm"), "Postcard in mm: {t:?}");
// Choosing Pixels: the cards show pixels and Postcard stays chosen.
app.ui.dialog.as_mut().unwrap().fields.insert("units".into(), json!("Pixels"));
let t = dialog_texts(&mut app);
assert!(shows(&t, "288 × 432 px") && !shows(&t, "101.6 × 152.4 mm"), "Postcard in px: {t:?}");
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["preset"], json!("Postcard"));
}