BREP_app 0.3.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
//! mode_bar — the special-mode EXIT controls, always pinned to the TOP-RIGHT
//! corner of the screen. Every special mode (reference-selection, sketch mode,
//! and any future mode) surfaces its Finish / Cancel here so the exit is in a
//! single, predictable place — the pattern the user asked for.
//!
//! It draws INTO a caller-owned `ui` (the shell owns the top-right `Area` and
//! stacks the context-action rail below it), and owns no model state.

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

/// The mode-exit card's transient UI state (the model lives in the engine).
#[derive(Default)]
pub struct ModeBar {
    /// Per-frame widget rects, published for the headed verifier.
    hits: HashMap<String, egui::Rect>,
}

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

    /// Draw the active special mode's exit controls as a card. No-op (draws
    /// nothing) in the normal modeling environment. Called from the shell inside
    /// the shared top-right overlay `Area`.
    pub fn card(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
        self.hits.clear();

        if state.ref_select_active() {
            self.reference_card(ui, state);
        } else if state.sketch_mode() {
            self.sketch_card(ui, state);
        }
    }

    /// Reference-selection: the running picked-name list (each with an ✕ to drop
    /// it) + Finish / Cancel. Picking itself happens by clicking in the viewport;
    /// this card is the whole picker UI now (the side panel is hidden).
    fn reference_card(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
        egui::Frame::popup(ui.style()).show(ui, |ui| {
            ui.set_max_width(260.0);
            ui.label(egui::RichText::new("Select reference").strong());
            ui.label(egui::RichText::new(state.ref_select_prompt()).weak().small());
            ui.label(
                egui::RichText::new("Click in the viewport to pick; drag to orbit.")
                    .weak()
                    .small(),
            );
            ui.separator();

            let names = state.ref_select_names();
            if names.is_empty() {
                ui.label(egui::RichText::new("(nothing picked yet)").weak());
            }
            let mut remove = None;
            for (i, name) in names.iter().enumerate() {
                ui.horizontal(|ui| {
                    ui.label(format!("\u{2022} {name}"));
                    let x = {
        let b = crate::icon_text::icon_button(ui, "\u{2716}").small();
        ui.add(b)
    };
                    self.hits.insert(format!("refsel:x{i}"), x.rect);
                    if x.clicked() {
                        remove = Some(i);
                    }
                });
            }
            if let Some(i) = remove {
                state.ref_select_remove(i);
            }

            ui.separator();
            ui.horizontal(|ui| {
                let finish = ui.button("Finish");
                self.hits.insert("refsel:finish".into(), finish.rect);
                if finish.clicked() {
                    state.finish_ref_select();
                }
                let cancel = ui.button("Cancel");
                self.hits.insert("refsel:cancel".into(), cancel.rect);
                if cancel.clicked() {
                    state.cancel_ref_select();
                }
            });
        });
    }

    /// Sketch mode: the sketch title + Finish (commit) / Cancel (discard). The
    /// drawing tools live in the sketch tool strip; the selection-driven
    /// constraint actions live in the shared context rail below this card.
    fn sketch_card(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
        egui::Frame::popup(ui.style()).show(ui, |ui| {
            let id = state.sketch_edit_feature_id().unwrap_or("").to_string();
            ui.label(egui::RichText::new(format!("Sketch: {id}")).strong());
            ui.horizontal(|ui| {
                let finish = ui.button("Finish").on_hover_text("Commit the sketch");
                self.hits.insert("sketch:finish".into(), finish.rect);
                if finish.clicked() {
                    let _ = state.exit_sketch_mode(true);
                }
                let cancel = ui
                    .button("Cancel")
                    .on_hover_text("Discard changes (deletes a new sketch)");
                self.hits.insert("sketch:cancel".into(), cancel.rect);
                if cancel.clicked() {
                    let _ = state.exit_sketch_mode(false);
                }
            });
        });
    }

    /// Published widget hit-rects for the headed verifier.
    #[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()
    }
}