use super::ButtonState;
use brep_ecad_egui::{Action, Editor, FootprintEditor, SymbolEditor};
pub struct Editors {
pub diagram: Editor,
pub pcb: Editor,
pub symbol: SymbolEditor,
pub pads: FootprintEditor,
focus: Option<(Target, String)>,
}
impl Editors {
pub fn new() -> Self {
let mut diagram = Editor::default();
diagram.set_document(brep_ecad_core::Document::new(brep_ecad_core::DocumentKind::Wiring));
let mut pcb = Editor::default();
pcb.set_document(brep_ecad_core::Document::new(brep_ecad_core::DocumentKind::Schematic));
Self {
diagram,
pcb,
symbol: SymbolEditor::blank(),
pads: FootprintEditor::new(Default::default(), None, Vec::new()),
focus: None,
}
}
pub fn focus(&mut self, target: Target, number: String) {
self.focus = Some((target, number));
}
pub fn apply_focus(&mut self, target: Target) -> bool {
match self.focus.take_if(|(asked, _)| *asked == target) {
Some((Target::Symbol, number)) => self.symbol.select_pin(&number),
Some((Target::Pads, number)) => self.pads.select_pad(&number),
_ => false,
}
}
pub fn selection_key(&mut self, target: Target, delete: bool) {
match target {
Target::Diagram | Target::Pcb => {
let editor = if target == Target::Diagram { &mut self.diagram } else { &mut self.pcb };
let action = match (editor.view, delete) {
(brep_ecad_egui::View::Board, true) => "board.delete",
(brep_ecad_egui::View::Board, false) => "board.cancel",
(_, true) => "sheet.delete",
(_, false) => "sheet.cancel",
};
editor.run_action(action);
}
Target::Symbol => { self.symbol.run_action(if delete { "symbol.delete" } else { "symbol.tool.select" }); }
Target::Pads => { self.pads.run_action(if delete { "pads.delete" } else { "pads.tool.select" }); }
}
}
}
impl Default for Editors {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Target {
Diagram,
Pcb,
Symbol,
Pads,
}
impl Target {
pub const ALL: [Target; 4] = [Target::Diagram, Target::Pcb, Target::Symbol, Target::Pads];
pub fn workbench_id(self) -> &'static str {
match self {
Target::Diagram => "diagram",
Target::Pcb => "pcb",
Target::Symbol => "symbol",
Target::Pads => "pads",
}
}
pub fn of_workbench(id: &str) -> Option<Target> {
Target::ALL.into_iter().find(|target| target.workbench_id() == id)
}
}
pub const LIBRARY_PANEL_ID: &str = "ecadLibrary";
pub const INSPECTOR_PANEL_ID: &str = "ecadInspector";
pub fn split(id: &str) -> Option<(Target, &str)> {
if let Some(action) = id.strip_prefix("diagram.") {
Some((Target::Diagram, action))
} else if let Some(action) = id.strip_prefix("pcb.") {
Some((Target::Pcb, action))
} else if id.starts_with("symbol.") {
Some((Target::Symbol, id))
} else if id.starts_with("pads.") {
Some((Target::Pads, id))
} else {
None
}
}
pub const BREP_OWN: &[&str] = &[];
pub fn ecad_action(id: &str) -> Option<(Target, &str)> {
let (target, action) = split(id)?;
let known = match target {
Target::Diagram | Target::Pcb => brep_ecad_egui::actions().any(|a| a.id == action),
Target::Symbol => brep_ecad_egui::symbol_actions().iter().any(|a| a.id == action),
Target::Pads => brep_ecad_egui::pad_actions().iter().any(|a| a.id == action),
};
known.then_some((target, action))
}
pub const LAYER_MENU_ID: &str = "pcb.board.layer";
pub const PUT_DOWN: &[&str] = &[
"sheet.place.done",
"sheet.wire.done",
"sheet.label.done",
"sheet.move.cancel",
"sheet.junction.done",
"board.route.done",
];
const DISABLED_BECAUSE: &[(&str, &str)] = &[
("view.schematic", "the autorouter is running"),
("view.board", "the autorouter is running"),
("sheet.wire.finish", "no wire is being drawn"),
("board.route.via", "a via needs a track being drawn on a board with more than one copper layer"),
("board.route.finish", "no track is being drawn"),
("board.zone.finish", "a zone needs three corners before it can be closed"),
("board.zones.fill", "the board has no zones"),
];
const DISABLED_UNEXPLAINED: &str = "the editor does not allow it right now";
pub fn active_target(state: &ButtonState) -> Option<Target> {
state.ecad?;
if state.engine.any_sketch_mode() {
return None;
}
Target::of_workbench(&state.engine.settings.workbench)
}
struct Reading {
offered: bool,
enabled: bool,
pressed: Option<bool>,
caption: String,
}
fn read_in<T: 'static>(
mut table: impl Iterator<Item = &'static Action<T>>,
editor: &T,
action: &str,
) -> Option<Reading> {
let action = table.find(|a| a.id == action)?;
Some(Reading {
offered: action.offered(editor),
enabled: action.enabled(editor),
pressed: action.pressed(editor),
caption: action.caption(editor),
})
}
fn read(editors: &Editors, target: Target, action: &str) -> Option<Reading> {
match target {
Target::Diagram => read_in(brep_ecad_egui::actions(), &editors.diagram, action),
Target::Pcb => read_in(brep_ecad_egui::actions(), &editors.pcb, action),
Target::Symbol => read_in(brep_ecad_egui::symbol_actions().iter(), &editors.symbol, action),
Target::Pads => read_in(brep_ecad_egui::pad_actions().iter(), &editors.pads, action),
}
}
fn reading(state: &ButtonState, id: &str) -> Option<Reading> {
let (target, action) = split(id)?;
if active_target(state) != Some(target) {
return None;
}
read(state.ecad?, target, action)
}
pub fn offered(state: &ButtonState, id: &str) -> bool {
reading(state, id).is_some_and(|r| r.offered)
}
pub fn pressed(state: &ButtonState, id: &str) -> bool {
reading(state, id).is_some_and(|r| r.pressed == Some(true))
}
pub fn disabled(state: &ButtonState, id: &str) -> Option<&'static str> {
let reading = reading(state, id)?;
if reading.enabled {
return None;
}
let action = split(id)?.1;
Some(
DISABLED_BECAUSE
.iter()
.find(|(known, _)| *known == action)
.map_or(DISABLED_UNEXPLAINED, |(_, why)| *why),
)
}
pub fn caption(state: &ButtonState, id: &str) -> String {
reading(state, id).map_or_else(
|| super::button_by_declared_id(id).map_or_else(String::new, |b| b.tooltip.to_string()),
|r| r.caption,
)
}
pub fn layer_menu_caption(state: &ButtonState) -> String {
let on = super::pcb::LAYERS
.iter()
.find(|entry| entry.offered(state) && entry.is_pressed(state))
.map(|entry| entry.detail(state));
match on {
Some(layer) => format!("Copper layer: {layer}"),
None => "Copper layer".to_string(),
}
}
pub fn dispatch(editors: &mut Editors, id: &str) -> Option<bool> {
dispatch_in(&super::declared_workbenches(), editors, id)
}
fn dispatch_in(registry: &[&'static super::Workbench], editors: &mut Editors, id: &str) -> Option<bool> {
let button = super::button_in(registry, id)?;
if !button.menu.is_empty() {
return None;
}
let (target, action) = ecad_action(id)?;
Some(match target {
Target::Diagram => editors.diagram.run_action(action),
Target::Pcb => editors.pcb.run_action(action),
Target::Symbol => editors.symbol.run_action(action),
Target::Pads => editors.pads.run_action(action),
})
}
macro_rules! ecad_button {
($id:literal, $glyph:literal, $tooltip:literal, $path:literal, toggle) => {
$crate::workbench::WorkbenchButton {
id: $id,
glyph: $glyph,
tooltip: $tooltip,
ribbon_path: $path,
size: $crate::workbench::CommandSize::Compact,
when: Some(|s| $crate::workbench::ecad::offered(s, $id)),
pressed: Some(|s| $crate::workbench::ecad::pressed(s, $id)),
disabled: Some(|s| $crate::workbench::ecad::disabled(s, $id)),
caption: Some(|s| $crate::workbench::ecad::caption(s, $id)),
menu: &[],
}
};
($id:literal, $glyph:literal, $tooltip:literal, $path:literal, command) => {
$crate::workbench::WorkbenchButton {
id: $id,
glyph: $glyph,
tooltip: $tooltip,
ribbon_path: $path,
size: $crate::workbench::CommandSize::Compact,
when: Some(|s| $crate::workbench::ecad::offered(s, $id)),
pressed: None,
disabled: Some(|s| $crate::workbench::ecad::disabled(s, $id)),
caption: Some(|s| $crate::workbench::ecad::caption(s, $id)),
menu: &[],
}
};
}
pub(crate) use ecad_button;
pub(crate) fn no_features(_feature: &super::FeatureInfo<'_>) -> bool {
false
}