use crate::automation::hit_keys::HitKeyDoc;
use crate::panels::file::FileAction;
use crate::panels::toolbar_button;
use crate::store::{ModelStore, SETTINGS_KEY};
use crate::workbench;
use brep_render::engine_state::EngineState;
use eframe::egui;
use std::collections::HashMap;
#[derive(Default)]
pub struct ToolbarOutcome {
pub file: Option<FileAction>,
pub workbench_button: Option<&'static str>,
pub bug_report: bool,
}
#[derive(Default)]
pub struct ToolbarPanel {
hits: HashMap<String, egui::Rect>,
}
impl ToolbarPanel {
pub fn new() -> Self {
Self::default()
}
pub fn show(
&mut self,
ui: &mut egui::Ui,
state: &mut EngineState,
store: &dyn ModelStore,
settings_open: &mut bool,
properties_open: &mut bool,
info_open: &mut bool,
) -> ToolbarOutcome {
self.hits.clear();
let mut outcome = ToolbarOutcome::default();
egui::containers::panel::Panel::top("brep-toolbar")
.resizable(false)
.show(ui, |ui| {
ui.add_space(3.0);
ui.horizontal_wrapped(|ui| {
self.workbench_selector(ui, state, store);
ui.separator();
outcome.file = self.file_actions(ui);
ui.separator();
self.edit_actions(ui, state);
ui.separator();
self.view_actions(ui, state, store);
ui.separator();
self.properties_action(ui, properties_open);
self.settings_action(ui, settings_open);
self.help_action(ui);
self.info_action(ui, info_open);
outcome.bug_report = self.bug_action(ui);
let current = state.settings.workbench.clone();
outcome.workbench_button = self.workbench_buttons_row(ui, ¤t);
});
ui.add_space(3.0);
});
outcome
}
fn workbench_selector(
&mut self,
ui: &mut egui::Ui,
state: &mut EngineState,
store: &dyn ModelStore,
) {
let current = state.settings.workbench.clone();
let options: Vec<(&str, &str, &str)> =
workbench::WORKBENCHES.iter().map(|w| (w.label, w.id, w.glyph)).collect();
let result =
toolbar_button::select(ui, "workbench", workbench::resolve(¤t).id, &options);
self.hits.insert("workbench".into(), result.header_rect);
for (id, rect) in &result.item_rects {
self.hits.insert(format!("workbench:item:{id}"), *rect);
}
if let Some(next) = result.changed {
let json = serde_json::json!({ "workbench": next }).to_string();
let _ = state.apply_settings_json(&json);
let _ = store.write(SETTINGS_KEY, &state.settings_json());
}
}
fn workbench_buttons_row(&mut self, ui: &mut egui::Ui, current: &str) -> Option<&'static str> {
let buttons = workbench::workbench_buttons(current);
if !buttons.is_empty() {
ui.separator();
}
self.draw_workbench_buttons(ui, &buttons)
}
fn draw_workbench_buttons(
&mut self,
ui: &mut egui::Ui,
buttons: &[&workbench::WorkbenchButton],
) -> Option<&'static str> {
let mut clicked = None;
for button in buttons {
let resp = toolbar_button::button(ui, button.glyph, button.tooltip);
self.hits.insert(format!("workbench:btn:{}", button.id), resp.rect);
if resp.clicked() {
clicked = Some(button.id);
}
}
clicked
}
fn help_action(&mut self, ui: &mut egui::Ui) {
let btn = toolbar_button::button(ui, "\u{2753}", "Help");
self.hits.insert("help".into(), btn.rect);
if btn.clicked() {
ui.ctx().open_url(egui::OpenUrl::new_tab(crate::automation::HELP_URL));
}
}
fn info_action(&mut self, ui: &mut egui::Ui, open: &mut bool) {
let btn = toolbar_button::toggle(ui, *open, "\u{2139}", "Info (licences and diagnostics)");
self.hits.insert("info".into(), btn.rect);
if btn.clicked() {
*open = !*open;
}
}
fn bug_action(&mut self, ui: &mut egui::Ui) -> bool {
let btn = toolbar_button::button(ui, "\u{1F41E}", "Submit Bug");
self.hits.insert("bug".into(), btn.rect);
btn.clicked()
}
fn properties_action(&mut self, ui: &mut egui::Ui, open: &mut bool) {
let btn = toolbar_button::toggle(ui, *open, "\u{E066}", "Part properties");
self.hits.insert("properties".into(), btn.rect);
if btn.clicked() {
*open = !*open;
}
}
fn settings_action(&mut self, ui: &mut egui::Ui, open: &mut bool) {
let btn = toolbar_button::toggle(ui, *open, "\u{2699}", "Settings");
self.hits.insert("settings".into(), btn.rect);
if btn.clicked() {
*open = !*open;
}
}
pub fn hits_json(&self) -> String {
crate::automation::hit_rects::hits_json(&self.hits)
}
fn file_actions(&mut self, ui: &mut egui::Ui) -> Option<FileAction> {
let mut action = None;
let new = toolbar_button::button(ui, "\u{E010}", "New");
self.hits.insert("file:new".into(), new.rect);
if new.clicked() {
action = Some(FileAction::New);
}
let open = toolbar_button::button(ui, "\u{E011}", "Open");
self.hits.insert("file:open".into(), open.rect);
if open.clicked() {
action = Some(FileAction::Open);
}
let save = toolbar_button::button(ui, "\u{E012}", "Save");
self.hits.insert("file:save".into(), save.rect);
if save.clicked() {
action = Some(FileAction::Save);
}
let save_as = toolbar_button::button(ui, "\u{E013}", "Save As");
self.hits.insert("file:saveas".into(), save_as.rect);
if save_as.clicked() {
action = Some(FileAction::SaveAs);
}
ui.separator();
let import = toolbar_button::button(ui, "\u{E014}", "Import STEP / IGES / STL / OBJ\u{2026}");
self.hits.insert("file:import".into(), import.rect);
if import.clicked() {
action = Some(FileAction::Import);
}
let export = toolbar_button::button(ui, "\u{E015}", "Export\u{2026} (STEP / IGES / STL)");
self.hits.insert("file:export".into(), export.rect);
if export.clicked() {
action = Some(FileAction::Export);
}
action
}
fn edit_actions(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
let sketch = state.sketch_mode();
let can_undo = if sketch { state.sketch_can_undo() } else { state.can_undo() };
let undo = toolbar_button::button_enabled(
ui,
can_undo,
"\u{E016}",
if sketch { "Undo sketch edit (Ctrl+Z)" } else { "Undo" },
);
self.hits.insert("undo".into(), undo.rect);
if undo.clicked() {
if sketch {
state.sketch_undo();
} else {
state.undo();
}
}
let can_redo = if sketch { state.sketch_can_redo() } else { state.can_redo() };
let redo = toolbar_button::button_enabled(
ui,
can_redo,
"\u{E017}",
if sketch { "Redo sketch edit (Ctrl+Y)" } else { "Redo" },
);
self.hits.insert("redo".into(), redo.rect);
if redo.clicked() {
if sketch {
state.sketch_redo();
} else {
state.redo();
}
}
}
fn view_actions(&mut self, ui: &mut egui::Ui, state: &mut EngineState, store: &dyn ModelStore) {
let wire = state.settings.wireframe;
let wf = toolbar_button::toggle(ui, wire, "\u{1F578}", "Wireframe");
self.hits.insert("wireframe".into(), wf.rect);
if wf.clicked() {
let next = !wire;
let _ = state.apply_settings_json(&format!("{{\"wireframe\": {next}}}"));
let _ = store.write(SETTINGS_KEY, &state.settings_json());
}
let is_persp =
matches!(state.camera.projection, brep_render::view::Projection::Perspective { .. });
let proj_tip = if is_persp {
"Perspective projection"
} else {
"Orthographic projection"
};
let proj = toolbar_button::toggle(ui, is_persp, "\u{E018}", proj_tip);
self.hits.insert("projection".into(), proj.rect);
if proj.clicked() {
let want_ortho = is_persp; let _ = state.apply_settings_json(&format!("{{\"orthographic\": {want_ortho}}}"));
let _ = store.write(SETTINGS_KEY, &state.settings_json());
}
for (glyph, label, key, on) in [
("\u{E028}", "Show faces", "show:faces", state.settings.show_faces),
("\u{E029}", "Show edges", "show:edges", state.settings.show_edges),
("\u{E02A}", "Show vertices", "show:vertices", state.settings.show_vertices),
] {
let btn = toolbar_button::toggle(ui, on, glyph, label);
self.hits.insert(key.into(), btn.rect);
if btn.clicked() {
let field = match key {
"show:faces" => "showFaces",
"show:edges" => "showEdges",
_ => "showVertices",
};
let next = !on;
let _ = state.apply_settings_json(&format!("{{\"{field}\": {next}}}"));
let _ = store.write(SETTINGS_KEY, &state.settings_json());
}
}
let fit = toolbar_button::button(ui, "\u{26F6}", "Zoom to fit");
self.hits.insert("fit".into(), fit.rect);
if fit.clicked() {
state.zoom_to_fit();
}
}
}
pub static HIT_KEYS: &[HitKeyDoc] = &[
HitKeyDoc { panel: "toolbar", prefix: "file:new", meaning: "new document", command: Some("doc_new") },
HitKeyDoc { panel: "toolbar", prefix: "file:open", meaning: "open", command: Some("doc_load") },
HitKeyDoc { panel: "toolbar", prefix: "file:save", meaning: "save", command: Some("doc_json") },
HitKeyDoc { panel: "toolbar", prefix: "file:saveas", meaning: "save as", command: Some("doc_json") },
HitKeyDoc { panel: "toolbar", prefix: "file:import", meaning: "import a file", command: Some("doc_import") },
HitKeyDoc { panel: "toolbar", prefix: "file:export", meaning: "export", command: Some("doc_export") },
HitKeyDoc { panel: "toolbar", prefix: "undo", meaning: "undo", command: Some("undo") },
HitKeyDoc { panel: "toolbar", prefix: "redo", meaning: "redo", command: Some("redo") },
HitKeyDoc { panel: "toolbar", prefix: "fit", meaning: "zoom to fit", command: Some("zoom_to_fit") },
HitKeyDoc { panel: "toolbar", prefix: "projection", meaning: "toggle perspective/orthographic", command: Some("set_projection") },
HitKeyDoc { panel: "toolbar", prefix: "wireframe", meaning: "toggle wireframe", command: Some("settings_set") },
HitKeyDoc { panel: "toolbar", prefix: "show:faces", meaning: "toggle the shaded faces", command: Some("settings_set") },
HitKeyDoc { panel: "toolbar", prefix: "show:edges", meaning: "toggle the edges", command: Some("settings_set") },
HitKeyDoc { panel: "toolbar", prefix: "show:vertices", meaning: "toggle the vertex points", command: Some("settings_set") },
HitKeyDoc { panel: "toolbar", prefix: "properties", meaning: "open the part properties window", command: Some("part_properties_window") },
HitKeyDoc { panel: "toolbar", prefix: "settings", meaning: "open the settings window", command: Some("settings_window") },
HitKeyDoc { panel: "toolbar", prefix: "help", meaning: "open the help site", command: Some("help_open") },
HitKeyDoc { panel: "toolbar", prefix: "info", meaning: "open the info window (licences + diagnostics)", command: Some("info_window") },
HitKeyDoc { panel: "toolbar", prefix: "bug", meaning: "open the bug report", command: Some("bug_report_open") },
HitKeyDoc { panel: "toolbar", prefix: "workbench", meaning: "the workbench dropdown", command: Some("settings_set") },
HitKeyDoc { panel: "toolbar", prefix: "workbench:btn:", meaning: "the workbench dropdown button", command: Some("workbench_button") },
HitKeyDoc { panel: "toolbar", prefix: "workbench:item:", meaning: "pick a workbench from the open dropdown (workbench:item:id)", command: Some("settings_set") },
];