brep_app/panels/selection.rs
1//! Selection panel — the **selection filter** (which entity kinds a viewport
2//! click may pick). Follows the panel pattern: a small state struct + a
3//! `show(&mut self, ui, state)` the shell calls once; `EngineState` stays the
4//! single brain (it owns the filter + the selection), borrowed in.
5//!
6//! * **Filter** — a toggle per kind (SOLID / FACE / EDGE / VERTEX). The engine
7//! honors it in `select_top_at` (via `pick_filtered` with the enabled kinds),
8//! so a plain click grabs only an allowed kind — a FACE-only filter selects a
9//! face, a SOLID-only filter the owning solid. Defaults to ALL kinds enabled
10//! (everything under the cursor is pickable, highest-priority kind wins).
11//! Reference-selection mode temporarily constrains it to the active field's
12//! allowed kinds (this panel is hidden then, but the constraint still governs
13//! picking + hover). The model state is the engine's `selection_filter`; the
14//! panel just reads/writes it.
15//!
16//! The quick actions on the current selection (Clear / Hide / Edit-owning-feature
17//! + the feature-from-selection actions) moved to the dedicated
18//! [`crate::panels::context_bar`] (the engine-native successor to the old app's
19//! floating selection action bar), which supersedes the minimal action bar this
20//! panel used to draw.
21//!
22//! The panel owns only the per-frame `hits` map (widget screen rects) the headed
23//! verifier reads to drive real clicks, exactly like the toolbar/history panels.
24
25use brep_render::engine_state::EngineState;
26use eframe::egui;
27use std::collections::HashMap;
28
29/// The pickable kinds, in the order the filter row draws them. `(key, label)`:
30/// the `key` is the engine kind name + the `hits` map key suffix.
31const KINDS: [(&str, &str); 4] = [
32 ("SOLID", "Solid"),
33 ("FACE", "Face"),
34 ("EDGE", "Edge"),
35 ("VERTEX", "Vertex"),
36];
37
38/// The selection panel's own state: the per-frame map of egui widget screen
39/// rects, published to JS for the headed verifier to drive real clicks. Rebuilt
40/// each frame (there is no DOM — egui is drawn on the canvas).
41#[derive(Default)]
42pub struct SelectionPanel {
43 hits: HashMap<String, egui::Rect>,
44}
45
46impl SelectionPanel {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 /// Draw the selection filter. Rebuilds `hits` each frame as it draws.
52 pub fn show(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
53 self.hits.clear();
54 egui::CollapsingHeader::new("Selection")
55 .id_salt("selection")
56 .default_open(true)
57 .show(ui, |ui| {
58 self.filter_row(ui, state);
59 });
60 }
61
62 /// The published widget hit-rects (egui points) for the headed verifier —
63 /// `filter:SOLID|FACE|EDGE|VERTEX`.
64 #[cfg(target_arch = "wasm32")]
65 pub fn hits_json(&self) -> String {
66 let map: serde_json::Map<String, serde_json::Value> = self
67 .hits
68 .iter()
69 .map(|(k, r)| {
70 (
71 k.clone(),
72 serde_json::json!([r.min.x, r.min.y, r.width(), r.height()]),
73 )
74 })
75 .collect();
76 serde_json::Value::Object(map).to_string()
77 }
78
79 /// The filter toggles: one `toggle_value` per pickable kind, reflecting the
80 /// LIVE engine `selection_filter` so the row is always honest. On any change
81 /// the whole filter is written back through `set_selection_filter`.
82 fn filter_row(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
83 ui.label("Pickable kinds — a click selects the highest-priority one enabled:");
84 let mut filter = state.selection_filter();
85 let before = filter;
86 ui.horizontal_wrapped(|ui| {
87 for (kind, label) in KINDS {
88 let mut on = filter.get(kind);
89 let resp = ui.toggle_value(&mut on, label);
90 self.hits.insert(format!("filter:{kind}"), resp.rect);
91 if resp.changed() {
92 filter.set(kind, on);
93 }
94 }
95 });
96 if filter != before {
97 state.set_selection_filter(filter);
98 }
99 }
100}