use brep_render::engine_state::{EngineState, SelectionFilter};
use eframe::egui;
use std::collections::HashMap;
const KINDS: [(&str, &str); 7] = [
("COMPONENT", "Component"),
("SOLID", "Solid"),
("SKETCH", "Sketch"),
("FACE", "Face"),
("EDGE", "Edge"),
("VERTEX", "Vertex"),
("PLANE", "Plane"),
];
#[derive(Default)]
pub struct SelectionPanel {
hits: HashMap<String, egui::Rect>,
}
impl SelectionPanel {
pub fn new() -> Self {
Self::default()
}
pub fn show_status_bar(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
self.hits.clear();
ui.horizontal_wrapped(|ui| {
let locked = state.ref_select_active();
if locked {
ui.label(egui::RichText::new("\u{1f512} Reference selection — filter locked").weak())
.on_hover_text(
"The pick filter is set by the field being referenced. \
Finish or cancel the reference selection to change it.",
);
} else {
ui.label("Pickable:");
}
ui.add_enabled_ui(!locked, |ui| {
let mut filter = state.selection_filter();
let before = filter;
let all_on = KINDS.iter().all(|(k, _)| filter.get(k));
let any_on = KINDS.iter().any(|(k, _)| filter.get(k));
let mut all_checked = all_on;
let resp = ui.add(
egui::Checkbox::new(&mut all_checked, "All").indeterminate(any_on && !all_on),
);
self.hits.insert("filter:ALL".into(), resp.rect);
if resp.changed() {
let target = toggle_all_target(&filter);
for (kind, _) in KINDS {
filter.set(kind, target);
}
}
for (kind, label) in KINDS {
let mut on = filter.get(kind);
let resp = ui.checkbox(&mut on, label);
self.hits.insert(format!("filter:{kind}"), resp.rect);
if resp.changed() {
filter.set(kind, on);
}
}
if filter != before {
state.set_selection_filter(filter);
}
});
});
}
pub fn clear_hits(&mut self) {
self.hits.clear();
}
#[cfg(target_arch = "wasm32")]
pub fn hits_json(&self) -> String {
let map: serde_json::Map<String, serde_json::Value> = self
.hits
.iter()
.map(|(k, r)| {
(
k.clone(),
serde_json::json!([r.min.x, r.min.y, r.width(), r.height()]),
)
})
.collect();
serde_json::Value::Object(map).to_string()
}
}
fn toggle_all_target(filter: &SelectionFilter) -> bool {
!KINDS.iter().all(|(k, _)| filter.get(k))
}
#[cfg(test)]
mod tests {
use super::*;
use brep_render::engine_state::EngineState;
#[test]
fn toggle_all_target_semantics() {
let all = SelectionFilter::default();
assert!(
all.solid && all.face && all.edge && all.vertex && all.plane && all.component,
"default is all-on"
);
assert!(!toggle_all_target(&all), "all-on toggles to off");
let partial = SelectionFilter {
solid: false,
sketch: false,
face: true,
edge: false,
vertex: false,
plane: false,
component: false,
};
assert!(toggle_all_target(&partial), "partial toggles to on");
let none = SelectionFilter {
solid: false,
sketch: false,
face: false,
edge: false,
vertex: false,
plane: false,
component: false,
};
assert!(toggle_all_target(&none), "none toggles to on");
}
#[test]
fn status_bar_publishes_hit_rects() {
let mut panel = SelectionPanel::new();
let mut state = EngineState::new();
let ctx = egui::Context::default();
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
panel.show_status_bar(ui, &mut state);
});
for key in [
"filter:COMPONENT",
"filter:SOLID",
"filter:FACE",
"filter:EDGE",
"filter:VERTEX",
"filter:PLANE",
"filter:ALL",
] {
assert!(
panel.hits.contains_key(key),
"status bar must publish `{key}`, got {:?}",
panel.hits.keys().collect::<Vec<_>>()
);
}
}
#[test]
fn checkbox_drives_engine_filter() {
use egui_kittest::kittest::Queryable;
let mut harness = egui_kittest::Harness::new_ui_state(
|ui, (panel, engine): &mut (SelectionPanel, EngineState)| {
panel.show_status_bar(ui, engine);
},
(SelectionPanel::new(), EngineState::new()),
);
harness.run();
assert!(harness.state().1.selection_filter().face, "default: face pickable");
harness.get_by_label("Face").click();
harness.run();
assert!(
!harness.state().1.selection_filter().face,
"unchecking Face must disable face-picking via set_selection_filter"
);
harness.get_by_label("Face").click();
harness.run();
assert!(
harness.state().1.selection_filter().face,
"rechecking Face must re-enable face-picking"
);
}
#[test]
fn all_checkbox_toggles_every_kind() {
use egui_kittest::kittest::Queryable;
let mut harness = egui_kittest::Harness::new_ui_state(
|ui, (panel, engine): &mut (SelectionPanel, EngineState)| {
panel.show_status_bar(ui, engine);
},
(SelectionPanel::new(), EngineState::new()),
);
harness.run();
let f = harness.state().1.selection_filter();
assert!(
f.solid && f.face && f.edge && f.vertex && f.plane && f.component,
"default: all kinds on"
);
harness.get_by_label("All").click();
harness.run();
let f = harness.state().1.selection_filter();
assert!(
!f.solid && !f.face && !f.edge && !f.vertex && !f.plane && !f.component,
"clicking All while all-on must clear every kind: {f:?}"
);
harness.get_by_label("All").click();
harness.run();
let f = harness.state().1.selection_filter();
assert!(
f.solid && f.face && f.edge && f.vertex && f.plane && f.component,
"clicking All again must set every kind on: {f:?}"
);
}
#[test]
fn plane_checkbox_drives_engine_filter() {
use egui_kittest::kittest::Queryable;
let mut harness = egui_kittest::Harness::new_ui_state(
|ui, (panel, engine): &mut (SelectionPanel, EngineState)| {
panel.show_status_bar(ui, engine);
},
(SelectionPanel::new(), EngineState::new()),
);
harness.run();
assert!(harness.state().1.selection_filter().plane, "default: plane pickable");
harness.get_by_label("Plane").click();
harness.run();
let f = harness.state().1.selection_filter();
assert!(
!f.plane,
"unchecking Plane must disable plane-picking via set_selection_filter"
);
assert!(f.face, "…and must not disturb the other kinds: {f:?}");
harness.get_by_label("Plane").click();
harness.run();
assert!(
harness.state().1.selection_filter().plane,
"rechecking Plane must re-enable plane-picking"
);
}
#[test]
fn ref_select_locks_the_filter_row() {
use egui_kittest::kittest::Queryable;
let mut state = EngineState::new();
state.begin_ref_select(
"f",
vec!["p".into()],
"Ref".into(),
vec!["FACE".into()],
false,
vec![],
);
assert!(state.ref_select_active(), "ref-select is active");
let constrained = state.selection_filter();
assert!(
constrained.face && !constrained.solid,
"field filter constrained to face-only: {constrained:?}"
);
let mut harness = egui_kittest::Harness::new_ui_state(
|ui, (panel, engine): &mut (SelectionPanel, EngineState)| {
panel.show_status_bar(ui, engine);
},
(SelectionPanel::new(), state),
);
harness.run();
harness.get_by_label("Solid").click();
harness.run();
assert_eq!(
harness.state().1.selection_filter(),
constrained,
"a locked checkbox click must not change the ref-select-constrained filter"
);
}
}