//! The browser (left): document settings, origin, bodies and sketches. use egui::{Color32, RichText, Sense, vec2}; use serde_json::json; use solvecraft_engine::Sel; use solvecraft_engine::doc::FeatureKind; use crate::theme::Tokens; use crate::{SolveApp, icons}; fn row(ui: &mut egui::Ui, icon: &str, label: &str, color: Option, selected: bool) -> egui::Response { let t = Tokens::get(); let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 20.0), Sense::click()); if selected { ui.painter().rect_filled(r, 2.0, t.accent_soft); } else if resp.hovered() { ui.painter().rect_filled(r, 2.0, t.hover); } let ir = egui::Rect::from_min_size(r.min + vec2(2.0, 2.0), vec2(16.0, 16.0)); icons::paint(ui.painter(), ir, icon, t.icon, t.icon_fill, t.accent); ui.painter().text(r.min + vec2(22.0, 10.0), egui::Align2::LEFT_CENTER, label, egui::FontId::proportional(12.5), color.unwrap_or(t.text)); resp } fn eye(ui: &mut egui::Ui, visible: bool) -> bool { let t = Tokens::get(); let (r, resp) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::click()); icons::paint(ui.painter(), r, "eye", if visible { t.icon } else { t.border }, t.icon_fill, t.accent); resp.on_hover_text(if visible { "Hide" } else { "Show" }).clicked() } pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) { let t = Tokens::get(); egui::Panel::left("sc_browser") .exact_size(230.0) .resizable(false) .frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(8, 6))) .show(ui, |ui| { ui.label(RichText::new("BROWSER").size(11.0).strong().color(t.text_dim)); ui.add_space(4.0); let st = app.session.model.state(); egui::ScrollArea::vertical().show(ui, |ui| { ui.horizontal(|ui| { let (r, _) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::hover()); icons::paint(ui.painter(), r, "box", t.icon, t.icon_fill, t.accent); ui.label(RichText::new(&app.session.doc.name).strong()); }); ui.indent("doc", |ui| { egui::CollapsingHeader::new("Document Settings").default_open(false).show(ui, |ui| { row(ui, "settings", &format!("Units: {}", app.session.doc.units), None, false); let n = app.session.doc.params.len(); if row(ui, "params", &format!("Parameters ({n})"), None, false).clicked() { app.start("ChangeParameterCommand"); } }); egui::CollapsingHeader::new("Origin").default_open(false).show(ui, |ui| { ui.horizontal(|ui| { if eye(ui, app.ui.show_origin) { app.ui.show_origin = !app.ui.show_origin; } ui.label("O, X, Y, Z"); }); for p in ["XY", "XZ", "YZ"] { row(ui, "plane", p, None, false); } }); egui::CollapsingHeader::new(format!("Bodies ({})", st.bodies.len())).default_open(true).show(ui, |ui| { for b in &st.bodies { let visible = !app.ui.hidden_bodies.contains(&b.name); let selected = app.session.selection.iter().any(|x| matches!(x, Sel::Body { name } if *name == b.name)); ui.horizontal(|ui| { if eye(ui, visible) { if visible { app.ui.hidden_bodies.push(b.name.clone()); } else { app.ui.hidden_bodies.retain(|n| n != &b.name); } } if row(ui, "body", &b.name, None, selected).clicked() { let _ = app.run("select.set", json!({"items": [{"type": "body", "name": b.name}]})); } }); } }); let sketches: Vec<(u64, String)> = app .session .doc .features .iter() .filter(|f| matches!(f.kind, FeatureKind::Sketch { .. })) .map(|f| (f.id, f.name.clone())) .collect(); egui::CollapsingHeader::new(format!("Sketches ({})", sketches.len())).default_open(true).show(ui, |ui| { ui.horizontal(|ui| { if eye(ui, app.ui.show_sketches) { app.ui.show_sketches = !app.ui.show_sketches; } ui.label(RichText::new("show unused sketches").size(11.0).color(t.text_dim)); }); for (id, name) in sketches { let active = app.session.active_sketch == Some(id); let r = if active { row(ui, "sketch", &format!("{name} (editing)"), Some(t.sketch_accent), true) } else { row(ui, "sketch", &name, None, false) }; if r.double_clicked() { let _ = app.run("SketchActivate", json!({"sketch": id})); } r.on_hover_text("Double-click to edit"); } }); }); }); }); }