BREP_app 0.1.0

The BREP CAD application: an eframe (egui + wgpu) host that draws the brep-render 3D engine into an egui frame — native + wasm from one codebase.
Documentation
//! Selection panel — the **selection filter** (which entity kinds a viewport
//! click may pick). Follows the panel pattern: a small state struct + a
//! `show(&mut self, ui, state)` the shell calls once; `EngineState` stays the
//! single brain (it owns the filter + the selection), borrowed in.
//!
//! * **Filter** — a toggle per kind (SOLID / FACE / EDGE / VERTEX). The engine
//!   honors it in `select_top_at` (via `pick_filtered` with the enabled kinds),
//!   so a plain click grabs only an allowed kind — a FACE-only filter selects a
//!   face, a SOLID-only filter the owning solid. Defaults to ALL kinds enabled
//!   (everything under the cursor is pickable, highest-priority kind wins).
//!   Reference-selection mode temporarily constrains it to the active field's
//!   allowed kinds (this panel is hidden then, but the constraint still governs
//!   picking + hover). The model state is the engine's `selection_filter`; the
//!   panel just reads/writes it.
//!
//! The quick actions on the current selection (Clear / Hide / Edit-owning-feature
//! + the feature-from-selection actions) moved to the dedicated
//! [`crate::panels::context_bar`] (the engine-native successor to the old app's
//! floating selection action bar), which supersedes the minimal action bar this
//! panel used to draw.
//!
//! The panel owns only the per-frame `hits` map (widget screen rects) the headed
//! verifier reads to drive real clicks, exactly like the toolbar/history panels.

use brep_render::engine_state::EngineState;
use eframe::egui;
use std::collections::HashMap;

/// The pickable kinds, in the order the filter row draws them. `(key, label)`:
/// the `key` is the engine kind name + the `hits` map key suffix.
const KINDS: [(&str, &str); 4] = [
    ("SOLID", "Solid"),
    ("FACE", "Face"),
    ("EDGE", "Edge"),
    ("VERTEX", "Vertex"),
];

/// The selection panel's own state: the per-frame map of egui widget screen
/// rects, published to JS for the headed verifier to drive real clicks. Rebuilt
/// each frame (there is no DOM — egui is drawn on the canvas).
#[derive(Default)]
pub struct SelectionPanel {
    hits: HashMap<String, egui::Rect>,
}

impl SelectionPanel {
    pub fn new() -> Self {
        Self::default()
    }

    /// Draw the selection filter. Rebuilds `hits` each frame as it draws.
    pub fn show(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
        self.hits.clear();
        egui::CollapsingHeader::new("Selection")
            .id_salt("selection")
            .default_open(true)
            .show(ui, |ui| {
                self.filter_row(ui, state);
            });
    }

    /// The published widget hit-rects (egui points) for the headed verifier —
    /// `filter:SOLID|FACE|EDGE|VERTEX`.
    #[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()
    }

    /// The filter toggles: one `toggle_value` per pickable kind, reflecting the
    /// LIVE engine `selection_filter` so the row is always honest. On any change
    /// the whole filter is written back through `set_selection_filter`.
    fn filter_row(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
        ui.label("Pickable kinds — a click selects the highest-priority one enabled:");
        let mut filter = state.selection_filter();
        let before = filter;
        ui.horizontal_wrapped(|ui| {
            for (kind, label) in KINDS {
                let mut on = filter.get(kind);
                let resp = ui.toggle_value(&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);
        }
    }
}