diff --git a/crates/engine/src/cmd/blocks.rs b/crates/engine/src/cmd/blocks.rs index 19543141e..c25b7c913 100644 --- a/crates/engine/src/cmd/blocks.rs +++ b/crates/engine/src/cmd/blocks.rs @@ -17,6 +17,7 @@ use super::{CommandSpec, bad, bool_or, f64_or, find_command, ok, point_param, po use super::has_doc; use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step}; +mod attdef; mod attedit; mod burst; mod description; @@ -45,7 +46,7 @@ pub fn specs() -> Vec { .menu(&["Draw", "Block", "Define Attributes..."]) .alias(&["att", "-attdef"]) .params("{tag, prompt?, default?, at, height?, invisible?}") - .interactive(|_| Ok(Box::new(AttdefM::default()))), + .interactive(|_| Ok(Box::new(attdef::AttdefM::default()))), attedit::attedit_spec(), attedit::dash_attedit_spec(), CommandSpec::new("wblock", "Write Block", run_wblock).alias(&["w"]).params("{path, name? | handles?, base?}"), @@ -983,52 +984,6 @@ impl Interactive for InsertM { } } -#[derive(Default)] -struct AttdefM { - tag: Option, - prompt: Option, - default: Option, -} - -impl Interactive for AttdefM { - fn name(&self) -> &'static str { - "ATTDEF" - } - fn prompt(&self, _s: &Session) -> Prompt { - match (&self.tag, &self.prompt, &self.default) { - (None, ..) => Prompt::new("Enter attribute tag name", Accept::TEXT), - (Some(_), None, _) => Prompt::new("Enter attribute prompt", Accept::TEXT), - (Some(_), Some(_), None) => Prompt::new("Enter default attribute value", Accept::TEXT), - _ => Prompt::new("Specify start point of text", Accept::POINT), - } - } - fn input(&mut self, s: &mut Session, i: Input) -> Result { - let text = match &i { - Input::Text(t) => Some(t.clone()), - Input::Enter => Some(String::new()), - _ => None, - }; - match (&self.tag, &self.prompt, &self.default) { - (None, ..) => { - if let Some(t) = text.filter(|t| !t.trim().is_empty() && !t.contains(' ')) { - self.tag = Some(t.to_ascii_uppercase()); - } - } - (Some(_), None, _) => self.prompt = text, - (Some(_), Some(_), None) => self.default = text, - (Some(tag), Some(pr), Some(def)) => { - if let Input::Point(p) = i { - let h = s.doc()?.header.f64("TEXTSIZE", 0.2); - let k = attdef_kind(s, tag, pr, def, p, h, false); - s.add_entity(k)?; - return Ok(Step::Done); - } - } - } - Ok(Step::Continue) - } -} - mod attribs; mod battman; mod blockreplace; diff --git a/crates/engine/src/cmd/blocks/attdef.rs b/crates/engine/src/cmd/blocks/attdef.rs new file mode 100644 index 000000000..0803cfe95 --- /dev/null +++ b/crates/engine/src/cmd/blocks/attdef.rs @@ -0,0 +1,293 @@ +//! -ATTDEF at the command line, as AutoCAD 2024 runs it (accoreconsole): the attribute modes +//! (kept in AFLAGS), the tag, prompt and default value (a constant attribute's value instead), +//! then the text's point, justification, style, height and rotation. + +use cadcraft_doc::{AttribModes, HAlign, VAlign}; + +use super::{Accept, Attrib, EntityKind, Input, Interactive, Prompt, Result, Session, Step, Text, Vec2, number, v3}; +use crate::cmd::curves::KW; +use crate::cmd::draw::justify; + +/// AFLAGS bits, as AutoCAD numbers the modes. +const INVISIBLE: i64 = 1; +const CONSTANT: i64 = 2; +const VERIFY: i64 = 4; +const PRESET: i64 = 8; +const LOCK: i64 = 16; +const MULTILINE: i64 = 32; + +const MODES: [&str; 7] = ["Invisible", "Constant", "Verify", "Preset", "Lock position", "Annotative", "Multiple lines"]; +const JUSTIFY: [&str; 15] = ["Left", "Center", "Right", "Align", "Middle", "Fit", "TL", "TC", "TR", "ML", "MC", "MR", "BL", "BC", "BR"]; + +/// Where -ATTDEF is in its prompts. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +enum Ask { + #[default] + Modes, + Tag, + Prompt, + Value, + Point, + Justify, + Style, + Height, + Rotation, +} + +pub(super) struct AttdefM { + ask: Ask, + flags: i64, + tag: String, + prompt: String, + value: String, + justify: (HAlign, VAlign), + style: String, + at: Vec2, + height: f64, +} + +impl Default for AttdefM { + fn default() -> Self { + AttdefM { + ask: Ask::Modes, + flags: LOCK, + tag: String::new(), + prompt: String::new(), + value: String::new(), + justify: (HAlign::Left, VAlign::Baseline), + style: "Standard".into(), + at: Vec2::ZERO, + height: 0.0, + } + } +} + +impl AttdefM { + fn echo_modes(&self, s: &mut Session) { + let yn = |bit: i64| if self.flags & bit != 0 { "Y" } else { "N" }; + s.echo("Current attribute modes:"); + s.echo(format!( + "Invisible={} Constant={} Verify={} Preset={} Lock position={} Annotative=N Multiple line={}", + yn(INVISIBLE), + yn(CONSTANT), + yn(VERIFY), + yn(PRESET), + yn(LOCK), + yn(MULTILINE) + )); + } + + fn echo_style(&self, s: &mut Session) -> Result<()> { + let th = s.doc()?.header.f64("TEXTSIZE", 0.2); + s.echo(format!("Current text style: \"{}\" Text height: {th:.4} Justify: {}", self.style, justify_name(self.justify))); + Ok(()) + } + + fn point_prompt(&self) -> &'static str { + match self.justify { + (HAlign::Center, VAlign::Baseline) => "Specify center point of text", + (HAlign::Right, VAlign::Baseline) => "Specify right endpoint of text baseline", + (HAlign::Middle, _) | (HAlign::Center, VAlign::Middle) => "Specify middle point of text", + (HAlign::Left, VAlign::Top) => "Specify top-left point of text", + (HAlign::Center, VAlign::Top) => "Specify top-center point of text", + (HAlign::Right, VAlign::Top) => "Specify top-right point of text", + (HAlign::Left, VAlign::Middle) => "Specify middle-left point of text", + (HAlign::Right, VAlign::Middle) => "Specify middle-right point of text", + (HAlign::Left, VAlign::Bottom) => "Specify bottom-left point of text", + (HAlign::Center, VAlign::Bottom) => "Specify bottom-center point of text", + (HAlign::Right, VAlign::Bottom) => "Specify bottom-right point of text", + _ => "Specify start point of text", + } + } + + fn finish(&mut self, s: &mut Session, rotation: f64) -> Result { + let (h, v) = self.justify; + let left = h == HAlign::Left && v == VAlign::Baseline; + s.add_entity(EntityKind::AttDef(Attrib { + tag: self.tag.to_uppercase(), + text: Text { + insert: v3(self.at), + align_pt: if left { None } else { Some(v3(self.at)) }, + height: self.height, + value: std::mem::take(&mut self.value), + rotation, + width_factor: 1.0, + oblique: 0.0, + style: self.style.clone(), + halign: h, + valign: v, + }, + invisible: self.flags & INVISIBLE != 0, + constant: self.flags & CONSTANT != 0, + prompt: std::mem::take(&mut self.prompt), + props: Default::default(), + multiline: None, + modes: AttribModes { verify: self.flags & VERIFY != 0, preset: self.flags & PRESET != 0, lock_position: self.flags & LOCK != 0 }, + }))?; + Ok(Step::Done) + } +} + +/// The justification's name in "Current text style: … Justify: …". +fn justify_name(j: (HAlign, VAlign)) -> &'static str { + JUSTIFY.into_iter().find(|k| justify(k) == Some(j)).unwrap_or("Left") +} + +impl Interactive for AttdefM { + fn name(&self) -> &'static str { + "ATTDEF" + } + fn begin(&mut self, s: &mut Session) -> Result { + let d = s.doc()?; + self.flags = d.header.i64("AFLAGS", LOCK); + self.style = d.header.str("TEXTSTYLE", "Standard"); + self.echo_modes(s); + Ok(Step::Continue) + } + fn prompt(&self, s: &Session) -> Prompt { + match self.ask { + Ask::Modes => Prompt::new("Enter an option to change", KW).kw(&MODES).default("done"), + Ask::Tag => Prompt::new("Enter attribute tag name", Accept::TEXT), + Ask::Prompt => Prompt::new("Enter attribute prompt", Accept::TEXT), + Ask::Value if self.flags & CONSTANT != 0 => Prompt::new("Enter attribute value", Accept::TEXT), + Ask::Value => Prompt::new("Enter default attribute value", Accept::TEXT), + // Once a justification is chosen AutoCAD asks for its point alone. + Ask::Point if self.justify == (HAlign::Left, VAlign::Baseline) => { + Prompt::new(self.point_prompt(), Accept::POINT).kw(&["Justify", "Style"]) + } + Ask::Point => Prompt::new(self.point_prompt(), Accept::POINT), + Ask::Justify => Prompt::new("Enter an option", Accept::TEXT).kw(&JUSTIFY), + Ask::Style => Prompt::new("Enter style name", Accept::TEXT).kw(&["?"]).default(self.style.clone()), + Ask::Height => { + let th = s.doc().map(|d| d.header.f64("TEXTSIZE", 0.2)).unwrap_or(0.2); + Prompt::new("Specify height", Accept::POINT_OR_NUMBER).default(format!("{th:.4}")).base(self.at) + } + Ask::Rotation => Prompt::new("Specify rotation angle of text", Accept::POINT_OR_NUMBER).default("0").base(self.at), + } + } + fn input(&mut self, s: &mut Session, i: Input) -> Result { + match self.ask { + Ask::Modes => match &i { + Input::Keyword(k) => { + let bit = match k.as_str() { + "Invisible" => INVISIBLE, + "Constant" => CONSTANT, + "Verify" => VERIFY, + "Preset" => PRESET, + "Lock position" => LOCK, + _ => { + s.echo(format!("{k} attributes are not available yet.")); + return Ok(Step::Continue); + } + }; + self.flags ^= bit; + s.doc_mut()?.header.set_i64("AFLAGS", self.flags); + self.echo_modes(s); + } + Input::Enter => self.ask = Ask::Tag, + Input::Text(_) => s.echo("Invalid option keyword."), + _ => {} + }, + Ask::Tag => match &i { + // AutoCAD takes spaces in a tag here, and upper-cases it. + Input::Text(t) if !t.trim().is_empty() => { + self.tag = t.trim().to_string(); + self.ask = if self.flags & CONSTANT != 0 { Ask::Value } else { Ask::Prompt }; + } + Input::Text(_) | Input::Enter => s.echo("Tag cannot be null"), + _ => {} + }, + Ask::Prompt | Ask::Value => { + let t = match i { + Input::Text(t) => t, + Input::Enter => String::new(), + _ => return Ok(Step::Continue), + }; + if self.ask == Ask::Prompt { + self.prompt = t; + self.ask = Ask::Value; + } else { + self.value = t; + self.echo_style(s)?; + self.ask = Ask::Point; + } + } + Ask::Point => match i { + Input::Point(p) => { + self.at = p; + self.ask = Ask::Height; + } + Input::Keyword(k) if k == "Justify" => self.ask = Ask::Justify, + Input::Keyword(_) => self.ask = Ask::Style, + _ => {} + }, + Ask::Justify => { + // A justification goes on to its point; anything else is asked again. + if let Input::Keyword(k) | Input::Text(k) = &i { + match justify(k) { + // Aligned and fitted text take two points: not here yet. + Some((HAlign::Aligned | HAlign::Fit, _)) => s.echo("Align and Fit are not available here yet."), + Some(j) => { + self.justify = j; + self.ask = Ask::Point; + } + None => s.echo("Invalid option keyword."), + } + } + } + Ask::Style => { + let name = match i { + Input::Enter => self.style.clone(), + Input::Text(t) | Input::Keyword(t) => t.trim().to_string(), + _ => return Ok(Step::Continue), + }; + if name == "?" { + let names: Vec = s.doc()?.text_styles.iter().map(|st| st.name.clone()).collect(); + s.echo(format!("Text styles: {}", names.join(", "))); + return Ok(Step::Continue); + } + match s.doc()?.text_style(&name).map(|st| st.name.clone()) { + Some(n) => { + self.style = n; + self.echo_style(s)?; + self.ask = Ask::Point; + } + None => s.echo(format!("Cannot find text style \"{name}\".")), + } + } + Ask::Height => { + let th = s.doc()?.header.f64("TEXTSIZE", 0.2); + let h = match &i { + Input::Enter => th, + Input::Point(p) => self.at.dist(*p), + // A distance, as TEXT takes its height (1'6" in feet and inches, too). + Input::Text(t) => number(t).unwrap_or(0.0), + _ => return Ok(Step::Continue), + }; + if !(h.is_finite() && h > 0.0) { + s.echo("Requires a positive height."); + return Ok(Step::Continue); + } + self.height = h; + s.doc_mut()?.header.set_f64("TEXTSIZE", h); + self.ask = Ask::Rotation; + } + Ask::Rotation => { + let r = match &i { + Input::Enter => 0.0, + Input::Point(p) => self.at.angle_to(*p), + Input::Text(t) => match s.angle_settings().direction(t) { + Some(a) => a, + None => { + s.echo("Requires an angle."); + return Ok(Step::Continue); + } + }, + _ => return Ok(Step::Continue), + }; + return self.finish(s, r); + } + } + Ok(Step::Continue) + } +} diff --git a/crates/engine/src/cmd/draw.rs b/crates/engine/src/cmd/draw.rs index 1b0e0c1cf..cebc44e6f 100644 --- a/crates/engine/src/cmd/draw.rs +++ b/crates/engine/src/cmd/draw.rs @@ -33,6 +33,7 @@ use point::{DonutM, PointM, RayM, run_donut, run_point, run_ray}; use polygon::{PolygonM, run_polygon}; use solid::{SolidM, run_solid}; use spline::{SplineM, run_spline}; +pub(crate) use text::justify; use text::{MTextM, TextM, run_mtext, run_text}; /// Every draw command's spec: JSON parameters, menu path, aliases and the interactive prompt diff --git a/crates/engine/tests/it/attdef_command.rs b/crates/engine/tests/it/attdef_command.rs new file mode 100644 index 000000000..7bbec7c37 --- /dev/null +++ b/crates/engine/tests/it/attdef_command.rs @@ -0,0 +1,186 @@ +//! -ATTDEF at the command line, as AutoCAD 2024 runs it (accoreconsole: these scripts with +//! TEXTSIZE 2.5 and AFLAGS 16, the drawing and AFLAGS read back afterwards). + +use cadcraft_engine::doc::{Attrib, EntityKind, HAlign, VAlign}; +use cadcraft_engine::{Session, sysvars}; +use serde_json::json; + +fn session() -> Session { + let mut s = Session::new(); + s.execute("setvar", &json!({ "name": "TEXTSIZE", "value": 2.5 })).unwrap(); + s.execute("setvar", &json!({ "name": "AFLAGS", "value": 16 })).unwrap(); + s +} + +fn attdefs(s: &Session) -> Vec { + s.doc().unwrap().model.iter().filter_map(|e| if let EntityKind::AttDef(a) = &e.kind { Some(a.clone()) } else { None }).collect() +} + +fn aflags(s: &Session) -> i64 { + sysvars::get(s, "AFLAGS").and_then(|v| v.as_f64()).unwrap() as i64 +} + +fn run(script: &str) -> Session { + let mut s = session(); + s.cmdline(script).unwrap(); + assert!(s.running.is_none(), "{script}: {}", s.prompt_text()); + s +} + +#[test] +fn modes_are_toggled_kept_in_aflags_and_stored_on_the_definition() { + // (script, AFLAGS after, invisible, constant, verify, preset, lock position, tag, prompt, value) + type Case = (&'static str, i64, [bool; 5], &'static str, &'static str, &'static str); + let cases: [Case; 5] = [ + // ATTDEF-000: the modes as they are. + ( + "(command \"_.-ATTDEF\" \"\" \"T1\" \"Nhap ten\" \"MAC\" '(5.0 10.0) 2.5 0)", + 16, + [false, false, false, false, true], + "T1", + "Nhap ten", + "MAC", + ), + // ATTDEF-001: Invisible and Verify. + ( + "(command \"_.-ATTDEF\" \"_I\" \"_V\" \"\" \"T2\" \"Hoi hai\" \"D2\" '(5.0 10.0) 3.0 30)", + 21, + [true, false, true, false, true], + "T2", + "Hoi hai", + "D2", + ), + // ATTDEF-002: Constant asks for the value, not a prompt and a default. + ("(command \"_.-ATTDEF\" \"_C\" \"\" \"T3\" \"GIATRI\" '(5.0 10.0) 2.5 0)", 18, [false, true, false, false, true], "T3", "", "GIATRI"), + // ATTDEF-003: Preset. + ( + "(command \"_.-ATTDEF\" \"_P\" \"\" \"T4\" \"Hoi bon\" \"D4\" '(612350.678 2398775.432) 2.5 0)", + 24, + [false, false, false, true, true], + "T4", + "Hoi bon", + "D4", + ), + // ATTDEF-004: Lock position off. + ( + "(command \"_.-ATTDEF\" \"_L\" \"\" \"T5\" \"Hoi nam\" \"D5\" '(5.0 10.0) 2.5 0)", + 0, + [false, false, false, false, false], + "T5", + "Hoi nam", + "D5", + ), + ]; + for (script, flags, [inv, con, ver, pre, lock], tag, prompt, value) in cases { + let s = run(script); + assert_eq!(aflags(&s), flags, "{script}"); + let a = attdefs(&s).remove(0); + assert_eq!((a.invisible, a.constant, a.modes.verify, a.modes.preset, a.modes.lock_position), (inv, con, ver, pre, lock), "{script}"); + assert_eq!((a.tag.as_str(), a.prompt.as_str(), a.text.value.as_str()), (tag, prompt, value), "{script}"); + } + // ATTDEF-001's height 3 and 30 degrees. + let a = attdefs(&run(cases[1].0)).remove(0); + assert!((a.text.height - 3.0).abs() < 1e-12 && (a.text.rotation - 30f64.to_radians()).abs() < 1e-12, "{a:?}"); +} + +#[test] +fn tags_justification_and_style_as_autocad_takes_them() { + // ATTDEF-021, ATTDEF-022: a tag is upper-cased, spaces and all. + for (typed, tag) in [("maso", "MASO"), ("ten ban ve", "TEN BAN VE")] { + let s = run(&format!("(command \"_.-ATTDEF\" \"\" \"{typed}\" \"Hoi\" \"D\" '(5.0 10.0) 2.5 0)")); + assert_eq!(attdefs(&s)[0].tag, tag); + } + // ATTDEF-023: an empty prompt stays empty. + let s = run("(command \"_.-ATTDEF\" \"\" \"T10\" \"\" \"D10\" '(612350.678 2398775.432) 2.5 0)"); + assert_eq!(attdefs(&s)[0].prompt, ""); + // ATTDEF-025: an empty tag is refused and asked again. + let s = run("(command \"_.-ATTDEF\" \"\" \"\" \"T11\" \"Hoi muoi mot\" \"D11\" '(5.0 10.0) 2.5 0)"); + assert!(s.log.iter().any(|l| l == "Tag cannot be null"), "{:?}", s.log); + assert_eq!(attdefs(&s)[0].tag, "T11"); + // ATTDEF-005: Justify MC at the middle point. + let s = run("(command \"_.-ATTDEF\" \"\" \"T6\" \"Hoi sau\" \"D6\" \"_J\" \"_MC\" '(5.0 10.0) 2.5 0)"); + let a = attdefs(&s).remove(0); + assert_eq!((a.text.halign, a.text.valign), (HAlign::Center, VAlign::Middle)); + assert!(a.text.align_pt.is_some_and(|p| p.x == 5.0 && p.y == 10.0), "{a:?}"); + // ATTDEF-006: Style, then the point. + let s = run("(command \"_.-ATTDEF\" \"\" \"T7\" \"Hoi bay\" \"D7\" \"_S\" \"Standard\" '(0.505 0.26) 2.5 0)"); + let a = attdefs(&s).remove(0); + assert!(a.text.style == "Standard" && a.text.insert.x == 0.505 && a.text.insert.y == 0.26, "{a:?}"); +} + +#[test] +fn the_prompts_are_autocads() { + let mut s = session(); + s.cmdline("-attdef").unwrap(); + assert!(s.log.iter().any(|l| l == "Current attribute modes:"), "{:?}", s.log); + assert_eq!(s.log.last().map(String::as_str), Some("Invisible=N Constant=N Verify=N Preset=N Lock position=Y Annotative=N Multiple line=N")); + assert!(s.prompt_text().contains("[Invisible/Constant/Verify/Preset/Lock position/Annotative/Multiple lines] :"), "{}", s.prompt_text()); + s.cmdline("X").unwrap(); + assert_eq!(s.log.last().map(String::as_str), Some("Invalid option keyword.")); + s.cmdline("_C").unwrap(); + assert_eq!(s.log.last().map(String::as_str), Some("Invisible=N Constant=Y Verify=N Preset=N Lock position=Y Annotative=N Multiple line=N")); + s.cmdline("").unwrap(); + assert!(s.prompt_text().ends_with("Enter attribute tag name:"), "{}", s.prompt_text()); + s.cmdline("T3").unwrap(); + assert!(s.prompt_text().ends_with("Enter attribute value:"), "{}", s.prompt_text()); + s.cmdline("GIATRI").unwrap(); + assert_eq!(s.log.last().map(String::as_str), Some("Current text style: \"Standard\" Text height: 2.5000 Justify: Left")); + assert!(s.prompt_text().ends_with("Specify start point of text or [Justify/Style]:"), "{}", s.prompt_text()); + s.cmdline("_J").unwrap(); + // A wrong answer, or a justification not available here, is asked again. + for typed in ["X", "_A"] { + s.cmdline(typed).unwrap(); + assert!(s.prompt_text().contains("[Left/Center/Right/Align/Middle/Fit/TL/"), "{typed}: {}", s.prompt_text()); + } + s.cmdline("_MC").unwrap(); + assert!(s.prompt_text().ends_with("Specify middle point of text:"), "{}", s.prompt_text()); + s.cmdline("0,0").unwrap(); + assert!(s.prompt_text().ends_with("Specify height <2.5000>:"), "{}", s.prompt_text()); + s.cmdline("").unwrap(); + assert!(s.prompt_text().ends_with("Specify rotation angle of text <0>:"), "{}", s.prompt_text()); + s.cancel(); +} + +#[test] +fn a_constant_attribute_stays_in_the_block() { + // INSERTATT-011: the constant TC is no attribute of the reference; the other one, TN, is. + let mut s = session(); + s.execute("line", &json!({ "points": [[612345.678, 2398765.432], [612355.678, 2398765.432]] })).unwrap(); + s.cmdline("(setq hl_A (entlast))").unwrap(); + s.cmdline("(progn (command \"_.-ATTDEF\" \"_C\" \"\" \"TC\" \"HANG\" '(612350.678 2398775.432) 2.5 0) (setq hl_a1 (entlast)))").unwrap(); + s.cmdline("(setvar \"AFLAGS\" 16)").unwrap(); + s.cmdline("(progn (command \"_.-ATTDEF\" \"\" \"TN\" \"Hoi N\" \"MN\" '(612350.678 2398785.432) 2.5 0) (setq hl_a2 (entlast)))").unwrap(); + s.cmdline("(command \"_.-BLOCK\" \"B11\" '(612345.678 2398765.432) hl_a1 hl_a2 hl_A \"\")").unwrap(); + s.cmdline("(command \"_.-INSERT\" \"B11\" '(612445.678 2398815.432) 1 1 0 \"XN\")").unwrap(); + assert!(s.running.is_none(), "{}", s.prompt_text()); + let ins = s.doc().unwrap().model.iter().find_map(|e| if let EntityKind::Insert(i) = &e.kind { Some(i.clone()) } else { None }).unwrap(); + let tags: Vec<(String, String)> = ins.attribs.iter().map(|a| (a.tag.clone(), a.text.value.clone())).collect(); + assert_eq!(tags, [("TN".to_string(), "XN".to_string())]); +} + +#[test] +fn a_typed_height_is_a_distance() { + let mut s = session(); + for typed in ["-attdef", "", "T1", "P", "V", "5,10", "x"] { + s.cmdline(typed).unwrap(); + } + assert_eq!(s.log.last().map(String::as_str), Some("Requires a positive height.")); + // Feet and inches, as TEXT takes them. + for typed in ["1'6\"", "0"] { + s.cmdline(typed).unwrap(); + } + assert!(s.running.is_none(), "{}", s.prompt_text()); + assert_eq!(attdefs(&s)[0].text.height, 18.0); +} + +#[test] +fn the_modes_start_from_aflags() { + // AFLAGS 0, not the default 16: Invisible toggled on gives 1, and nothing else. + let mut s = session(); + s.execute("setvar", &json!({ "name": "AFLAGS", "value": 0 })).unwrap(); + s.cmdline("(command \"_.-ATTDEF\" \"_I\" \"\" \"T1\" \"Hoi\" \"D\" '(5.0 10.0) 2.5 0)").unwrap(); + assert!(s.running.is_none(), "{}", s.prompt_text()); + assert_eq!(aflags(&s), 1); + let a = attdefs(&s).remove(0); + assert_eq!((a.invisible, a.modes.lock_position), (true, false)); +} diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs index 0763ee6eb..88cbefe31 100644 --- a/crates/engine/tests/it/main.rs +++ b/crates/engine/tests/it/main.rs @@ -5,6 +5,7 @@ mod annotative; mod arc_ctrl_direction; mod array_commands; mod assoc_array; +mod attdef_command; mod attdisp_attsync; mod attedit_prompts; mod attrib_modes;