Properties palette: every control acts or is visibly unavailable (#182)

* Properties palette: every control acts or is visibly unavailable (#6)

Several Properties palette controls looked clickable but did nothing: most
current-property rows with nothing selected (linetype, lineweight, thickness,
text/dimension/multileader/table style), the selection-type drop-down, a
circle's circumference and area, a line's length and angle, a dimension's
style, and the All/My switch.

- Nothing selected: Linetype runs `linetype {current}`, Lineweight `lweight`,
  Thickness `setvar THICKNESS`, the style rows `style.current`,
  `dimstyle.current`, `mleaderstyle` and `tablestyle`. Transparency,
  Annotation scale and Plot style(s) have no command yet: greyed with a
  "Not available yet" tooltip.
- Header: the type drop-down narrows the selection to one object type
  (`select {handles}`), plus Toggle PICKADD (`setvar PICKADD`), Select Objects
  (`select`) and Quick Select buttons, with icons drawn in code.
- Geometry: circle circumference/area, line length/angle and dimension style
  are editable through `properties.set` (new keys `circumference`, `area`,
  `dimStyle`); computed values (line deltas, polyline length/area, block
  name) are greyed read-only.
- PICKADD now changes clicking with no command running: off, each pick
  replaces the selection and Shift adds.
- Palette text fields keep typed text while the window itself is unfocused
  (they read focus from egui memory, not `Response::has_focus`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move palette tests away from other PRs' hunks to avoid merge conflicts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valentin Klein
2026-10-10 12:15:34 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 306ba991a5
commit 8d71f7de4a
5 changed files with 350 additions and 122 deletions
+13
View File
@@ -172,6 +172,9 @@ fn run_set(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(l) = str_param(p, "layer") {
d.ensure_layer(l);
}
let dim_style = str_param(p, "dimStyle")
.map(|n| d.dim_style(n).map(|st| st.name.clone()).ok_or_else(|| bad("properties.set", format!("dimension style `{n}` not found"))))
.transpose()?;
for h in &hs {
d.modify_entity(*h, |e| {
if let Some(l) = str_param(p, "layer") {
@@ -226,6 +229,12 @@ fn run_set(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(dm) = num("diameter").filter(|r| *r > 0.0) {
c.radius = dm / 2.0;
}
if let Some(cf) = num("circumference").filter(|x| *x > 0.0) {
c.radius = cf / cadcraft_geom::TAU;
}
if let Some(a) = num("area").filter(|x| *x > 0.0) {
c.radius = (a / cadcraft_geom::PI).sqrt();
}
}
EntityKind::Arc(a) => {
if let Some(v) = p.get("center") {
@@ -293,6 +302,10 @@ fn run_set(s: &mut Session, p: &Value) -> Result<Value> {
dm.text = v.to_string();
dm.block = None;
}
if let Some(st) = &dim_style {
dm.style.clone_from(st);
dm.block = None;
}
}
EntityKind::Hatch(h) => {
if let Some(v) = str_param(p, "pattern") {
+11 -7
View File
@@ -855,15 +855,18 @@ impl Session {
// ---------------- picking without a command ----------------
/// A click with no command running: pick/toggle objects or start a selection window.
///
/// PICKADD on (the default): picks add to the selection and Shift removes. PICKADD off: each pick
/// replaces the selection and Shift adds (Shift+pick on a selected object removes it).
pub fn idle_click(&mut self, p: Vec2, shift: bool) -> Result<()> {
let space = self.space();
let pickadd = self.settings.pickadd;
if let Some(pw) = self.pending_window.take() {
let crossing = p.x < pw.corner.x;
let hs = select::select_window(self.doc()?, &space, Bounds2::new(pw.corner, p), crossing);
let mut sel = if shift { Vec::new() } else { self.selection() };
if shift {
let cur = self.selection();
sel = cur.into_iter().filter(|h| !hs.contains(h)).collect();
let mut sel = if pickadd || shift { self.selection() } else { Vec::new() };
if shift && pickadd {
sel.retain(|h| !hs.contains(h));
} else {
sel.extend(hs);
}
@@ -873,10 +876,11 @@ impl Session {
let ap = self.pixel_size() * self.settings.pickbox.max(1.0) * 1.5;
match select::pick(self.doc()?, &space, p, ap) {
Some(h) => {
let mut sel = self.selection();
if shift {
let mut sel = if pickadd || shift { self.selection() } else { Vec::new() };
let had = sel.contains(&h);
if shift && (pickadd || had) {
sel.retain(|x| *x != h);
} else if !sel.contains(&h) {
} else if !had {
sel.push(h);
}
self.set_selection(sel);
+91
View File
@@ -183,6 +183,97 @@ fn extend_line_to_boundary() {
assert!(g[2].near(Vec2::new(8.0, 0.0), 1e-9));
}
#[test]
fn properties_set_circle_size_and_dim_style() {
// Issue #6: the Properties palette edits a circle's circumference/area and a dimension's style.
let mut s = Session::new();
s.execute("circle", &json!({"center": [0, 0], "radius": 1})).unwrap();
let c = s.doc().unwrap().model.last().unwrap().handle;
let radius = |s: &Session| match &s.doc().unwrap().entity(c).unwrap().kind {
EntityKind::Circle(c) => c.radius,
_ => f64::NAN,
};
s.execute("properties.set", &json!({"handles": [c.hex()], "circumference": 4.0 * std::f64::consts::PI})).unwrap();
assert!((radius(&s) - 2.0).abs() < 1e-12);
s.execute("properties.set", &json!({"handles": [c.hex()], "area": 9.0 * std::f64::consts::PI})).unwrap();
assert!((radius(&s) - 3.0).abs() < 1e-12);
// Zero, negative or non-numeric sizes are ignored.
for v in [json!(0), json!(-5), json!("NaN"), json!(null)] {
s.execute("properties.set", &json!({"handles": [c.hex()], "circumference": v, "area": v})).unwrap();
assert!((radius(&s) - 3.0).abs() < 1e-12, "{v}");
}
s.execute("properties.set", &json!({"handles": [c.hex()], "area": 1e308})).unwrap();
assert!(radius(&s).is_finite());
s.execute("dimstyle", &json!({"name": "Big", "current": false})).unwrap();
s.execute("dimlinear", &json!({"p1": [0, 0], "p2": [3, 8], "at": [6, 4]})).unwrap();
let dm = s.doc().unwrap().model.last().unwrap().handle;
let style = |s: &Session| match &s.doc().unwrap().entity(dm).unwrap().kind {
EntityKind::Dimension(d) => d.style.clone(),
_ => String::new(),
};
s.execute("properties.set", &json!({"handles": [dm.hex()], "dimStyle": "big"})).unwrap();
assert_eq!(style(&s), "Big");
assert!(s.execute("properties.set", &json!({"handles": [dm.hex()], "dimStyle": "Nope"})).is_err());
assert_eq!(style(&s), "Big");
}
#[test]
fn properties_palette_current_property_commands() {
// Issue #6: the commands the Properties palette runs with nothing selected.
let mut s = Session::new();
s.execute("linetype", &json!({"current": "ByBlock"})).unwrap();
s.execute("lweight", &json!({"lineweight": 0.5})).unwrap();
s.execute("setvar", &json!({"name": "THICKNESS", "value": 2.5})).unwrap();
s.execute("dimstyle", &json!({"name": "Big", "current": false})).unwrap();
s.execute("dimstyle.current", &json!({"name": "Big"})).unwrap();
s.execute("mleaderstyle", &json!({"name": "Callout", "current": false})).unwrap();
s.execute("mleaderstyle", &json!({"name": "Callout", "current": true})).unwrap();
s.execute("tablestyle", &json!({"name": "Schedule", "current": false})).unwrap();
s.execute("tablestyle", &json!({"name": "Schedule", "current": true})).unwrap();
s.execute("style", &json!({"name": "Notes", "current": false})).unwrap();
s.execute("style.current", &json!({"name": "Notes"})).unwrap();
let h = &s.doc().unwrap().header;
assert_eq!(h.str("CELTYPE", ""), "ByBlock");
assert_eq!(h.i64("CELWEIGHT", 0), 50);
assert_eq!(h.f64("THICKNESS", 0.0), 2.5);
assert_eq!(h.str("DIMSTYLE", ""), "Big");
assert_eq!(h.str("CMLEADERSTYLE", ""), "Callout");
assert_eq!(h.str("CTABLESTYLE", ""), "Schedule");
assert_eq!(h.str("TEXTSTYLE", ""), "Notes");
}
#[test]
fn pickadd_controls_idle_picks() {
// Issue #6: the palette's PICKADD toggle must change how clicks select.
let mut s = Session::new();
s.execute("line", &json!({"points": [[0, 0], [10, 0]]})).unwrap();
let a = s.doc().unwrap().model.last().unwrap().handle;
s.execute("line", &json!({"points": [[0, 5], [10, 5]]})).unwrap();
let b = s.doc().unwrap().model.last().unwrap().handle;
let (pa, pb) = (Vec2::new(5.0, 0.0), Vec2::new(5.0, 5.0));
// PICKADD on: picks add, Shift removes.
s.idle_click(pa, false).unwrap();
s.idle_click(pb, false).unwrap();
assert_eq!(s.selection(), vec![a, b]);
s.idle_click(pa, true).unwrap();
assert_eq!(s.selection(), vec![b]);
// PICKADD off: each pick replaces, Shift adds, Shift on a selected object removes it.
s.execute("setvar", &json!({"name": "PICKADD", "value": 0})).unwrap();
s.idle_click(pa, false).unwrap();
assert_eq!(s.selection(), vec![a]);
s.idle_click(pb, true).unwrap();
assert_eq!(s.selection(), vec![a, b]);
s.idle_click(pa, true).unwrap();
assert_eq!(s.selection(), vec![b]);
s.idle_click(pa, false).unwrap();
assert_eq!(s.selection(), vec![a]);
// A window replaces the selection too.
s.idle_click(Vec2::new(-1.0, 4.0), false).unwrap();
s.idle_click(Vec2::new(11.0, 6.0), false).unwrap();
assert_eq!(s.selection(), vec![b]);
}
#[test]
fn fillet_two_lines() {
let mut s = Session::new();
+19
View File
@@ -139,6 +139,9 @@ pub enum Icon {
Distance,
Area,
List,
PickAdd,
SelectObjects,
QuickSelect,
}
struct Pen<'a> {
@@ -883,6 +886,22 @@ pub fn paint(p: &Painter, rect: Rect, icon: Icon, dim: bool) {
pen.l(8.0, y, 20.0, y);
}
}
PickAdd => {
// An object box with a plus: picks add to the selection.
pen.rect(3.0, 3.0, 11.0, 11.0, b);
pen.la(17.0, 11.0, 17.0, 21.0);
pen.la(12.0, 16.0, 22.0, 16.0);
}
SelectObjects => {
// A pointer picking an object box.
pen.rect(12.0, 3.0, 9.0, 9.0, a);
pen.poly(&[(4.0, 6.0), (4.0, 20.0), (7.5, 16.5), (10.5, 21.5), (12.5, 20.5), (9.5, 15.5), (14.0, 15.0)], true, b);
}
QuickSelect => {
// A filter funnel with a spark.
pen.poly(&[(3.0, 4.0), (19.0, 4.0), (13.0, 11.0), (13.0, 19.0), (9.0, 17.0), (9.0, 11.0)], true, b);
pen.poly(&[(20.0, 10.0), (16.5, 15.5), (20.0, 15.5), (17.0, 21.0)], false, a);
}
}
}
+216 -115
View File
@@ -442,9 +442,12 @@ fn value_box(ui: &mut egui::Ui, text: &str, combo: bool, enabled: bool) -> egui:
fn edit_field(ui: &mut egui::Ui, id: egui::Id, value: String) -> Option<String> {
let mut buf = ui.data_mut(|d| d.get_temp::<String>(id)).unwrap_or_else(|| value.clone());
let w = ui.available_width() - 10.0;
let resp = ui.add_sized([w, 20.0], egui::TextEdit::singleline(&mut buf).id(id.with("te")).font(crate::theme::body()));
let te = id.with("te");
let resp = ui.add_sized([w, 20.0], egui::TextEdit::singleline(&mut buf).id(te).font(crate::theme::body()));
let mut out = None;
if resp.has_focus() {
// Keyboard focus from memory: `Response::has_focus` is false while the window itself is
// unfocused, which dropped the typed text every frame.
if ui.memory(|m| m.has_focus(te)) {
ui.data_mut(|d| d.insert_temp(id, buf.clone()));
} else {
ui.data_mut(|d| d.remove::<String>(id));
@@ -455,107 +458,215 @@ fn edit_field(ui: &mut egui::Ui, id: egui::Id, value: String) -> Option<String>
out
}
fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
/// A drop-down of `items` showing `current`; returns the item clicked.
fn pick_menu(ui: &mut egui::Ui, current: &str, items: impl IntoIterator<Item = String>) -> Option<String> {
let mut out = None;
ui.menu_button(current.to_string(), |ui| {
for it in items {
if ui.button(&it).clicked() {
out = Some(it);
ui.close();
}
}
});
out
}
/// A greyed value with no command behind it yet: visibly inert, never a silent dead button.
fn unavailable(ui: &mut egui::Ui, text: &str) {
value_box(ui, text, false, false).on_hover_text("Not available yet");
}
/// A greyed value computed from the geometry.
fn read_only(ui: &mut egui::Ui, text: &str) {
value_box(ui, text, false, false).on_hover_text("Read-only");
}
/// The Properties header: All/My, the selection-type filter and the PICKADD / Select Objects /
/// Quick Select buttons.
fn properties_header(app: &mut CadApp, ui: &mut egui::Ui) {
let t = Tokens::get();
let hr = section_header(ui, "Properties");
// All | My segmented control.
// All | My segmented control ("My" property sets don't exist yet).
let seg = Rect::from_min_size(pos2(hr.right() - 120.0, hr.top() + 4.0), vec2(70.0, 18.0));
ui.painter().rect_filled(seg, 3.0, t.chrome_dark);
let half = Rect::from_min_size(seg.min, vec2(35.0, 18.0));
ui.painter().rect_filled(if app.ui.properties_all { half } else { half.translate(vec2(35.0, 0.0)) }, 3.0, t.control);
let my = half.translate(vec2(35.0, 0.0));
ui.painter().rect_filled(if app.ui.properties_all { half } else { my }, 3.0, t.control);
ui.painter().text(half.center(), egui::Align2::CENTER_CENTER, "All", crate::theme::small(), t.text);
ui.painter().text(half.center() + vec2(35.0, 0.0), egui::Align2::CENTER_CENTER, "My", crate::theme::small(), t.text);
ui.painter().text(my.center(), egui::Align2::CENTER_CENTER, "My", crate::theme::small(), t.text_faint);
ui.interact(my, ui.id().with("prop_my"), Sense::hover()).on_hover_text("My properties: not available yet");
let sel = app.session.selection();
let pickadd = app.session.settings.pickadd;
let Ok(d) = app.session.doc() else { return };
// Selection type combo.
let types: Vec<&'static str> = sel.iter().filter_map(|h| d.entity(*h).map(|e| e.kind.type_name())).collect();
let header = match types.len() {
0 => "No selection".to_string(),
1 => types.first().map(|s| s.to_string()).unwrap_or_default(),
n => {
let first = types.first().copied().unwrap_or("");
if types.iter().all(|t| *t == first) { format!("{first} ({n})") } else { format!("All ({n})") }
// Selected handles by type, in first-seen order.
let mut types: Vec<(&'static str, Vec<String>)> = Vec::new();
for h in &sel {
if let Some(e) = d.entity(*h) {
let n = e.kind.type_name();
match types.iter_mut().find(|(t, _)| *t == n) {
Some((_, hs)) => hs.push(h.hex()),
None => types.push((n, vec![h.hex()])),
}
}
}
let count: usize = types.iter().map(|(_, hs)| hs.len()).sum();
let header = match (count, types.as_slice()) {
(0, _) => "No selection".to_string(),
(1, [(n, _)]) => n.to_string(),
(n, [(ty, _)]) => format!("{ty} ({n})"),
(n, _) => format!("All ({n})"),
};
let mut run: Option<(&str, Value)> = None;
let mut start: Option<&str> = None;
ui.horizontal(|ui| {
ui.add_space(8.0);
value_box(ui, &header, true, !sel.is_empty());
});
ui.add_space(4.0);
let h = &d.header;
if sel.is_empty() {
let rows: Vec<(&str, String, bool)> = vec![
("Color", Color::from_aci(h.i64("CECOLOR", 256) as i16).name(), true),
("Layer", h.str("CLAYER", "0"), true),
("Linetype", h.str("CELTYPE", "ByLayer"), true),
("Linetype scale", format!("{:.4}", h.f64("CELTSCALE", 1.0)), true),
("Lineweight", cadcraft_doc::Lineweight::from_dxf(h.i64("CELWEIGHT", -1) as i16).name(), true),
("Transparency", "ByLayer".into(), true),
("Thickness", format!("{:.4}", h.f64("THICKNESS", 0.0)), true),
("Text style", h.str("TEXTSTYLE", "Standard"), true),
("Dimension style", h.str("DIMSTYLE", "Standard"), true),
("Multileader style", h.str("CMLEADERSTYLE", "Standard"), true),
("Table style", h.str("CTABLESTYLE", "Standard"), true),
("Annotation scale", "1:1".into(), true),
("Text height", format!("{:.4}", h.f64("TEXTSIZE", 0.2)), true),
("Plot style", "ByColor".into(), true),
("Plot style table", "None".into(), true),
("Plot style attached to", "Model".into(), false),
("Plot table type", "Not available".into(), false),
];
let layers: Vec<String> = d.layers.iter().map(|l| l.name.clone()).collect();
let mut action: Option<(&str, Value)> = None;
for (label, val, en) in rows {
prop_row(ui, label, |ui| match label {
"Color" => {
ui.menu_button(format!("■ {val}"), |ui| {
for (n, c) in [
("ByLayer", 256),
("ByBlock", 0),
("Red", 1),
("Yellow", 2),
("Green", 3),
("Cyan", 4),
("Blue", 5),
("Magenta", 6),
("White", 7),
] {
if ui.button(n).clicked() {
action = Some(("color", json!({ "color": c })));
ui.close();
}
}
});
}
"Layer" => {
ui.menu_button(val.clone(), |ui| {
for l in &layers {
if ui.button(l).clicked() {
action = Some(("layer.current", json!({ "name": l })));
ui.close();
}
}
});
}
"Text height" => {
if let Some(v) = edit_field(ui, ui.id().with("cur_textsize"), val.clone())
&& let Ok(f) = v.trim().parse::<f64>()
{
action = Some(("setvar", json!({ "name": "TEXTSIZE", "value": f })));
ui.spacing_mut().item_spacing = vec2(3.0, 0.0);
let w = (ui.available_width() - 80.0).max(60.0);
let resp = ui.allocate_ui(vec2(w, 20.0), |ui| value_box(ui, &header, true, count > 0)).inner;
if count > 0 {
// Narrow the selection to one object type.
egui::Popup::from_toggle_button_response(&resp).width(w - 10.0).close_behavior(egui::PopupCloseBehavior::CloseOnClick).show(|ui| {
for (ty, hs) in &types {
if ui.button(format!("{ty} ({})", hs.len())).clicked() {
run = Some(("select", json!({ "handles": hs })));
}
}
"Linetype scale" => {
if let Some(v) = edit_field(ui, ui.id().with("cur_celtscale"), val.clone())
&& let Ok(f) = v.trim().parse::<f64>()
{
action = Some(("setvar", json!({ "name": "CELTSCALE", "value": f })));
}
}
_ => {
value_box(ui, &val, en, en);
}
});
}
let tip = if pickadd { "Toggle PICKADD (on: picks add to the selection)" } else { "Toggle PICKADD (off: each pick replaces the selection)" };
if icons::button(ui, Icon::PickAdd, 22.0, tip, pickadd).clicked() {
run = Some(("setvar", json!({ "name": "PICKADD", "value": i32::from(!pickadd) })));
}
if icons::button(ui, Icon::SelectObjects, 22.0, "Select Objects", false).clicked() {
start = Some("select");
}
if icons::button(ui, Icon::QuickSelect, 22.0, "Quick Select", false).clicked() {
start = Some("ui.dialog.qselect");
}
});
if let Some((c, p)) = run {
let _ = app.run(c, p);
}
if let Some(c) = start {
app.start(c);
}
}
fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
let t = Tokens::get();
properties_header(app, ui);
let sel = app.session.selection();
let Ok(d) = app.session.doc() else { return };
ui.add_space(4.0);
let h = &d.header;
if sel.is_empty() {
// Current properties for new objects; each row runs the command that sets it.
let layers: Vec<String> = d.layers.iter().map(|l| l.name.clone()).collect();
let linetypes: Vec<String> = d.linetypes.iter().map(|l| l.name.clone()).collect();
let text_styles: Vec<String> = d.text_styles.iter().map(|l| l.name.clone()).collect();
let dim_styles: Vec<String> = d.dim_styles.iter().map(|l| l.name.clone()).collect();
let ml_styles: Vec<String> = d.mleader_styles.iter().map(|l| l.name.clone()).collect();
let tb_styles: Vec<String> = d.table_styles.iter().map(|l| l.name.clone()).collect();
let mut action: Option<(&str, Value)> = None;
let mut act = |c: &'static str, p: Value| action = Some((c, p));
prop_row(ui, "Color", |ui| {
ui.menu_button(format!("■ {}", Color::from_aci(h.i64("CECOLOR", 256) as i16).name()), |ui| {
for (n, c) in [
("ByLayer", 256),
("ByBlock", 0),
("Red", 1),
("Yellow", 2),
("Green", 3),
("Cyan", 4),
("Blue", 5),
("Magenta", 6),
("White", 7),
] {
if ui.button(n).clicked() {
act("color", json!({ "color": c }));
ui.close();
}
}
});
});
prop_row(ui, "Layer", |ui| {
if let Some(l) = pick_menu(ui, &h.str("CLAYER", "0"), layers) {
act("layer.current", json!({ "name": l }));
}
});
prop_row(ui, "Linetype", |ui| {
let items = ["ByLayer", "ByBlock"]
.map(String::from)
.into_iter()
.chain(linetypes.into_iter().filter(|l| !["ByLayer", "ByBlock"].contains(&l.as_str())));
if let Some(l) = pick_menu(ui, &h.str("CELTYPE", "ByLayer"), items) {
act("linetype", json!({ "current": l }));
}
});
prop_row(ui, "Linetype scale", |ui| {
if let Some(v) = edit_field(ui, ui.id().with("cur_celtscale"), format!("{:.4}", h.f64("CELTSCALE", 1.0)))
&& let Ok(f) = v.trim().parse::<f64>()
{
act("setvar", json!({ "name": "CELTSCALE", "value": f }));
}
});
prop_row(ui, "Lineweight", |ui| {
ui.menu_button(cadcraft_doc::Lineweight::from_dxf(h.i64("CELWEIGHT", -1) as i16).name(), |ui| {
for n in ["ByLayer", "ByBlock", "Default"] {
if ui.button(n).clicked() {
act("lweight", json!({ "lineweight": n }));
ui.close();
}
}
for v in cadcraft_doc::Lineweight::STANDARD {
if ui.button(format!("{:.2} mm", f64::from(v) / 100.0)).clicked() {
act("lweight", json!({ "lineweight": f64::from(v) / 100.0 }));
ui.close();
}
}
});
});
prop_row(ui, "Transparency", |ui| unavailable(ui, "ByLayer"));
prop_row(ui, "Thickness", |ui| {
if let Some(v) = edit_field(ui, ui.id().with("cur_thickness"), format!("{:.4}", h.f64("THICKNESS", 0.0)))
&& let Ok(f) = v.trim().parse::<f64>()
{
act("setvar", json!({ "name": "THICKNESS", "value": f }));
}
});
prop_row(ui, "Text style", |ui| {
if let Some(n) = pick_menu(ui, &h.str("TEXTSTYLE", "Standard"), text_styles) {
act("style.current", json!({ "name": n }));
}
});
prop_row(ui, "Dimension style", |ui| {
if let Some(n) = pick_menu(ui, &h.str("DIMSTYLE", "Standard"), dim_styles) {
act("dimstyle.current", json!({ "name": n }));
}
});
prop_row(ui, "Multileader style", |ui| {
if let Some(n) = pick_menu(ui, &h.str("CMLEADERSTYLE", "Standard"), ml_styles) {
act("mleaderstyle", json!({ "name": n, "current": true }));
}
});
prop_row(ui, "Table style", |ui| {
if let Some(n) = pick_menu(ui, &h.str("CTABLESTYLE", "Standard"), tb_styles) {
act("tablestyle", json!({ "name": n, "current": true }));
}
});
prop_row(ui, "Annotation scale", |ui| unavailable(ui, "1:1"));
prop_row(ui, "Text height", |ui| {
if let Some(v) = edit_field(ui, ui.id().with("cur_textsize"), format!("{:.4}", h.f64("TEXTSIZE", 0.2)))
&& let Ok(f) = v.trim().parse::<f64>()
{
act("setvar", json!({ "name": "TEXTSIZE", "value": f }));
}
});
prop_row(ui, "Plot style", |ui| unavailable(ui, "ByColor"));
prop_row(ui, "Plot style table", |ui| unavailable(ui, "None"));
prop_row(ui, "Plot style attached to", |ui| read_only(ui, "Model"));
prop_row(ui, "Plot table type", |ui| read_only(ui, "Not available"));
if let Some((c, p)) = action {
let _ = app.run(c, p);
}
@@ -641,9 +752,11 @@ fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
});
prop_row(ui, "Lineweight", |ui| {
ui.menu_button(lw.clone(), |ui| {
if ui.button("ByLayer").clicked() {
set = Some(json!({ "handles": ids, "lineweight": "ByLayer" }));
ui.close();
for n in ["ByLayer", "ByBlock", "Default"] {
if ui.button(n).clicked() {
set = Some(json!({ "handles": ids, "lineweight": n }));
ui.close();
}
}
for v in cadcraft_doc::Lineweight::STANDARD {
if ui.button(format!("{:.2} mm", f64::from(v) / 100.0)).clicked() {
@@ -694,11 +807,6 @@ fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
};
match &e.kind {
EntityKind::Line(l) => {
let fixed = |ui: &mut egui::Ui, label: &str, v: f64| {
prop_row(ui, label, |ui| {
value_box(ui, &format!("{v:.4}"), false, true);
})
};
prop_row(ui, "Start X", |ui| {
if let Some(s) = edit_field(ui, ui.id().with(("sx", &ids)), format!("{:.4}", l.a.x))
&& let Ok(f) = s.trim().parse::<f64>()
@@ -728,22 +836,19 @@ fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
}
});
let dlt = l.b.xy() - l.a.xy();
fixed(ui, "Delta X", dlt.x);
fixed(ui, "Delta Y", dlt.y);
fixed(ui, "Length", dlt.len());
fixed(ui, "Angle", dlt.angle().to_degrees());
prop_row(ui, "Delta X", |ui| read_only(ui, &format!("{:.4}", dlt.x)));
prop_row(ui, "Delta Y", |ui| read_only(ui, &format!("{:.4}", dlt.y)));
// Length and angle keep the start point and move the end point.
num(ui, "Length", "length", dlt.len(), &mut set, &ids);
num(ui, "Angle", "angle", dlt.angle().to_degrees(), &mut set, &ids);
}
EntityKind::Circle(c) => {
num(ui, "Center X", "cx", c.center.x, &mut set, &ids);
num(ui, "Center Y", "cy", c.center.y, &mut set, &ids);
num(ui, "Radius", "radius", c.radius, &mut set, &ids);
num(ui, "Diameter", "diameter", c.radius * 2.0, &mut set, &ids);
prop_row(ui, "Circumference", |ui| {
value_box(ui, &format!("{:.4}", c.radius * std::f64::consts::TAU), false, true);
});
prop_row(ui, "Area", |ui| {
value_box(ui, &format!("{:.4}", c.radius * c.radius * std::f64::consts::PI), false, true);
});
num(ui, "Circumference", "circumference", c.radius * std::f64::consts::TAU, &mut set, &ids);
num(ui, "Area", "area", c.radius * c.radius * std::f64::consts::PI, &mut set, &ids);
if let Some(v) = &mut set
&& let Some(o) = v.as_object_mut()
{
@@ -782,13 +887,9 @@ fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
EntityKind::LwPolyline(pl) => {
num(ui, "Global width", "width", pl.const_width, &mut set, &ids);
let g = cadcraft_geom::Polyline { vertices: pl.vertices.clone(), closed: pl.closed };
prop_row(ui, "Length", |ui| {
value_box(ui, &format!("{:.4}", g.len()), false, true);
});
prop_row(ui, "Length", |ui| read_only(ui, &format!("{:.4}", g.len())));
if pl.closed {
prop_row(ui, "Area", |ui| {
value_box(ui, &format!("{:.4}", g.area().abs()), false, true);
});
prop_row(ui, "Area", |ui| read_only(ui, &format!("{:.4}", g.area().abs())));
}
prop_row(ui, "Closed", |ui| {
let mut c = pl.closed;
@@ -819,13 +920,13 @@ fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
}
});
prop_row(ui, "Dim style", |ui| {
value_box(ui, &dm.style, true, true);
if let Some(n) = pick_menu(ui, &dm.style, d.dim_styles.iter().map(|st| st.name.clone())) {
set = Some(json!({ "handles": ids, "dimStyle": n }));
}
});
}
EntityKind::Insert(i) => {
prop_row(ui, "Name", |ui| {
value_box(ui, &i.block, false, true);
});
prop_row(ui, "Name", |ui| read_only(ui, &i.block));
num(ui, "Rotation", "rotation", i.rotation.to_degrees(), &mut set, &ids);
num(ui, "Scale", "scale", i.scale.x, &mut set, &ids);
}