fix(ui): accept toolbar drops across panel labels and gaps (#454)
This commit is contained in:
1 parent
5c7149e57d
commit
ddd528abaf
4 files changed
+81
-7
No files matched your search
@@ -287,6 +287,8 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
|
||||
record their coordinate frame; legacy wires migrate from known provenance without changing
|
||||
coordinates. Broken legacy links with missing provenance can require explicit refresh or relink.
|
||||
See [occurrence workflows](docs/occurrence-workflows.md).
|
||||
- Toolbar drops on panel labels, gaps and margins append commands with insertion feedback;
|
||||
button drops keep insert-before ordering.
|
||||
- Parameters and Configurations have distinct actions in the SOLID toolbar's CONFIGURE panel.
|
||||
Parameters opens the user/model variable editor; Configurations opens the configuration table.
|
||||
Configure remains available in the panel dropdown.
|
||||
|
||||
@@ -572,12 +572,6 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let free_x = bx + drawn as f32 * 40.0;
|
||||
let free = Rect::from_min_size(pos2(free_x, top + 2.0), vec2((px + width - 4.0 - free_x).max(8.0), 38.0));
|
||||
let fresp = ui.interact(free, ui.id().with(("panel_free", panel)), if collapsed { Sense::click() } else { Sense::hover() });
|
||||
if fresp.dnd_hover_payload::<ToolbarDrag>().is_some() {
|
||||
painter.line_segment([pos2(free.left(), free.top() + 2.0), pos2(free.left(), free.bottom() - 2.0)], Stroke::new(2.0, t.accent));
|
||||
}
|
||||
if let Some(d) = fresp.dnd_release_payload::<ToolbarDrag>() {
|
||||
drop = Some((d.id, *panel, None));
|
||||
}
|
||||
let mut opened = false;
|
||||
if collapsed {
|
||||
// One button: the panel's first command's icon over a caret; it opens the
|
||||
@@ -691,6 +685,20 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if lresp.clicked() {
|
||||
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new("sc_panel_open"), popup_id));
|
||||
}
|
||||
// Labels, gaps and margins append; actual buttons keep their insert-before target.
|
||||
// Respect the current layer so a dropdown covering the panel is not a drop target.
|
||||
let panel_rect = Rect::from_min_max(pos2(px, top + 2.0), pos2(px + width, r.bottom()));
|
||||
let over_button = ui
|
||||
.input(|input| input.pointer.hover_pos())
|
||||
.is_some_and(|p| (0..drawn).any(|i| Rect::from_min_size(pos2(bx + i as f32 * 40.0, top + 2.0), vec2(38.0, 38.0)).contains(p)));
|
||||
if !over_button && ui.rect_contains_pointer(panel_rect) && egui::DragAndDrop::payload::<ToolbarDrag>(ui.ctx()).is_some() {
|
||||
painter.line_segment([pos2(px + width - 4.0, top + 4.0), pos2(px + width - 4.0, top + 38.0)], Stroke::new(2.0, t.accent));
|
||||
if ui.input(|input| input.pointer.any_released())
|
||||
&& let Some(d) = egui::DragAndDrop::take_payload::<ToolbarDrag>(ui.ctx())
|
||||
{
|
||||
drop = Some((d.id, *panel, None));
|
||||
}
|
||||
}
|
||||
let open = ui.ctx().data(|d| d.get_temp::<egui::Id>(egui::Id::new("sc_panel_open"))) == Some(popup_id);
|
||||
if open {
|
||||
let cmds = if collapsed { menu_commands(&cmds, &shown) } else { cmds.clone() };
|
||||
@@ -1319,3 +1327,44 @@ mod dropdown_tests {
|
||||
assert!(menu.contains(*at), "the focused last row is out of view: {at:?} in {menu:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod panel_drop_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn panel_label_gap_and_margins_append_while_buttons_insert_before() {
|
||||
for target in ["label", "gap", "left margin", "right margin", "button"] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.step(&json!({"click":{"handle":"panel:SOLID:CREATE"}})).unwrap();
|
||||
let label = crate::scenario::handle_at("panel:SOLID:CREATE").unwrap();
|
||||
let destination = match target {
|
||||
"gap" => label - vec2(0.0, 10.0),
|
||||
"left margin" => pos2(label.x - panel_width(4) / 2.0 + 1.0, label.y - 30.0),
|
||||
"right margin" => pos2(label.x + panel_width(4) / 2.0 - 1.0, label.y - 30.0),
|
||||
"button" => crate::scenario::handle_at("button:solid.extrude").unwrap(),
|
||||
_ => label,
|
||||
};
|
||||
h.step(&json!({"drag":[{"handle":"dropdown:solid.cylinder"}, [destination.x,destination.y]],"steps":12})).unwrap();
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
let order = if target == "button" {
|
||||
json!(["button:sketch.create", "button:solid.cylinder", "button:solid.extrude", "button:solid.revolve", "button:solid.sweep"])
|
||||
} else {
|
||||
json!(["button:sketch.create", "button:solid.extrude", "button:solid.revolve", "button:solid.sweep", "button:solid.cylinder"])
|
||||
};
|
||||
h.step(&json!({"expect":{"handle_order":order,"tool":null,"dialog":null,"errors":0}})).unwrap();
|
||||
assert!(h.app.session.doc.features.is_empty(), "{target}: pinning must not run the command");
|
||||
h.step(&json!({"restart":"close"})).unwrap();
|
||||
h.step(&json!({"expect":{"handle_order":order}})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_label_drop_moves_a_button_between_panels() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.step(&json!({"drag":[{"handle":"button:solid.extrude"},{"handle":"panel:SOLID:MODIFY"}],"steps":12})).unwrap();
|
||||
h.step(&json!({"expect":{"handle_order":["button:solid.fillet","button:solid.chamfer","button:solid.scale","button:solid.extrude"],"tool":null,"dialog":null,"errors":0}})).unwrap();
|
||||
h.step(&json!({"restart":"close"})).unwrap();
|
||||
h.step(&json!({"expect":{"handle_order":["button:solid.scale","button:solid.extrude"]}})).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[
|
||||
{"note":"#333: dropping a dropdown command on a panel label appends it; moving between labels keeps the chosen order after restart."},
|
||||
{"click":{"handle":"panel:SOLID:CREATE"}},
|
||||
{"drag":[{"handle":"dropdown:solid.cylinder"},{"handle":"panel:SOLID:CREATE"}],"steps":12},
|
||||
{"key":"Escape"},
|
||||
{"expect":{"handle_order":["button:solid.sweep","button:solid.cylinder"],"dialog":null,"tool":null,"errors":0}},
|
||||
{"click":{"handle":"panel:SOLID:CREATE"}},
|
||||
{"drag":[{"handle":"dropdown:solid.torus"},{"handle":"panel:SOLID:MODIFY"}],"steps":12},
|
||||
{"key":"Escape"},
|
||||
{"expect":{"handle_order":["button:solid.scale","button:solid.torus"],"dialog":null,"tool":null,"errors":0}},
|
||||
{"drag":[{"handle":"button:solid.torus"},{"handle":"panel:SOLID:CREATE"}],"steps":12},
|
||||
{"expect":{"handle_order":["button:solid.cylinder","button:solid.torus"]}},
|
||||
{"restart":"close"},
|
||||
{"expect":{"handle_order":["button:solid.sweep","button:solid.cylinder","button:solid.torus"],"errors":0}},
|
||||
{"call":"ui.resize","params":{"width":640,"height":720}},
|
||||
{"click":{"handle":"panel:SOLID:CREATE"}},
|
||||
{"drag":[{"handle":"dropdown:solid.box"},{"handle":"panel:SOLID:CREATE"}],"steps":12},
|
||||
{"key":"Escape"},
|
||||
{"call":"ui.resize","params":{"width":1600,"height":1000}},
|
||||
{"expect":{"handle_order":["button:solid.torus","button:solid.box"],"tool":null,"dialog":null,"errors":0}}
|
||||
]
|
||||
@@ -7,7 +7,9 @@
|
||||
undo history, selection and camera).
|
||||
- **Toolbar.** The workspace switcher (DESIGN) sits on the left. The tabs (SOLID, SURFACE,
|
||||
SHEET METAL, PLASTIC, UTILITIES, and SKETCH while you sketch) hold panels of commands. Click a
|
||||
panel's name for all its commands.
|
||||
panel's name for all its commands. Drag a command from the dropdown onto a panel's label
|
||||
or empty space to append it; the insertion line shows its destination. Drop onto an existing
|
||||
button to insert before it. Buttons can also move between panels, and the layout is saved.
|
||||
- **Browser** (left). The design as a tree: document settings and units, named views, the
|
||||
origin, bodies, sketches, construction planes, canvases, joints and components. The eye shows or
|
||||
hides an item, a component's radio button makes it the active component, and right-clicking any
|
||||
|
||||
Reference in new issue
Block a user