mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-22 19:08:39 +00:00
Area Type Options > Align: Top / Center / Bottom / Justify (AreaOptions.verticalAlign, LayoutOptions.vertical_align). Each row/column cell is aligned on its own; rectangular cells shift their lines, other frames and text wrap reflow until settled without losing text. Justify shares the leftover space equally between line gaps. Area Type Options shows Align and First Baseline as dropdowns (the parameter dialog's choices table).
111 lines
4.9 KiB
Rust
111 lines
4.9 KiB
Rust
//! The generic parameter dialog (`ui.paramDialog`): edits a command's parameters (`__command`,
|
||
//! headed `__label`) and runs it on OK.
|
||
|
||
use serde_json::Value;
|
||
|
||
use super::{DialogSpec, form};
|
||
use crate::VectorcraftApp;
|
||
use crate::state::Dialog;
|
||
|
||
pub(super) const SPEC: DialogSpec = DialogSpec {
|
||
heading: |d| tl!(&d.str("__label")).to_string(),
|
||
body: |app, ui, d| {
|
||
let command = d.str("__command");
|
||
let lengths = lengths(&command);
|
||
form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), app.session.general_unit());
|
||
false
|
||
},
|
||
confirm,
|
||
..DialogSpec::FORM
|
||
};
|
||
|
||
/// The parameters of the commands this dialog edits that are distances.
|
||
fn lengths(command: &str) -> &'static [&'static str] {
|
||
match command {
|
||
"graph.create" => &["width", "height"],
|
||
"shape.flare" => &["diameter", "pathLength"],
|
||
"artboard.rearrange" => &["spacing"],
|
||
"perspective.grid.set" => &["cell", "distance"],
|
||
"object.repeat.options" => &["radius", "hSpacing", "vSpacing"],
|
||
"text.areaOptions" => &["width", "height", "gutter", "inset", "firstBaselineMin"],
|
||
"type.pathOptions" => &["spacing"],
|
||
_ => &[],
|
||
}
|
||
}
|
||
|
||
/// The parameters of the commands this dialog edits that pick one of some values.
|
||
fn choices(command: &str, key: &str) -> Option<form::Choices> {
|
||
match (command, key) {
|
||
("type.pathOptions", "effect") => Some(crate::menus::PATH_EFFECTS),
|
||
("type.pathOptions", "alignToPath") => Some(PATH_ALIGN),
|
||
("text.areaOptions", "firstBaseline") => Some(FIRST_BASELINE),
|
||
("text.areaOptions", "verticalAlign") => Some(VERTICAL_ALIGN),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Type on a Path Options › Align to Path.
|
||
const PATH_ALIGN: form::Choices = &[("Ascender", "ascender"), ("Descender", "descender"), ("Center", "center"), ("Baseline", "baseline")];
|
||
|
||
/// Area Type Options › First Baseline.
|
||
const FIRST_BASELINE: form::Choices =
|
||
&[("Ascent", "ascent"), ("Cap Height", "capHeight"), ("x Height", "xHeight"), ("Leading", "leading"), ("Fixed", "fixed")];
|
||
|
||
/// Area Type Options › Align (vertical alignment of the lines in each row/column).
|
||
const VERTICAL_ALIGN: form::Choices = &[("Top", "top"), ("Center", "center"), ("Bottom", "bottom"), ("Justify", "justify")];
|
||
|
||
/// Closes before running, so a dialog the command opens stays open.
|
||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||
let cmd = d.str("__command");
|
||
let params = form::params(d);
|
||
app.ui.dialog = None;
|
||
// A tool's click-to-size shape (Flare) goes on the active perspective plane while the grid shows.
|
||
let at = |x: &str, y: &str| Some(vectorcraft_geom::Point::new(params.get(x)?.as_f64()?, params.get(y)?.as_f64()?));
|
||
if let Some((c, p)) =
|
||
at("cx", "cy").or_else(|| at("x", "y")).and_then(|pt| vectorcraft_engine::perspective_click(&app.session, &cmd, ¶ms, pt))
|
||
{
|
||
return app.run(&c, p);
|
||
}
|
||
app.run(&cmd, params)
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use serde_json::json;
|
||
use vectorcraft_doc::{NodeKind, VerticalAlign};
|
||
use vectorcraft_engine::Session;
|
||
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn area_type_options_choices_match_the_params_doc() {
|
||
let doc = vectorcraft_engine::find_command("text.areaOptions").unwrap().params;
|
||
for key in ["firstBaseline", "verticalAlign"] {
|
||
let values: Vec<&str> = choices("text.areaOptions", key).unwrap().iter().map(|(_, v)| *v).collect();
|
||
assert!(doc.contains(&format!("{key}?: {}", values.join("|"))), "{key}: {values:?}");
|
||
}
|
||
assert!(choices("text.areaOptions", "firstBaselineMin").is_none(), "a distance, not a choice");
|
||
assert!(choices("text.areaOptions", "inset").is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn area_type_options_dialog_sets_the_vertical_alignment() {
|
||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
||
let id = app.run("text.create", json!({"x": 20, "y": 20, "text": "Hello", "area": {"width": 200, "height": 200}})).unwrap()["id"]
|
||
.as_u64()
|
||
.unwrap();
|
||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||
crate::menus::invoke(&mut app, "text.areaOptions", json!({}));
|
||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("verticalAlign"), "top");
|
||
let ctx = egui::Context::default();
|
||
crate::theme::install_fonts(&ctx);
|
||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(&mut app, ui.ctx()));
|
||
out.textures_delta.clear();
|
||
app.ui.dialog.as_mut().unwrap().fields.insert("verticalAlign".into(), json!("center"));
|
||
crate::dialogs::confirm(&mut app).unwrap();
|
||
let NodeKind::Text(t) = &app.session.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().kind else { panic!("text") };
|
||
assert_eq!(t.area.vertical_align, VerticalAlign::Center);
|
||
}
|
||
}
|