feat: Group sketch creation tools and remember variants per part (#589)
Sketch Create groups its drawing tools under Line, Rectangle, Circle, Arc, Slot, Polygon and Spline. Each family's main button starts the part's last-used variant and its caret lists the others; choices are per open part, kept across sketch sessions and tab switches, and never touch the design or undo history. Co-authored-by: Karl Von Richter <[email protected]>
This commit is contained in:
1 parent
14635e5d3a
commit
04cfb74c5e
8 files changed
+306
-22
No files matched your search
@@ -61,6 +61,9 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
|
||||
|
||||
## Dimensioning and error reporting
|
||||
|
||||
- Sketch Create groups drawing variants under Line, Rectangle, Circle, Arc, Slot,
|
||||
Polygon and Spline. Each open part remembers its last interactive choice per family
|
||||
across sketch sessions and document-tab switches; closing the part resets these UI choices.
|
||||
- Inscribed, circumscribed and edge polygon tools preview their actual outline before
|
||||
placement, including orientation and the selected side count.
|
||||
- Construction (X) preserves the active sketch drawing tool, picked anchors and inline
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct DocSlot {
|
||||
/// The per-design part of the UI state (what is hidden, shown or locked).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct DocView {
|
||||
sketch_choices: crate::sketch_families::Choices,
|
||||
hidden_profiles: Vec<u64>,
|
||||
shown_dims: Vec<u64>,
|
||||
locked_bodies: Vec<String>,
|
||||
@@ -59,6 +60,7 @@ impl Documents {
|
||||
fn take_view(app: &mut SolveApp) -> DocView {
|
||||
let u = &mut app.ui;
|
||||
DocView {
|
||||
sketch_choices: std::mem::take(&mut app.sketch_choices),
|
||||
hidden_profiles: std::mem::take(&mut u.hidden_profiles),
|
||||
shown_dims: std::mem::take(&mut u.shown_dims),
|
||||
locked_bodies: std::mem::take(&mut u.locked_bodies),
|
||||
@@ -66,6 +68,7 @@ fn take_view(app: &mut SolveApp) -> DocView {
|
||||
}
|
||||
|
||||
fn put_view(app: &mut SolveApp, v: DocView) {
|
||||
app.sketch_choices = v.sketch_choices;
|
||||
let u = &mut app.ui;
|
||||
u.hidden_profiles = v.hidden_profiles;
|
||||
u.shown_dims = v.shown_dims;
|
||||
@@ -148,6 +151,7 @@ pub fn adopt(app: &mut SolveApp, session: Session) {
|
||||
app.menu.props = None;
|
||||
crate::sketch_attachment_ui::clear(app);
|
||||
app.session = session;
|
||||
app.sketch_choices = crate::sketch_families::Choices::default();
|
||||
app.session.invalidate_view(rev);
|
||||
app.cam = Camera::default();
|
||||
app.fit_view();
|
||||
|
||||
@@ -98,6 +98,7 @@ mod sketch_display_ui_tests;
|
||||
mod sketch_drag_ui_tests;
|
||||
#[cfg(test)]
|
||||
mod sketch_edit_tests;
|
||||
mod sketch_families;
|
||||
pub mod sketch_palette;
|
||||
pub(crate) mod sketch_text;
|
||||
pub mod sketch_tools;
|
||||
@@ -227,6 +228,7 @@ pub struct SolveApp {
|
||||
pub crashes: crash_ui::CrashUi,
|
||||
/// Toolbar buttons the user pinned, removed or reordered.
|
||||
pub toolbar_custom: toolbar_custom::ToolbarCustom,
|
||||
pub sketch_choices: sketch_families::Choices,
|
||||
/// Preferences kept between runs, and their window.
|
||||
pub preferences: prefs::Prefs,
|
||||
pub prefs_window: prefs::PrefsWindow,
|
||||
@@ -300,6 +302,7 @@ impl SolveApp {
|
||||
issue_report: issue_report::ReportState::default(),
|
||||
crashes: crash_ui::CrashUi::default(),
|
||||
toolbar_custom: toolbar_custom::ToolbarCustom::default(),
|
||||
sketch_choices: sketch_families::Choices::default(),
|
||||
preferences: prefs::Prefs::default(),
|
||||
prefs_window: prefs::PrefsWindow::default(),
|
||||
preview: preview::PreviewState::default(),
|
||||
@@ -464,6 +467,7 @@ impl SolveApp {
|
||||
Ok(_) => {
|
||||
self.session.echo(format!("{} done", command_label(id)));
|
||||
if matches!(id, "file.new" | "doc.open" | "doc.recover") {
|
||||
self.sketch_choices = sketch_families::Choices::default();
|
||||
dim_view::cancel();
|
||||
self.menu.props = None;
|
||||
sketch_attachment_ui::clear(self);
|
||||
@@ -535,6 +539,9 @@ impl SolveApp {
|
||||
if !sketch_tools::start_hook(self, id) {
|
||||
return;
|
||||
}
|
||||
if solidcraft_engine::find_command(id).is_some_and(|c| c.info(&self.session).enabled) {
|
||||
self.sketch_choices.remember(id);
|
||||
}
|
||||
if id == "parameters.change" {
|
||||
params_dialog::open(self);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//! Sketch creation families and the active part's last interactive variant.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use solidcraft_engine::CommandSpec;
|
||||
|
||||
pub struct Family {
|
||||
pub label: &'static str,
|
||||
pub commands: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub const FAMILIES: &[Family] = &[
|
||||
Family { label: "Line", commands: &["sketch.line", "sketch.line.midpoint"] },
|
||||
Family { label: "Rectangle", commands: &["sketch.rectangle.two_point", "sketch.rectangle.three_point", "sketch.rectangle.center"] },
|
||||
Family {
|
||||
label: "Circle",
|
||||
commands: &[
|
||||
"sketch.circle.center",
|
||||
"sketch.circle.two_point",
|
||||
"sketch.circle.three_point",
|
||||
"sketch.circle.two_tangent",
|
||||
"sketch.circle.three_tangent",
|
||||
],
|
||||
},
|
||||
Family { label: "Arc", commands: &["sketch.arc.three_point", "sketch.arc.center_point", "sketch.arc.tangent"] },
|
||||
Family {
|
||||
label: "Slot",
|
||||
commands: &[
|
||||
"sketch.slot.center_to_center",
|
||||
"sketch.slot.overall",
|
||||
"sketch.slot.center_point",
|
||||
"sketch.slot.arc_three_point",
|
||||
"sketch.slot.arc_center",
|
||||
],
|
||||
},
|
||||
Family { label: "Polygon", commands: &["sketch.polygon.inscribed", "sketch.polygon.circumscribed", "sketch.polygon.edge"] },
|
||||
Family { label: "Spline", commands: &["sketch.spline.fit_point", "sketch.spline.control_point", "sketch.spline.control_point_5"] },
|
||||
];
|
||||
|
||||
pub fn family(id: &str) -> Option<&'static Family> {
|
||||
FAMILIES.iter().find(|f| f.commands.contains(&id))
|
||||
}
|
||||
|
||||
/// UI state only, swapped with document tabs; never dirties the model or its undo history.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Choices(BTreeMap<&'static str, &'static str>);
|
||||
|
||||
impl Choices {
|
||||
pub fn remember(&mut self, id: &str) {
|
||||
if let Some(f) = family(id)
|
||||
&& let Some(id) = f.commands.iter().find(|c| **c == id)
|
||||
{
|
||||
self.0.insert(f.label, id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self, f: &Family) -> Option<&'static CommandSpec> {
|
||||
self.0.get(f.label).copied().or_else(|| f.commands.first().copied()).and_then(solidcraft_engine::find_command)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn families_are_disjoint_registered_creation_commands() {
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
for f in FAMILIES {
|
||||
assert!(!f.commands.is_empty());
|
||||
for id in f.commands {
|
||||
assert!(seen.insert(id), "duplicate variant {id}");
|
||||
let c = solidcraft_engine::find_command(id).unwrap();
|
||||
assert_eq!((c.tab, c.panel), ("SKETCH", "CREATE"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choices_ignore_unknown_commands_and_keep_families_independent() {
|
||||
let mut choices = Choices::default();
|
||||
choices.remember("sketch.rectangle.center");
|
||||
choices.remember("sketch.circle.three_point");
|
||||
choices.remember("sketch.rectangle.unknown");
|
||||
for (id, expected) in [
|
||||
("sketch.rectangle.two_point", "sketch.rectangle.center"),
|
||||
("sketch.circle.center", "sketch.circle.three_point"),
|
||||
("sketch.line", "sketch.line"),
|
||||
] {
|
||||
assert_eq!(choices.selected(family(id).unwrap()).unwrap().id, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
-16
@@ -562,7 +562,10 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
(panel, cmds, shown)
|
||||
})
|
||||
.collect();
|
||||
let widths: Vec<f32> = lists.iter().map(|(_, _, shown)| panel_width(shown.len())).collect();
|
||||
let width_of = |panel: &str, count: usize| {
|
||||
if tab == "SKETCH" && panel == "CREATE" { (count.max(1) as f32 * 68.0).max(64.0) + 8.0 } else { panel_width(count) }
|
||||
};
|
||||
let widths: Vec<f32> = lists.iter().map(|(panel, _, shown)| width_of(panel, shown.len())).collect();
|
||||
let fits = fit(&widths, r.right() - 40.0 - px);
|
||||
crate::scenario::publish_count("toolbar_collapsed", fits.iter().filter(|f| **f == Fit::Collapsed).count() as f64);
|
||||
crate::scenario::publish_count("toolbar_more", fits.iter().filter(|f| **f == Fit::More).count() as f64);
|
||||
@@ -573,16 +576,18 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
continue;
|
||||
}
|
||||
let collapsed = fit == Fit::Collapsed;
|
||||
let width = if collapsed { COLLAPSED_WIDTH.min(panel_width(shown.len())) } else { panel_width(shown.len()) };
|
||||
let width = if collapsed { COLLAPSED_WIDTH.min(width_of(panel, shown.len())) } else { width_of(panel, shown.len()) };
|
||||
let sketch_create = tab == "SKETCH" && *panel == "CREATE";
|
||||
let stride = if sketch_create { 68.0 } else { 40.0 };
|
||||
let enabled_panel = !cmds.is_empty() || *panel == "SELECT";
|
||||
let popup_id = ui.id().with(("panel_menu", panel));
|
||||
// Dropped on the panel's free space (all of a collapsed panel): the button goes to
|
||||
// its end.
|
||||
let drawn = if collapsed { 0 } else { shown.len() };
|
||||
// Buttons narrower than the panel's minimum width (a one-button panel) are centred.
|
||||
let inset = if drawn == 0 { 0.0 } else { ((width - 8.0 - (drawn as f32 * 40.0 - 2.0)) / 2.0).max(0.0).floor() };
|
||||
let inset = if drawn == 0 { 0.0 } else { ((width - 8.0 - (drawn as f32 * stride - 2.0)) / 2.0).max(0.0).floor() };
|
||||
let bx = px + 4.0 + inset;
|
||||
let free_x = bx + drawn as f32 * 40.0;
|
||||
let free_x = bx + drawn as f32 * stride;
|
||||
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() });
|
||||
let mut opened = false;
|
||||
@@ -606,12 +611,23 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
for (i, c) in shown.iter().enumerate().take(drawn) {
|
||||
let br = Rect::from_min_size(pos2(bx + i as f32 * 40.0, top + 2.0), vec2(38.0, 38.0));
|
||||
let info = c.info(&app.session);
|
||||
// Only the family's default pin follows the part's choice. Explicitly pinned
|
||||
// variants remain exact commands, including in customised toolbars.
|
||||
let family = crate::sketch_families::family(c.id).filter(|f| sketch_create && f.commands.first().copied() == Some(c.id));
|
||||
let selected = family.and_then(|f| app.sketch_choices.selected(f)).unwrap_or(c);
|
||||
let whole = Rect::from_min_size(pos2(bx + i as f32 * stride, top + 2.0), vec2(stride - 2.0, 38.0));
|
||||
let mut br = whole;
|
||||
if family.is_some() {
|
||||
br.max.y -= 12.0;
|
||||
}
|
||||
let info = selected.info(&app.session);
|
||||
let resp = ui.interact(br, ui.id().with(("cmd", c.id)), Sense::click_and_drag());
|
||||
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), br.center());
|
||||
// The button itself (`toolbar:<id>` is also a drop-down row's while one is open).
|
||||
crate::scenario::publish_handle(&format!("button:{}", c.id), br.center());
|
||||
if selected.id != c.id {
|
||||
crate::scenario::publish_handle(&format!("button:{}", selected.id), br.center());
|
||||
}
|
||||
if resp.drag_started() {
|
||||
resp.dnd_set_drag_payload(ToolbarDrag { id: c.id, from_menu: false });
|
||||
}
|
||||
@@ -629,34 +645,60 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if resp.secondary_clicked() {
|
||||
menu = Some((resp.interact_pointer_pos().unwrap_or(br.left_bottom()), c.id));
|
||||
}
|
||||
if running.as_deref() == Some(c.id) {
|
||||
if running.as_deref() == Some(selected.id) {
|
||||
painter.rect_filled(br, 4.0, t.border.gamma_multiply(0.7));
|
||||
} else if resp.hovered() && info.enabled {
|
||||
painter.rect_filled(br, 4.0, t.hover);
|
||||
}
|
||||
let (ink, fill, acc) =
|
||||
if info.enabled { (t.icon, t.icon_fill, panel_accent(t, &tab, panel)) } else { (t.border, t.panel_header, t.border) };
|
||||
let icon = match c.id {
|
||||
let icon = match selected.id {
|
||||
"sketch.finish" => "finish",
|
||||
"config.table" => "configuration_table",
|
||||
_ => c.icon,
|
||||
_ => selected.icon,
|
||||
};
|
||||
icons::paint(&painter, br.shrink(6.0), icon, ink, fill, acc);
|
||||
let icon_rect =
|
||||
if sketch_create { Rect::from_center_size(pos2(br.center().x, br.top() + 13.0), vec2(24.0, 24.0)) } else { br.shrink(6.0) };
|
||||
icons::paint(&painter, icon_rect, icon, ink, fill, acc);
|
||||
let label = match c.id {
|
||||
"parameters.change" if *panel == "CONFIGURE" => "Parameters",
|
||||
"config.table" if *panel == "CONFIGURE" => "Configurations",
|
||||
_ => c.label,
|
||||
_ => selected.label,
|
||||
};
|
||||
let tr_label = app.tr_label(label);
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && info.enabled, tr_label));
|
||||
let tip = match &info.disabled_reason {
|
||||
Some(why) => format!("{}\n{why}", tr_label),
|
||||
None => format!("{}{}", tr_label, c.shortcut.map(|k| format!(" ({k})")).unwrap_or_default()),
|
||||
None => format!("{}{}", tr_label, selected.shortcut.map(|k| format!(" ({k})")).unwrap_or_default()),
|
||||
};
|
||||
if resp.on_hover_text(tip).clicked() && info.enabled {
|
||||
let id = c.id;
|
||||
let id = selected.id;
|
||||
app.start(id);
|
||||
}
|
||||
if let Some(f) = family {
|
||||
let ar = Rect::from_min_max(pos2(whole.left(), br.bottom()), whole.right_bottom());
|
||||
let arrow = ui.interact(ar, ui.id().with(("family", f.label)), Sense::click());
|
||||
crate::scenario::publish_handle(&format!("family:{}", f.label), ar.center());
|
||||
if arrow.hovered() {
|
||||
painter.rect_filled(ar, 3.0, t.hover);
|
||||
}
|
||||
painter.text(ar.center() - vec2(4.0, 0.0), Align2::CENTER_CENTER, app.tr_label(f.label), FontId::proportional(10.0), t.text);
|
||||
caret(&painter, pos2(ar.right() - 5.0, ar.center().y), t.text);
|
||||
arrow.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, format!("{} variants", f.label)));
|
||||
egui::Popup::menu(&arrow).show(|ui| {
|
||||
let variants: Vec<_> = f.commands.iter().filter_map(|id| solidcraft_engine::find_command(id)).collect();
|
||||
dropdown_keys(app, ui, &variants);
|
||||
sketch_menu_rows(app, ui, &variants, &mut menu, &mut toggle);
|
||||
});
|
||||
} else if sketch_create {
|
||||
painter.text(
|
||||
pos2(br.center().x, br.bottom() - 5.0),
|
||||
Align2::CENTER_CENTER,
|
||||
app.tr_label(c.label),
|
||||
FontId::proportional(10.0),
|
||||
t.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
if shown.is_empty() && !collapsed {
|
||||
let br = Rect::from_min_size(pos2(px + 4.0, top + 2.0), vec2(width - 8.0, 38.0));
|
||||
@@ -698,9 +740,9 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// 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)));
|
||||
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 * stride, top + 2.0), vec2(stride - 2.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())
|
||||
@@ -720,6 +762,10 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
} else if cmds.is_empty() {
|
||||
ui.label(egui::RichText::new("Not available yet").color(t.text_dim));
|
||||
}
|
||||
if sketch_create {
|
||||
sketch_create_menu(app, ui, &cmds, &mut menu, &mut toggle);
|
||||
return;
|
||||
}
|
||||
let row_w = dropdown_width(app, ui, &cmds);
|
||||
// Up and Down move the keyboard focus through the enabled rows (from none
|
||||
// focused: the first or the last), instead of egui's own spatial move.
|
||||
@@ -778,6 +824,10 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = ui.menu_button("»", |ui| {
|
||||
for (panel, cmds) in &overflow {
|
||||
ui.menu_button(*panel, |ui| {
|
||||
if tab == "SKETCH" && *panel == "CREATE" {
|
||||
sketch_create_menu(app, ui, cmds, &mut menu, &mut toggle);
|
||||
return;
|
||||
}
|
||||
if *panel == "SELECT" {
|
||||
selection_filter(app, ui);
|
||||
}
|
||||
@@ -924,6 +974,91 @@ fn dropdown_row(app: &SolveApp, ui: &mut egui::Ui, c: &'static solidcraft_engine
|
||||
RowAction::None
|
||||
}
|
||||
|
||||
/// Creation menus keep related variants together, including collapsed and overflow panels.
|
||||
fn sketch_create_menu(
|
||||
app: &mut SolveApp,
|
||||
ui: &mut egui::Ui,
|
||||
cmds: &[&'static solidcraft_engine::CommandSpec],
|
||||
menu: &mut Option<(egui::Pos2, &'static str)>,
|
||||
toggle: &mut Option<&'static str>,
|
||||
) {
|
||||
let room = (ui.ctx().content_rect().bottom() - ui.cursor().top() - 12.0).max(ROW_H * 3.0);
|
||||
egui::ScrollArea::vertical().id_salt("sketch_create_rows").max_height(room).show(ui, |ui| {
|
||||
for f in crate::sketch_families::FAMILIES {
|
||||
let variants: Vec<_> = f.commands.iter().filter_map(|id| cmds.iter().find(|c| c.id == *id).copied()).collect();
|
||||
if !variants.is_empty() {
|
||||
let response = ui.menu_button(app.tr_label(f.label).to_string(), |ui| {
|
||||
dropdown_keys(app, ui, &variants);
|
||||
sketch_menu_rows(app, ui, &variants, menu, toggle);
|
||||
});
|
||||
crate::scenario::publish_handle(&format!("create_family:{}", f.label), response.response.rect.center());
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
let rest: Vec<_> = cmds.iter().copied().filter(|c| crate::sketch_families::family(c.id).is_none()).collect();
|
||||
dropdown_keys(app, ui, &rest);
|
||||
sketch_menu_rows(app, ui, &rest, menu, toggle);
|
||||
});
|
||||
}
|
||||
|
||||
/// Family popups use the same ordered arrow-key navigation as a panel's command list.
|
||||
fn dropdown_keys(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solidcraft_engine::CommandSpec]) {
|
||||
let (down, up) = ui.input(|i| (i.key_pressed(egui::Key::ArrowDown), i.key_pressed(egui::Key::ArrowUp)));
|
||||
if !down && !up {
|
||||
return;
|
||||
}
|
||||
// A submenu renders before the remaining rows of its parent. Handle a key once, but
|
||||
// leave the event available for the focused row's scroll-into-view behaviour.
|
||||
let handled = egui::Id::new("sketch_menu_key_pass");
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
if ui.ctx().data(|d| d.get_temp::<u64>(handled)) == Some(pass) {
|
||||
return;
|
||||
}
|
||||
ui.ctx().data_mut(|d| d.insert_temp(handled, pass));
|
||||
let enabled: Vec<_> = cmds.iter().filter(|c| c.info(&app.session).enabled).collect();
|
||||
if enabled.is_empty() {
|
||||
return;
|
||||
}
|
||||
let focused = ui.ctx().memory(|m| m.focused());
|
||||
let at = enabled.iter().position(|c| focused == Some(dropdown_row_id(c.id)));
|
||||
let n = enabled.len();
|
||||
let next = match (at, down) {
|
||||
(None, true) => 0,
|
||||
(None, false) => n - 1,
|
||||
(Some(i), true) => (i + 1) % n,
|
||||
(Some(i), false) => (i + n - 1) % n,
|
||||
};
|
||||
if let Some(c) = enabled.get(next) {
|
||||
ui.ctx().memory_mut(|m| {
|
||||
m.request_focus(dropdown_row_id(c.id));
|
||||
m.move_focus(egui::FocusDirection::None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn sketch_menu_rows(
|
||||
app: &mut SolveApp,
|
||||
ui: &mut egui::Ui,
|
||||
cmds: &[&'static solidcraft_engine::CommandSpec],
|
||||
menu: &mut Option<(egui::Pos2, &'static str)>,
|
||||
toggle: &mut Option<&'static str>,
|
||||
) {
|
||||
let width = dropdown_width(app, ui, cmds);
|
||||
for c in cmds {
|
||||
let pinned = crate::toolbar_custom::pinned_on(app, "SKETCH", c.id).is_some();
|
||||
match dropdown_row(app, ui, c, width, pinned) {
|
||||
RowAction::None => {}
|
||||
RowAction::Menu(at) => *menu = Some((at, c.id)),
|
||||
RowAction::TogglePin => *toggle = Some(c.id),
|
||||
RowAction::Start => {
|
||||
app.start(c.id);
|
||||
ui.ctx().data_mut(|d| d.remove::<egui::Id>(egui::Id::new("sc_panel_open")));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A command being dragged: a toolbar button, or a row of a panel drop-down (`from_menu`).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ToolbarDrag {
|
||||
|
||||
@@ -104,13 +104,14 @@ pub fn layout<'a>(tab: &str, panel: &str, specs: &[&'a CommandSpec]) -> Option<(
|
||||
&[
|
||||
"sketch.line",
|
||||
"sketch.rectangle.two_point",
|
||||
"sketch.rectangle.three_point",
|
||||
"sketch.rectangle.center",
|
||||
"sketch.circle.center",
|
||||
"sketch.circle.two_point",
|
||||
"sketch.arc.three_point",
|
||||
"sketch.slot.center_to_center",
|
||||
"sketch.polygon.inscribed",
|
||||
"sketch.spline.fit_point",
|
||||
"sketch.point",
|
||||
],
|
||||
7,
|
||||
8,
|
||||
tab,
|
||||
),
|
||||
("SOLID", "CONFIGURE") => (&["parameters.change", "config.table"], 2, tab),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{"note":"#191: Point is a default Sketch CREATE button; the existing six drawing buttons remain available."},
|
||||
{"note":"#191: Point remains a default Sketch CREATE button alongside the drawing families."},
|
||||
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
|
||||
{"expect":{"shown_handles":["button:sketch.line","button:sketch.rectangle.two_point","button:sketch.rectangle.three_point","button:sketch.rectangle.center","button:sketch.circle.center","button:sketch.circle.two_point","button:sketch.point"]}},
|
||||
{"expect":{"shown_handles":["button:sketch.line","family:Rectangle","family:Circle","family:Arc","family:Slot","family:Polygon","family:Spline","button:sketch.point"]}},
|
||||
{"click":{"handle":"button:sketch.point"}},
|
||||
{"expect":{"tool":"sketch.point","dialog":null}},
|
||||
{"click":{"sketch":[20,15]}},
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
[
|
||||
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
|
||||
{"click":{"handle":"family:Rectangle"}},
|
||||
{"expect":{"shown_handles":["dropdown:sketch.rectangle.two_point","dropdown:sketch.rectangle.three_point","dropdown:sketch.rectangle.center"],"hidden_handles":["dropdown:sketch.circle.center"]}},
|
||||
{"key":"ArrowDown"},
|
||||
{"expect":{"shown_handles":["dropdown_hot:sketch.rectangle.two_point"]}},
|
||||
{"key":"ArrowUp"},
|
||||
{"expect":{"shown_handles":["dropdown_hot:sketch.rectangle.center"]}},
|
||||
{"key":"Enter"},
|
||||
{"expect":{"tool":"sketch.rectangle.center"}},
|
||||
{"click":{"sketch":[0,0]}},
|
||||
{"click":{"sketch":[20,15]}},
|
||||
{"key":"Escape"},
|
||||
{"click":{"handle":"button:sketch.rectangle.two_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.center"}},
|
||||
{"key":"Escape"},
|
||||
{"call":"ui.documents","params":{"action":"new"}},
|
||||
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
|
||||
{"click":{"handle":"button:sketch.rectangle.two_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.two_point"}},
|
||||
{"key":"Escape"},
|
||||
{"call":"ui.documents","params":{"action":"switch","index":0}},
|
||||
{"click":{"handle":"button:sketch.rectangle.two_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.center"}},
|
||||
{"key":"Escape"},
|
||||
{"start":"sketch.finish"},
|
||||
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
|
||||
{"click":{"handle":"button:sketch.rectangle.two_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.center"}},
|
||||
{"key":"Escape"},
|
||||
{"call":"ui.resize","params":{"width":640,"height":720}},
|
||||
{"click":{"handle":"panel:SKETCH:CREATE"}},
|
||||
{"expect":{"shown_handles":["create_family:Rectangle","create_family:Circle"],"hidden_handles":["dropdown:sketch.rectangle.center"]}},
|
||||
{"click":{"handle":"create_family:Rectangle"}},
|
||||
{"click":{"handle":"dropdown:sketch.rectangle.three_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.three_point","errors":0}},
|
||||
{"key":"Escape"},
|
||||
{"call":"ui.resize","params":{"width":1280,"height":800}},
|
||||
{"click":{"handle":"button:sketch.rectangle.two_point"}},
|
||||
{"expect":{"tool":"sketch.rectangle.three_point","errors":0}}
|
||||
]
|
||||
Reference in new issue
Block a user