Browser: joints use the shared context menu (Edit Joint, Drive Joint, Suppress, Delete, Rename)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 12:09:31 +00:00
1 parent 13070abc55
commit a901dade04
2 files changed
+44 -5

No files matched your search

+38
View File
@@ -42,6 +42,10 @@ pub enum Target {
Origin, Origin,
/// Document Settings › Units. /// Document Settings › Units.
Units, Units,
/// An assembly joint.
Joint {
id: u64,
},
/// The Named Views folder, and one view (saved or standard) in it. /// The Named Views folder, and one view (saved or standard) in it.
NamedViews, NamedViews,
NamedView { NamedView {
@@ -156,6 +160,7 @@ pub enum RenameWhat {
Group(u64), Group(u64),
Canvas(u64), Canvas(u64),
View(String), View(String),
Joint(u64),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -220,6 +225,7 @@ pub fn target_bodies(app: &SolveApp, target: &Target) -> Vec<String> {
| Target::Canvas { .. } | Target::Canvas { .. }
| Target::Origin | Target::Origin
| Target::Units | Target::Units
| Target::Joint { .. }
| Target::NamedViews | Target::NamedViews
| Target::NamedView { .. } => Vec::new(), | Target::NamedView { .. } => Vec::new(),
} }
@@ -281,6 +287,7 @@ pub fn items(app: &SolveApp, target: &Target) -> Vec<Item> {
Target::Canvas { id } => crate::browser::canvas_items(app, *id), Target::Canvas { id } => crate::browser::canvas_items(app, *id),
Target::Origin => crate::browser::origin_items(app), Target::Origin => crate::browser::origin_items(app),
Target::NamedViews => vec![act("ui.newView", "New Named View", "perspective")], Target::NamedViews => vec![act("ui.newView", "New Named View", "perspective")],
Target::Joint { id } => joint_items(app, *id),
Target::Units => crate::prefs::UNITS Target::Units => crate::prefs::UNITS
.iter() .iter()
.map(|u| { .map(|u| {
@@ -462,6 +469,20 @@ fn sketch_items(app: &SolveApp, id: u64) -> Vec<Item> {
] ]
} }
/// An assembly joint: edit, drive, suppress, delete.
fn joint_items(app: &SolveApp, id: u64) -> Vec<Item> {
let Some(j) = app.session.doc.assembly.joints.iter().find(|j| j.id == id) else { return Vec::new() };
let rigid = j.kind == solvecraft_engine::doc::joints::JointKind::Rigid;
vec![
act("ui.editJoint", "Edit Joint", "joint").with(json!({ "joint": id })),
act("ui.driveJoint", "Drive Joint", "").with(json!({ "joint": id })).on(!rigid),
cmd(app, "joint.edit", if j.suppressed { "Unsuppress" } else { "Suppress" }).with(json!({ "joint": id, "suppressed": !j.suppressed })),
Item::sep(),
cmd(app, "joint.delete", "Delete").key("Del").with(json!({ "joint": id })),
act("ui.rename", "Rename", "").with(json!({ "joint": id })),
]
}
fn component_items(app: &SolveApp, id: u64) -> Vec<Item> { fn component_items(app: &SolveApp, id: u64) -> Vec<Item> {
let doc = &app.session.doc; let doc = &app.session.doc;
let occ = doc.occurrence_of(id); let occ = doc.occurrence_of(id);
@@ -670,6 +691,8 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
RenameWhat::Canvas(c) RenameWhat::Canvas(c)
} else if let Some(v) = p.get("view").and_then(Value::as_str) { } else if let Some(v) = p.get("view").and_then(Value::as_str) {
RenameWhat::View(v.to_string()) RenameWhat::View(v.to_string())
} else if let Some(j) = id_of(&p, "joint") {
RenameWhat::Joint(j)
} else { } else {
return; return;
}; };
@@ -751,6 +774,19 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
} }
} }
"ui.canvasVisible" => drop(app.run("canvas.edit", p)), "ui.canvasVisible" => drop(app.run("canvas.edit", p)),
"ui.editJoint" | "ui.driveJoint" => {
if let Some(id) = id_of(&p, "joint") {
let d = if item.id == "ui.editJoint" {
crate::dialogs_assembly::edit_joint(app, id)
} else {
crate::dialogs_assembly::drive_joint(app, id)
};
if let Some(d) = d {
app.tool = None;
app.dialog = Some(d);
}
}
}
"ui.newView" => drop(crate::browser::save_view(app, None)), "ui.newView" => drop(crate::browser::save_view(app, None)),
"ui.restoreView" => { "ui.restoreView" => {
if let Some(v) = p.get("view").and_then(Value::as_str) { if let Some(v) = p.get("view").and_then(Value::as_str) {
@@ -923,6 +959,7 @@ pub fn start_rename(app: &mut SolveApp, what: RenameWhat, at: Pos2) {
RenameWhat::Group(g) => crate::browser::group_name(app, *g).unwrap_or_default(), RenameWhat::Group(g) => crate::browser::group_name(app, *g).unwrap_or_default(),
RenameWhat::Canvas(c) => doc.canvases.iter().find(|x| x.id == *c).map(|x| x.name.clone()).unwrap_or_default(), RenameWhat::Canvas(c) => doc.canvases.iter().find(|x| x.id == *c).map(|x| x.name.clone()).unwrap_or_default(),
RenameWhat::View(v) => v.clone(), RenameWhat::View(v) => v.clone(),
RenameWhat::Joint(j) => doc.assembly.joints.iter().find(|x| x.id == *j).map(|x| x.name.clone()).unwrap_or_default(),
}; };
app.menu.rename = Some(Rename { what, text, at, focused: false }); app.menu.rename = Some(Rename { what, text, at, focused: false });
} }
@@ -947,6 +984,7 @@ fn commit_rename(app: &mut SolveApp, r: &Rename) {
RenameWhat::Group(g) => crate::browser::group_action(app, "ui.renameGroup", &json!({ "group": g, "name": name })), RenameWhat::Group(g) => crate::browser::group_action(app, "ui.renameGroup", &json!({ "group": g, "name": name })),
RenameWhat::Canvas(c) => drop(app.run("canvas.edit", json!({ "canvas": c, "name": name }))), RenameWhat::Canvas(c) => drop(app.run("canvas.edit", json!({ "canvas": c, "name": name }))),
RenameWhat::View(v) => drop(app.run("view.rename", json!({ "view": v, "name": name }))), RenameWhat::View(v) => drop(app.run("view.rename", json!({ "view": v, "name": name }))),
RenameWhat::Joint(j) => drop(app.run("joint.edit", json!({ "joint": j, "name": name }))),
} }
} }
+6 -5
View File
@@ -1005,16 +1005,17 @@ pub fn browser_joints(app: &mut SolveApp, ui: &mut egui::Ui, depth: usize) {
return; return;
} }
let mut dialog: Option<Dialog> = None; let mut dialog: Option<Dialog> = None;
let mut run: Option<(&'static str, Value)> = None;
for (id, name, kind, suppressed) in joints { for (id, name, kind, suppressed) in joints {
let resp = row(ui, depth + 1.0, "joint", &name, suppressed, None).on_hover_text(format!("{kind:?} joint")); let resp = row(ui, depth + 1.0, "joint", &name, suppressed, None).on_hover_text(format!("{kind:?} joint"));
if resp.double_clicked() { if resp.double_clicked() {
dialog = edit_joint(app, id); dialog = edit_joint(app, id);
} }
resp.context_menu(|ui| joint_menu(app, ui, (id, kind, suppressed), &mut dialog, &mut run)); // The same menus as the rest of the browser.
} if resp.secondary_clicked() {
if let Some((c, p)) = run { let at = resp.interact_pointer_pos().unwrap_or(resp.rect.left_bottom());
let _ = app.run(c, p); crate::context_menu::open_for(app, at, crate::context_menu::Target::Joint { id });
}
let _ = (kind, suppressed);
} }
if let Some(d) = dialog { if let Some(d) = dialog {
app.tool = None; app.tool = None;