Selection mode: the Modelling SELECT panel shows and clears component picking (#9382) (#9485)

* claim: #9382

* Show component picking in the Modelling Select panel (#9382)
This commit is contained in:
Colin Faulkingham
2026-10-30 19:03:10 +00:00
committed by GitHub
parent 4f05716f69
commit cdffdcbbef
5 changed files with 60 additions and 13 deletions
@@ -0,0 +1,10 @@
{
"en": { "ui.select_components_by_face": "Select components (a face picks its component)" },
"ar": { "ui.select_components_by_face": "تحديد المكوّنات (تحديد وجه يحدّد المكوّن الذي ينتمي إليه)" },
"de": { "ui.select_components_by_face": "Komponenten auswählen (eine Fläche wählt ihre Komponente)" },
"he": { "ui.select_components_by_face": "בחירת רכיבים (בחירת פאה בוחרת את הרכיב שלה)" },
"ja": { "ui.select_components_by_face": "コンポーネントを選択(面をクリックするとそのコンポーネントが選択されます)" },
"ko": { "ui.select_components_by_face": "구성요소 선택(면을 클릭하면 해당 구성요소 선택)" },
"vi": { "ui.select_components_by_face": "Chọn thành phần (chọn một mặt sẽ chọn thành phần chứa mặt đó)" },
"zh": { "ui.select_components_by_face": "选择组件(单击面即选择其所属组件)" }
}
+12
View File
@@ -368,6 +368,18 @@ pub struct UiState {
pub tool_cursor: bool,
}
impl UiState {
fn pick_mode(&self) -> &'static str {
if self.pick_components {
"component"
} else if self.pick_bodies {
"body"
} else {
"face"
}
}
}
impl Default for UiState {
fn default() -> Self {
UiState {
+1 -11
View File
@@ -85,7 +85,7 @@ fn stage_tools(app: &mut SolveApp, ui: &mut egui::Ui) {
Stage::Look => {
ui_kit::StageBar::caption(ui, app.tr("render.pick_label"));
let modes = ["body", "face", "component"];
let mut selected = modes.iter().position(|mode| *mode == current_pick_mode(app)).unwrap_or(0);
let mut selected = modes.iter().position(|mode| *mode == app.ui.pick_mode()).unwrap_or(0);
let pick = ui.add_enabled_ui(!crate::render_workspace::result_active(app), |ui| {
if ui_kit::segmented(
ui,
@@ -225,16 +225,6 @@ pub(crate) fn dock_chip(app: &mut SolveApp, ui: &mut egui::Ui, id: &'static str,
crate::shell::register_panel(app, id, Some(chip.response.rect), None);
}
fn current_pick_mode(app: &SolveApp) -> &'static str {
if app.ui.pick_components {
"component"
} else if app.ui.pick_bodies {
"body"
} else {
"face"
}
}
fn shots_pill(app: &mut SolveApp, ui: &mut egui::Ui, free: Rect) {
crate::shell::register_panel(app, "render:shots:show", None, None);
if app.preferences.render_shots_strip {
@@ -4431,6 +4431,35 @@ fn render_workspace_pick_stage_tools_filter_sample_clicks_with_shots_shown_and_h
assert!(h.app.tool.is_none() && h.app.dialog.is_none());
}
#[test]
fn modelling_select_panel_shows_and_clears_renders_component_pick() {
use egui::accesskit::{Role, Toggled};
const BODY: &str = "Select bodies (a face picks its body)";
const COMPONENT: &str = "Select components (a face picks its component)";
let (mut h, _) = render_pick_sample();
h.step(&json!({"click":{"handle":"render:pick:component"}})).unwrap();
enter(&mut h.app, false).unwrap();
h.settle();
h.step(&json!({"click":{"handle":"tab:VIEW"}})).unwrap();
h.step(&json!({"click":{"handle":"panel:VIEW:SELECT"}})).unwrap();
let check = |h: &Harness, body: bool, component: bool| {
assert_eq!((h.app.ui.pick_bodies, h.app.ui.pick_components), (body, component));
let tree = h.output.accesskit_update.as_ref().unwrap();
for (label, on) in [(BODY, body), (COMPONENT, component)] {
let node = tree.nodes.iter().find(|(_, n)| n.role() == Role::CheckBox && n.label() == Some(label));
assert_eq!(node.and_then(|(_, n)| n.toggled()), Some(if on { Toggled::True } else { Toggled::False }), "{label}");
}
};
check(&h, false, true);
h.step(&json!({"widget":"Faces"})).unwrap();
for (label, body, component) in [(COMPONENT, false, false), (BODY, true, false), (COMPONENT, false, true), (BODY, true, false)] {
h.step(&json!({"widget":label})).unwrap();
check(&h, body, component);
assert!(h.app.ui.pick_off.iter().any(|kind| kind == "faces"), "changing pick mode must preserve the panel's Faces filter");
}
}
#[test]
fn render_workspace_click_modifiers_clear_and_browser_share_selection() {
use solidcraft_engine::Sel;
+8 -2
View File
@@ -2881,8 +2881,14 @@ fn drag_ghost(app: &SolveApp, ctx: &egui::Context, id: &str, at: egui::Pos2, rem
pub(crate) fn selection_filter(app: &mut SolveApp, ui: &mut egui::Ui) {
let t = Tokens::get();
ui.label(egui::RichText::new(crate::i18n::tr_label("Selection filter")).color(t.text_dim).size(t.font_small));
if crate::ui_kit::checkbox(ui, crate::i18n::tr_label("Select bodies (a face picks its body)"), &mut app.ui.pick_bodies).changed() {
app.ui.pick_components = false;
for (mode, label) in [("body", app.tr_label("Select bodies (a face picks its body)")), ("component", app.tr("ui.select_components_by_face"))] {
let mut on = app.ui.pick_mode() == mode;
if ui_kit::checkbox(ui, label, &mut on).changed() {
// The Faces checkbox below is independent. The standalone command enables faces.
let filters = std::mem::take(&mut app.ui.pick_off);
let _ = app.run("select.filter", json!({"mode": if on { mode } else { "face" }}));
app.ui.pick_off = filters;
}
}
for (k, label) in [
("faces", "Faces"),