mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Browser: show F2 shortcut on Rename context menu rows and route key when open (#10388)
Show F2 beside Rename on context menus where F2 already renames the target (bodies, features and sketches, construction planes, components and occurrences, browser groups, and canvases). Saved views and joints remain without a shortcut label because F2 does not rename them without a menu. When a context menu is open, pressing F2 routes through press_shortcut to rename the menu's target (such as a clicked browser group or canvas) and close the menu, rather than renaming the viewport selection. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Gemini 3.8 Flash <noreply@google.com>
This commit is contained in:
@@ -364,7 +364,7 @@ pub fn group_items(app: &SolveApp, id: u64, scope: &str) -> Vec<Item> {
|
||||
v.push(Item::action("ui.newGroup", "New Group", "folder").with(json!({ "component": g.component, "folder": g.folder, "parent": id })));
|
||||
}
|
||||
v.extend([
|
||||
Item::action("ui.rename", "Rename", "rename").with(json!({ "group": id })),
|
||||
Item::action("ui.rename", "Rename", "rename").key("F2").with(json!({ "group": id })),
|
||||
Item::action("ui.ungroup", "Ungroup", "folder").with(json!({ "group": id })),
|
||||
Item::sep(),
|
||||
// The group itself: its items keep their own state.
|
||||
@@ -2181,7 +2181,7 @@ pub fn canvas_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
Item::action("canvas.to_sketch", "Canvas to Sketch", "canvas").with(json!({ "canvas": id })),
|
||||
Item::sep(),
|
||||
Item::action("ui.delete", "Delete", "delete").with(json!({ "canvases": [id] })),
|
||||
Item::action("ui.rename", "Rename", "rename").with(json!({ "canvas": id })),
|
||||
Item::action("ui.rename", "Rename", "rename").key("F2").with(json!({ "canvas": id })),
|
||||
Item::sep(),
|
||||
Item::action("ui.canvasVisible", "Show/Hide", "eye").key("V").with(json!({ "canvas": id, "visible": !visible })),
|
||||
]
|
||||
|
||||
@@ -609,7 +609,7 @@ fn feature_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
vec![
|
||||
act("ui.editFeature", "Edit Feature", "edit").with(json!({"feature":id})).on(exists),
|
||||
act("ui.editSketch", "Edit Profile Sketch", "sketch").with(json!({"sketch": sketch.map(|s| s.0)})).on(sketch.is_some()),
|
||||
act("ui.rename", "Rename", "rename").with(json!({"feature":id})).on(exists),
|
||||
act("ui.rename", "Rename", "rename").key("F2").with(json!({"feature":id})).on(exists),
|
||||
act("timeline.suppress", if suppressed { "Unsuppress" } else { "Suppress" }, "suppress")
|
||||
.with(json!({"feature":id, "suppressed": !suppressed}))
|
||||
.on(exists),
|
||||
@@ -788,7 +788,7 @@ fn body_items(app: &SolveApp, bodies: &[String], browser: bool) -> Vec<Item> {
|
||||
act("ui.paste", "Paste", "paste").key("Ctrl+V").on(crate::edit_clipboard::can_paste(app)),
|
||||
act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": items })).on(!locked),
|
||||
cmd(app, "solid.remove", "Remove").with(json!({ "bodies": names })).on(!locked),
|
||||
act("ui.rename", "Rename", "rename").with(json!({ "body": bodies.first() })).on(one),
|
||||
act("ui.rename", "Rename", "rename").key("F2").with(json!({ "body": bodies.first() })).on(one),
|
||||
Item::sep(),
|
||||
act(if all_hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": names })),
|
||||
act("ui.lock", if locked { "Unlock" } else { "Lock" }, "ground").with(json!({ "bodies": names })),
|
||||
@@ -841,7 +841,7 @@ fn sketch_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
act("ui.copy", "Copy", "copy").key("Ctrl+C").on(crate::edit_clipboard::something_to_copy(app)),
|
||||
act("ui.paste", "Paste", "paste").key("Ctrl+V").on(crate::edit_clipboard::can_paste(app)),
|
||||
act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": [{"type": "feature", "id": id}] })),
|
||||
act("ui.rename", "Rename", "rename").with(json!({ "feature": id })),
|
||||
act("ui.rename", "Rename", "rename").key("F2").with(json!({ "feature": id })),
|
||||
crate::browser::sketches_to_parts_item(sketches).on(parts && !editing),
|
||||
Item::sep(),
|
||||
act("ui.lookAt", "Look At", "lookat").with(json!({ "sketch": id })),
|
||||
@@ -868,7 +868,7 @@ fn plane_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
items.push(Item::sep());
|
||||
items.extend([
|
||||
act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": [{"type": "feature", "id": id}] })),
|
||||
act("ui.rename", "Rename", "rename").with(json!({ "feature": id })),
|
||||
act("ui.rename", "Rename", "rename").key("F2").with(json!({ "feature": id })),
|
||||
Item::sep(),
|
||||
act("ui.originPart", "Show/Hide", "eye").key("V").with(json!({ "keys": [f.name], "show": !visible })),
|
||||
Item::sep(),
|
||||
@@ -1083,7 +1083,7 @@ fn component_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
if id != 0 {
|
||||
v.push(act("ui.delete", "Delete", "delete").key("Del").with(json!({ "occurrences": [occ.map(|o| o.id)] })).on(occ.is_some()));
|
||||
}
|
||||
v.push(act("ui.rename", "Rename", "rename").with(json!({ "component": id })));
|
||||
v.push(act("ui.rename", "Rename", "rename").key("F2").with(json!({ "component": id })));
|
||||
v.push(Item::sep());
|
||||
v.push(act(if hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": names })).on(!bodies.is_empty()));
|
||||
v.push(act("ui.showAll", "Show All Bodies", "eye"));
|
||||
|
||||
@@ -1057,6 +1057,88 @@ fn f2_renames_the_selected_item() {
|
||||
assert!(!context_menu::rename_selection(&mut app, pos2(0.0, 0.0)), "nothing selected");
|
||||
}
|
||||
|
||||
/// Rename shows F2 wherever F2 renames (bodies, sketches and features, construction geometry,
|
||||
/// components and placements, groups, canvases), as Cut, Copy, Delete and Show/Hide show their
|
||||
/// keys; joints and saved views, which F2 does not rename, show none. F2 pressed with such a
|
||||
/// menu open renames what the menu is for, as its row says: a group or canvas is not selected by
|
||||
/// the right-click that opens its menu, so F2 would rename the selection instead.
|
||||
#[test]
|
||||
fn rename_shows_f2_and_f2_with_a_menu_open_renames_what_the_menu_is_for() {
|
||||
use crate::context_menu::RenameWhat;
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.run("solid.box", json!({"length": 10, "width": 20, "height": 30})).unwrap();
|
||||
let b = body(&h.app);
|
||||
let feature = h.app.session.doc.features[0].id;
|
||||
h.app.run("construct.plane.offset", json!({"base": "XY", "offset": 5})).unwrap();
|
||||
let plane = h.app.session.doc.features.last().unwrap().id;
|
||||
let part = h.app.run("component.create", json!({"name": "Menu part"})).unwrap()["component"].as_u64().unwrap();
|
||||
let occurrence = h.app.session.doc.occurrence_of(part).unwrap().id;
|
||||
let group = h.app.run("browser.group", json!({"component": 0, "folder": "bodies", "items": [b]})).unwrap()["group"].as_u64().unwrap();
|
||||
let mut png = b"\x89PNG\r\n\x1a\n\0\0\0\rIHDR".to_vec();
|
||||
png.extend(400u32.to_be_bytes());
|
||||
png.extend(200u32.to_be_bytes());
|
||||
png.extend([8, 6, 0, 0, 0, 0, 0, 0, 0]);
|
||||
let data = solidcraft_engine::doc::canvas::base64_encode(&png);
|
||||
let canvas = h.app.run("canvas.insert", json!({"data": data, "plane": "XY", "width": 80})).unwrap()["canvas"].as_u64().unwrap();
|
||||
let rename_key = |app: &SolveApp, target: &Target| {
|
||||
context_menu::items(app, target)
|
||||
.into_iter()
|
||||
.find(|i| i.id == "ui.rename")
|
||||
.map(|i| i.shortcut)
|
||||
.unwrap_or_else(|| panic!("no Rename row: {target:?}"))
|
||||
};
|
||||
for target in [
|
||||
Target::Body { name: b.clone() },
|
||||
Target::Sketch { id: feature },
|
||||
Target::Feature { id: feature },
|
||||
Target::Plane { id: plane },
|
||||
Target::Component { id: part },
|
||||
Target::Occurrence { path: vec![occurrence] },
|
||||
Target::Group { id: group, scope: "c0".into() },
|
||||
Target::Canvas { id: canvas },
|
||||
] {
|
||||
assert_eq!(rename_key(&h.app, &target), "F2", "Rename shows F2: {target:?}");
|
||||
}
|
||||
assert_eq!(rename_key(&h.app, &Target::NamedView { name: "Top".into() }), "", "F2 does not rename a saved view");
|
||||
let sample = solidcraft_engine::sample::samples().into_iter().find(|s| s.name == "Hinge Assembly").unwrap();
|
||||
let mut assembly = SolveApp::new(Session::default(), Services::default());
|
||||
assembly.session.run_script(&(sample.script)()).unwrap();
|
||||
let joint = assembly.session.doc.assembly.joints[0].id;
|
||||
assert_eq!(rename_key(&assembly, &Target::Joint { id: joint }), "", "F2 does not rename a joint");
|
||||
|
||||
let press_f2 = |h: &mut crate::scenario::Harness| {
|
||||
for pressed in [true, false] {
|
||||
h.app.synthetic.push(egui::Event::Key { key: egui::Key::F2, physical_key: None, pressed, repeat: false, modifiers: Default::default() });
|
||||
}
|
||||
while !h.app.synthetic.is_empty() {
|
||||
h.frame();
|
||||
}
|
||||
};
|
||||
// A body selected elsewhere is not what the group's or the canvas's menu is for.
|
||||
h.app.run("select.set", json!({"items": [{"type": "body", "name": b}]})).unwrap();
|
||||
for (target, what) in
|
||||
[(Target::Group { id: group, scope: "c0".into() }, RenameWhat::Group(group)), (Target::Canvas { id: canvas }, RenameWhat::Canvas(canvas))]
|
||||
{
|
||||
context_menu::open_for(&mut h.app, pos2(300.0, 200.0), target.clone());
|
||||
h.frames(2);
|
||||
press_f2(&mut h);
|
||||
let renaming = h.app.menu.rename.as_ref().map(|r| r.what.clone());
|
||||
assert_eq!(renaming, Some(what), "F2 on the menu of {target:?}");
|
||||
assert!(h.app.viewport.context_menu.is_none(), "the menu closes as after a click");
|
||||
h.step(&json!({"key": "Escape"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.app.menu.rename.is_none());
|
||||
}
|
||||
// With no menu open, F2 still renames the selection.
|
||||
h.app.run("select.set", json!({"items": [{"type": "body", "name": b}]})).unwrap();
|
||||
h.frames(2);
|
||||
press_f2(&mut h);
|
||||
assert_eq!(h.app.menu.rename.as_ref().map(|r| r.what.clone()), Some(RenameWhat::Body(b)), "F2 without a menu renames the selection");
|
||||
h.step(&json!({"key": "Escape"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.app.menu.rename.is_none());
|
||||
}
|
||||
|
||||
/// A double-click on a body's browser row edits its name in the row, like F2 (no floating box);
|
||||
/// Escape cancels, and a second double-click then commits a new name.
|
||||
#[test]
|
||||
|
||||
@@ -3181,8 +3181,10 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
Key::F2 => {
|
||||
let at = ctx.input(|i| i.pointer.latest_pos()).unwrap_or(ctx.content_rect().center());
|
||||
crate::context_menu::rename_selection(app, at);
|
||||
if !crate::context_menu::press_shortcut(app, "F2") {
|
||||
let at = ctx.input(|i| i.pointer.latest_pos()).unwrap_or(ctx.content_rect().center());
|
||||
crate::context_menu::rename_selection(app, at);
|
||||
}
|
||||
}
|
||||
Key::F6 => app.animate_view("fit"),
|
||||
Key::Backspace if crate::tools::undo_spline_point(app) => {}
|
||||
|
||||
Reference in New Issue
Block a user