mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-27 20:36:48 -06:00
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:
co-authored by
Claude Opus 5.5
parent
306ba991a5
commit
8d71f7de4a
@@ -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") {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user