use crate::automation::hit_keys::HitKeyDoc;
use crate::panels::toolbar_button;
use crate::workbench;
use brep_render::engine_state::EngineState;
use brep_render::features;
use eframe::egui;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Default)]
pub struct WorkbenchToolbarOutcome {
pub feature: Option<String>,
pub constraint: Option<String>,
}
struct ActionButton {
id: String,
glyph: String,
tooltip: String,
}
#[derive(Default)]
pub struct WorkbenchToolbarPanel {
hits: HashMap<String, egui::Rect>,
features: Vec<ActionButton>,
cached_workbench: Option<String>,
constraints: Vec<ActionButton>,
}
impl WorkbenchToolbarPanel {
pub fn new() -> Self {
Self::default()
}
pub fn visible(state: &EngineState) -> bool {
state.settings.show_workbench_toolbar && !state.sketch_mode() && !state.ref_select_active()
}
pub fn show(&mut self, ui: &mut egui::Ui, state: &EngineState) -> WorkbenchToolbarOutcome {
self.hits.clear();
let mut outcome = WorkbenchToolbarOutcome::default();
if !Self::visible(state) {
return outcome;
}
let active = state.settings.workbench.clone();
self.refresh_buttons(&active);
let with_constraints =
workbench::panel_visible(&active, workbench::assembly::CONSTRAINTS_PANEL_ID);
if self.features.is_empty() && !with_constraints {
return outcome;
}
egui::containers::panel::Panel::top("brep-workbench-toolbar")
.resizable(false)
.show(ui, |ui| {
ui.add_space(2.0);
ui.horizontal_wrapped(|ui| {
if !self.features.is_empty() {
caption(ui, "Features");
outcome.feature = Self::draw_group(ui, &self.features, &mut self.hits);
}
if with_constraints {
if !self.features.is_empty() {
ui.separator();
}
caption(ui, "Constraints");
outcome.constraint =
Self::draw_group(ui, &self.constraints, &mut self.hits);
}
});
ui.add_space(2.0);
});
outcome
}
fn refresh_buttons(&mut self, active: &str) {
if self.cached_workbench.as_deref() != Some(active) {
self.features = feature_buttons(active);
self.cached_workbench = Some(active.to_string());
}
if self.constraints.is_empty() {
self.constraints = constraint_buttons();
}
}
fn draw_group(
ui: &mut egui::Ui,
buttons: &[ActionButton],
hits: &mut HashMap<String, egui::Rect>,
) -> Option<String> {
let mut clicked = None;
for button in buttons {
let resp = toolbar_button::button(ui, &button.glyph, &button.tooltip);
hits.insert(format!("wbtb:{}", button.id), resp.rect);
if resp.clicked() {
clicked = button.id.split_once(':').map(|(_, payload)| payload.to_string());
}
}
clicked
}
pub fn hits_json(&self) -> String {
crate::automation::hit_rects::hits_json(&self.hits)
}
}
fn caption(ui: &mut egui::Ui, text: &str) {
ui.label(egui::RichText::new(text).weak().small());
}
fn feature_buttons(active: &str) -> Vec<ActionButton> {
let catalogue = features::feature_catalogue();
let mut buttons = Vec::new();
if let Some(list) = catalogue.get("features").and_then(Value::as_array) {
for feature in list {
let ty = feature.get("type").and_then(Value::as_str).unwrap_or("");
if ty.is_empty() || !workbench::includes_feature(active, ty) {
continue;
}
let name = feature
.get("longName")
.and_then(Value::as_str)
.unwrap_or(ty)
.to_string();
let glyph = match features::feature_icon(ty) {
Some(icon) => icon.to_string(),
None => feature
.get("shortName")
.and_then(Value::as_str)
.unwrap_or(ty)
.to_string(),
};
buttons.push(ActionButton {
id: format!("feature:{ty}"),
glyph,
tooltip: format!("Add {name}"),
});
}
}
buttons
}
fn constraint_buttons() -> Vec<ActionButton> {
brep_render::brep_kernel::CONSTRAINT_TYPES
.iter()
.map(|def| ActionButton {
id: format!("constraint:{}", def.type_id),
glyph: def.icon.to_string(),
tooltip: format!("Add {} constraint from the selection", def.label),
})
.collect()
}
pub static HIT_KEYS: &[HitKeyDoc] = &[
HitKeyDoc { panel: "wbtoolbar", prefix: "wbtb:feature:", meaning: "add a feature of that type (one button per feature the active workbench offers)", command: Some("feature_add") },
HitKeyDoc { panel: "wbtoolbar", prefix: "wbtb:constraint:", meaning: "add an assembly constraint of that type, seeded from the current selection", command: Some("assembly_add_constraint") },
];