//! Current attributes (the indicator panel). use super::{always, CmdResult, Command}; use crate::{Engine, EngineError, Indicator, UpdateFlag}; use serde_json::{json, Value}; pub(super) fn commands() -> Vec { vec![ Command { id: "indicator.get", label: "Get current attributes", menu: "", shortcut: "", params: "{}", mutates: false, enabled: always, run: |e, _| serde_json::to_value(&e.indicator).map_err(|err| EngineError::internal(err.to_string())), }, Command { id: "indicator.set", label: "Set current attributes", menu: "", shortcut: "", params: "{any indicator field: line, thickness, pen_color, fill_color, area_fill, depth, style_val, cap, join, forward_arrow, backward_arrow, arrow, font, font_size, local_font (string|null), postscript, justify, text_angle, box_radius, dot_gap, sides, rotate_angle, grid, snap, align_h, align_v, pie_arc, text_step, tangent_length (0-20), linkmode: off|move|slide, angle_geom: unconstrained|latex_line|latex_arrow|mount_hattan|manhattan|mountain}", mutates: false, enabled: always, run: set, }, Command { id: "indicator.set_update_flag", label: "Set update flag", menu: "", shortcut: "", params: "{\"attr\": text_just|font|font_size|pen_color|fill_color|fill_style|line_style|join_style|cap_style|line_width|depth|text_angle|text_flags|arrow_mode|arrow_type|arrow_size|box_radius|arc_type, \"on\"?: bool (default: toggle)} — whether Update (and update region, update settings) copies this attribute (xfig's update box on the attribute button)", mutates: false, enabled: always, run: set_update_flag, }, Command { id: "indicator.update_flags", label: "Update control", menu: "", shortcut: "", params: "{\"all\": true | false | \"toggle\"} — set, clear or toggle every update flag (xfig's Update Control buttons); returns the flags that are on", mutates: false, enabled: always, run: update_flags, }, Command { id: "indicator.reset", label: "Reset current attributes", menu: "", shortcut: "", params: "{}", mutates: false, enabled: always, run: |e, _| { e.indicator = Indicator::default(); Ok(json!({})) }, }, ] } fn set(e: &mut Engine, p: &Value) -> CmdResult { let Value::Object(changes) = p else { return Err(EngineError::bad_params("expected an object of indicator fields")); }; // Choosing a Fig font drops the installed one, unless it is set too. let drop_local = (changes.contains_key("font") || changes.contains_key("postscript")) && !changes.contains_key("local_font"); let mut cur = serde_json::to_value(&e.indicator).map_err(|err| EngineError::internal(err.to_string()))?; let Value::Object(fields) = &mut cur else { return Err(EngineError::internal("indicator is not an object")); }; if drop_local { fields.insert("local_font".into(), Value::Null); } for (k, v) in changes { if !fields.contains_key(k) { return Err(EngineError::bad_params(format!("unknown indicator field {k:?}"))); } fields.insert(k.clone(), v.clone()); } let mut ind: Indicator = serde_json::from_value(cur).map_err(|err| EngineError::bad_params(format!("bad indicator value: {err}")))?; ind.thickness = ind.thickness.clamp(0, 10_000); ind.depth = ind.depth.clamp(0, rustfig_model::MAX_DEPTH); ind.area_fill = ind.area_fill.clamp(-1, 62); ind.sides = ind.sides.clamp(3, 1000); ind.dimline.sanitize(); // The Arrow Size panel takes 0.1 to 10000 (zero would be 1, as in xfig). let unit = rustfig_geom::FIG_PER_STYLE_UNIT; let fix = |v: f64, default: f64| if v.is_finite() { v.clamp(0.1, 10_000.0) } else { default }; ind.arrow_mult = ( fix(ind.arrow_mult.0, 1.0), fix(ind.arrow_mult.1, 4.0), fix(ind.arrow_mult.2, 8.0), ); ind.arrow.thickness = fix(ind.arrow.thickness, 1.0); ind.arrow.width = fix(ind.arrow.width / unit, 4.0) * unit; ind.arrow.height = fix(ind.arrow.height / unit, 8.0) * unit; ind.num_copies = ind.num_copies.clamp(1, 99); ind.num_x_copies = ind.num_x_copies.min(99); ind.num_y_copies = ind.num_y_copies.min(99); if !ind.font_size.is_finite() { ind.font_size = 12.0; } ind.font_size = ind.font_size.clamp(1.0, 2000.0); if !ind.text_angle.is_finite() { ind.text_angle = 0.0; } if !ind.rotate_angle.is_finite() { ind.rotate_angle = 90.0; } if !ind.style_val.is_finite() { ind.style_val = 4.0; } if !ind.tangent_length.is_finite() { ind.tangent_length = 1.0; } ind.tangent_length = ind.tangent_length.clamp(0.0, crate::cmd::tangent::MAX_LENGTH); if !ind.dot_gap.is_finite() { ind.dot_gap = 3.0; } e.indicator = ind; Ok(json!({})) } fn set_update_flag(e: &mut Engine, p: &Value) -> CmdResult { let name = p .get("attr") .and_then(Value::as_str) .ok_or_else(|| EngineError::bad_params("attr must be an update flag name"))?; let flag = UpdateFlag::from_name(name).ok_or_else(|| EngineError::bad_params(format!("unknown update flag {name:?}")))?; let on = match p.get("on") { None | Some(Value::Null) => !e.indicator.updates(flag), Some(Value::Bool(b)) => *b, Some(_) => return Err(EngineError::bad_params("on must be true or false")), }; e.indicator.set_update_flag(flag, on); Ok(json!({"attr": flag.name(), "on": on})) } fn update_flags(e: &mut Engine, p: &Value) -> CmdResult { match p.get("all") { Some(Value::Bool(true)) => { e.indicator.update_off.clear(); e.set_status("Update commands are now ENABLED for all buttons"); } Some(Value::Bool(false)) => { e.indicator.update_off = UpdateFlag::ALL.into_iter().collect(); e.set_status("Update commands will be IGNORED for all buttons"); } Some(Value::String(s)) if s == "toggle" => { e.indicator.update_off = UpdateFlag::ALL .into_iter() .filter(|f| e.indicator.updates(*f)) .collect(); e.set_status("Update command status TOGGLED for all buttons"); } _ => return Err(EngineError::bad_params("all must be true, false or \"toggle\"")), } let on: Vec = UpdateFlag::ALL .into_iter() .filter(|f| e.indicator.updates(*f)) .map(UpdateFlag::name) .collect(); Ok(json!({"on": on})) } .