diff --git a/crates/topola-egui/src/appearance_panel.rs b/crates/topola-egui/src/appearance_panel.rs index c4b03d3..326e466 100644 --- a/crates/topola-egui/src/appearance_panel.rs +++ b/crates/topola-egui/src/appearance_panel.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT +use egui::{widget_text::WidgetText, Context, Grid, ScrollArea, SidePanel}; use topola::board::{AccessMesadata, Board}; pub struct AppearancePanel { @@ -10,40 +11,63 @@ pub struct AppearancePanel { // In2.Cu shall be #ce7d2c (#e8c39e when selected). pub visible: Box<[bool]>, - pub active_layer: usize, + pub active_layer: Option, } impl AppearancePanel { pub fn new(board: &Board) -> Self { let layer_count = board.layout().drawing().layer_count(); - let visible = std::iter::repeat(true) + let visible = core::iter::repeat(true) .take(layer_count) - .collect::>() - .into_boxed_slice(); + .collect::>(); Self { visible, - active_layer: 0, + active_layer: Some(0), } } - pub fn update(&mut self, ctx: &egui::Context, board: &Board) { - egui::SidePanel::right("right_side_panel").show(ctx, |ui| { + pub fn update(&mut self, ctx: &Context, board: &Board) { + SidePanel::right("appearance_panel").show(ctx, |ui| { ui.label("Layers"); + let row_height = ui.spacing().interact_size.y; + ScrollArea::vertical().show_rows( + ui, + row_height, + self.visible.len(), + |ui, row_range| { + let start_row = row_range.start; + Grid::new("appearance_layers") + .min_col_width(ui.spacing().icon_width) + .num_columns(3) + .start_row(start_row) + .show(ui, |ui| { + for (layer, visible) in self.visible[row_range].iter_mut().enumerate() { + let layer = layer + start_row; + let layername = + board.layout().drawing().rules().layer_layername(layer); - for (layer, visible) in self.visible.iter_mut().enumerate() { - let layername = board - .layout() - .drawing() - .rules() - .layer_layername(layer) - .unwrap_or("Unnamed layer"); - - let old = *visible; - ui.checkbox(visible, layername); - if old != *visible { - self.active_layer = layer; - } - } + // unnamed layers can't be used for routing + if layername.is_some() { + ui.radio_value( + &mut self.active_layer, + Some(layer), + WidgetText::default(), + ); + } else { + // dummy item to bump the grid + ui.label(""); + } + ui.checkbox(visible, WidgetText::default()); + ui.label( + layername + .map(|i| i.to_string()) + .unwrap_or_else(|| format!("{} - Unnamed layer", layer)), + ); + ui.end_row(); + } + }) + }, + ); }); } } diff --git a/crates/topola-egui/src/menu_bar.rs b/crates/topola-egui/src/menu_bar.rs index 39bfd26..93f21ac 100644 --- a/crates/topola-egui/src/menu_bar.rs +++ b/crates/topola-egui/src/menu_bar.rs @@ -8,6 +8,7 @@ use topola::{ autorouter::{ execution::Command, invoker::InvokerError, selection::Selection, AutorouterOptions, }, + board::AccessMesadata, router::RouterOptions, specctra::{design::SpecctraDesign, ParseError, ParseErrorContext as SpecctraLoadingError}, }; @@ -281,7 +282,22 @@ impl MenuBar { ) { } else if workspace_activities_enabled { let mut schedule = |op: fn(Selection, AutorouterOptions) -> Command| { - let selection = workspace.overlay.take_selection(); + let mut selection = workspace.overlay.take_selection(); + if let Some(active_layer) = workspace.appearance_panel.active_layer { + let active_layer = workspace + .interactor + .invoker() + .autorouter() + .board() + .layout() + .rules() + .layer_layername(active_layer) + .expect("unknown active layer"); + selection + .pin_selection + .0 + .retain(|i| i.layer == active_layer); + } workspace .interactor .schedule(op(selection, self.autorouter_options)); diff --git a/crates/topola-egui/src/overlay.rs b/crates/topola-egui/src/overlay.rs index 5f84efa..75afc87 100644 --- a/crates/topola-egui/src/overlay.rs +++ b/crates/topola-egui/src/overlay.rs @@ -39,7 +39,7 @@ pub enum SelectionMode { pub struct Overlay { ratsnest: Ratsnest, - selection: Selection, + pub selection: Selection, planar_incr_navmesh: Option, reselect_bbox: Option<(SelectionMode, Point)>, } @@ -86,6 +86,10 @@ impl Overlay { use spade::Triangulation; use topola::router::planar_incr_embed::navmesh::TrianVertex; + let Some(active_layer) = appearance_panel.active_layer else { + return; + }; + if let Ok(triangulation) = spade::DelaunayTriangulation::>::bulk_load( board @@ -93,16 +97,8 @@ impl Overlay { .drawing() .rtree() .locate_in_envelope_intersecting(&AABB::<[f64; 3]>::from_corners( - [ - -f64::INFINITY, - -f64::INFINITY, - appearance_panel.active_layer as f64, - ], - [ - f64::INFINITY, - f64::INFINITY, - appearance_panel.active_layer as f64, - ], + [-f64::INFINITY, -f64::INFINITY, active_layer as f64], + [f64::INFINITY, f64::INFINITY, active_layer as f64], )) .map(|&geom| geom.data) .filter_map(|node| { diff --git a/src/autorouter/selection.rs b/src/autorouter/selection.rs index 29595c0..959d25b 100644 --- a/src/autorouter/selection.rs +++ b/src/autorouter/selection.rs @@ -74,7 +74,7 @@ impl PinSelector { } #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct PinSelection(BTreeSet); +pub struct PinSelection(pub BTreeSet); impl PinSelection { pub fn new() -> Self {