Add appearance panel, show only enabled layers

This commit is contained in:
Mikolaj Wielgus 2026-03-11 00:49:33 +01:00
parent 8a50e70a6a
commit fde7b163df
8 changed files with 305 additions and 30 deletions

View File

@ -128,6 +128,10 @@ impl eframe::App for App {
self.update_state();
if let Some(ref mut workspace) = self.workspace {
workspace.update_appearance_panel(ctx);
}
self.viewport.update(ctx, self.workspace.as_mut());
self.update_locale();

View File

@ -0,0 +1,217 @@
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::collections::BTreeMap;
use egui::{Context, Grid, ScrollArea, SidePanel, widget_text::WidgetText};
use serde::{Deserialize, Serialize};
use topola::Board;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Colors {
pub layers: LayerColors,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LayerColors {
default: LayerColor,
colors: BTreeMap<String, LayerColor>,
}
impl LayerColors {
pub fn color(&self, layer_name: Option<&str>) -> &LayerColor {
layer_name
.map(|layername| self.colors.get(layername).unwrap_or(&self.default))
.unwrap_or(&self.default)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LayerColor {
pub normal: egui::Color32,
pub highlighted: egui::Color32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AppearancePanel {
// TODO:
// In1.Cu shall be #7fc87f (#d5ecd5 when selected).
// In2.Cu shall be #ce7d2c (#e8c39e when selected).
pub dark_colors: Colors,
pub light_colors: Colors,
#[serde(skip)]
pub visible: Box<[bool]>,
}
impl AppearancePanel {
pub fn new(board: &Board) -> Self {
let dark_colors = Colors {
layers: LayerColors {
default: LayerColor {
normal: egui::Color32::from_rgb(255, 255, 255),
highlighted: egui::Color32::from_rgb(255, 255, 255),
},
colors: BTreeMap::from([
(
"F.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(255, 52, 52),
highlighted: egui::Color32::from_rgb(255, 100, 100),
},
),
(
"1".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(255, 52, 52),
highlighted: egui::Color32::from_rgb(255, 100, 100),
},
),
(
"B.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(52, 52, 255),
highlighted: egui::Color32::from_rgb(100, 100, 255),
},
),
(
"2".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(52, 52, 255),
highlighted: egui::Color32::from_rgb(100, 100, 255),
},
),
(
"In1.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(127, 200, 127),
highlighted: egui::Color32::from_rgb(213, 236, 213),
},
),
(
"In2.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(206, 125, 44),
highlighted: egui::Color32::from_rgb(232, 195, 158),
},
),
]),
},
};
let light_colors = Colors {
layers: LayerColors {
default: LayerColor {
normal: egui::Color32::from_rgb(0, 0, 0),
highlighted: egui::Color32::from_rgb(0, 0, 0),
},
colors: BTreeMap::from([
(
"F.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(255, 27, 27),
highlighted: egui::Color32::from_rgb(255, 52, 52),
},
),
(
"1".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(255, 27, 27),
highlighted: egui::Color32::from_rgb(255, 52, 52),
},
),
(
"B.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(27, 27, 255),
highlighted: egui::Color32::from_rgb(52, 52, 255),
},
),
(
"2".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(27, 27, 255),
highlighted: egui::Color32::from_rgb(52, 52, 255),
},
),
(
"In1.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(76, 169, 76),
highlighted: egui::Color32::from_rgb(127, 200, 127),
},
),
(
"In2.Cu".to_string(),
LayerColor {
normal: egui::Color32::from_rgb(183, 80, 12),
highlighted: egui::Color32::from_rgb(206, 125, 44),
},
),
]),
},
};
let layer_count = board.layout().layer_count();
let visible = core::iter::repeat(true)
.take(*layer_count)
.collect::<Box<[_]>>();
Self {
dark_colors,
light_colors,
visible,
}
}
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 layer_name = board.layer_name(layer);
// unnamed layers can't be used for routing
/*if layer_name.is_some() {
ui.radio_value(
&mut options.planar.principal_layer,
layer,
WidgetText::default(),
);
} else {
// dummy item to bump the grid
ui.label("");
}*/
ui.checkbox(visible, WidgetText::default());
ui.label(
layer_name
.map(|i| i.to_string())
.unwrap_or_else(|| format!("{} - Unnamed layer", layer)),
);
ui.end_row();
}
})
},
);
});
}
pub fn colors(&self, ctx: &Context) -> &Colors {
match ctx.theme() {
egui::Theme::Dark => &self.dark_colors,
egui::Theme::Light => &self.light_colors,
}
}
}

View File

@ -46,24 +46,58 @@ impl Displayer {
);
for (_, joint) in workspace.board.layout().joints().collection() {
self.paint_joint(ctx, ui, viewport, joint);
if workspace.appearance_panel.visible[joint.layer] {
self.paint_joint(
ctx,
ui,
viewport,
joint,
workspace
.appearance_panel
.colors(ctx)
.layers
.color(workspace.board.layer_name(joint.layer))
.normal,
);
}
}
for (i, segment) in workspace.board.layout().segments().collection() {
self.paint_segment(
ctx,
ui,
viewport,
segment,
workspace
.board
.layout()
.segment_endpoints(SegmentId::new(i)),
);
if workspace.appearance_panel.visible[segment.layer] {
self.paint_segment(
ctx,
ui,
viewport,
segment,
workspace
.board
.layout()
.segment_endpoints(SegmentId::new(i)),
workspace
.appearance_panel
.colors(ctx)
.layers
.color(workspace.board.layer_name(segment.layer))
.normal,
);
}
}
for (_, polygon) in workspace.board.layout().polygons().collection() {
self.paint_polygon(ctx, ui, viewport, polygon);
if workspace.appearance_panel.visible[polygon.layer] {
self.paint_polygon(
ctx,
ui,
viewport,
polygon,
workspace
.appearance_panel
.colors(ctx)
.layers
.color(workspace.board.layer_name(polygon.layer))
.normal,
);
}
}
}
@ -73,11 +107,12 @@ impl Displayer {
ui: &egui::Ui,
viewport: &Viewport,
joint: &Joint,
color: egui::Color32,
) {
ui.painter().circle_filled(
egui::pos2(joint.position[0] as f32, joint.position[1] as f32),
joint.radius as f32,
egui::Color32::RED,
color,
);
}
@ -88,13 +123,14 @@ impl Displayer {
viewport: &Viewport,
segment: &Segment,
endpoints: [[i64; 2]; 2],
color: egui::Color32,
) {
ui.painter().line_segment(
[
egui::pos2(endpoints[0][0] as f32, endpoints[0][1] as f32),
egui::pos2(endpoints[1][0] as f32, endpoints[1][1] as f32),
],
egui::Stroke::new(segment.half_width as f32 * 2.0, egui::Color32::RED),
egui::Stroke::new(segment.half_width as f32 * 2.0, color),
);
}
@ -104,6 +140,7 @@ impl Displayer {
ui: &egui::Ui,
viewport: &Viewport,
polygon: &Polygon,
color: egui::Color32,
) {
let points: Vec<egui::Pos2> = polygon
.vertices
@ -114,7 +151,7 @@ impl Displayer {
ui.painter().add(egui::Shape::convex_polygon(
points,
egui::Color32::RED,
egui::Stroke::new(5.0 / viewport.scale_factor(), egui::Color32::RED),
egui::Stroke::new(5.0 / viewport.scale_factor(), color),
));
}
}

View File

@ -7,6 +7,7 @@
mod action;
mod actions;
mod app;
mod appearance_panel;
mod displayer;
mod menu_bar;
mod translator;

View File

@ -4,14 +4,24 @@
use topola::Board;
use crate::translator::Translator;
use crate::{appearance_panel::AppearancePanel, translator::Translator};
pub struct Workspace {
pub board: Board,
pub appearance_panel: AppearancePanel,
}
impl Workspace {
pub fn new(board: Board, tr: &Translator) -> Self {
Self { board }
let appearance_panel = AppearancePanel::new(&board);
Self {
board,
appearance_panel,
}
}
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
self.appearance_panel.update(ctx, &self.board);
}
}

View File

@ -26,9 +26,9 @@ pub struct Board {
}
impl Board {
pub fn new(boundary: Vec<[i64; 2]>) -> Self {
pub fn new(boundary: Vec<[i64; 2]>, layer_count: usize) -> Self {
Self {
layout: Layout::new(boundary),
layout: Layout::new(boundary, layer_count),
layer_names: BiBTreeMap::new(),
net_names: BiBTreeMap::new(),
}
@ -36,11 +36,12 @@ impl Board {
pub fn with_names(
boundary: Vec<[i64; 2]>,
layer_count: usize,
layer_names: BiBTreeMap<usize, String>,
net_names: BiBTreeMap<usize, String>,
) -> Self {
Self {
layout: Layout::new(boundary),
layout: Layout::new(boundary, layer_count),
layer_names,
net_names,
}
@ -66,20 +67,20 @@ impl Board {
self.layout.add_polygon(polygon)
}
pub fn layer_name(&self, layer: usize) -> &str {
&self.layer_names.get_by_left(&layer).unwrap()
pub fn layer_name(&self, layer: usize) -> Option<&str> {
self.layer_names.get_by_left(&layer).map(String::as_str)
}
pub fn layer_id(&self, name: &str) -> usize {
*self.layer_names.get_by_right(name).unwrap()
pub fn layer_id(&self, name: &str) -> Option<usize> {
self.layer_names.get_by_right(name).copied()
}
pub fn net_name(&self, net: usize) -> &str {
&self.net_names.get_by_left(&net).unwrap()
pub fn net_name(&self, net: usize) -> Option<&str> {
self.net_names.get_by_left(&net).map(String::as_str)
}
pub fn net_id(&self, name: &str) -> usize {
*self.net_names.get_by_right(name).unwrap()
pub fn net_id(&self, name: &str) -> Option<usize> {
self.net_names.get_by_right(name).copied()
}
}

View File

@ -132,6 +132,8 @@ pub struct Polygon {
pub struct Layout {
boundary: Vec<[i64; 2]>,
place_boundary: Vec<[i64; 2]>,
layer_count: usize,
joints: Recorder<StableVec<Joint>>,
segments: Recorder<StableVec<Segment>>,
arcs: Recorder<StableVec<Arc>>,
@ -140,10 +142,12 @@ pub struct Layout {
}
impl Layout {
pub fn new(boundary: Vec<[i64; 2]>) -> Self {
pub fn new(boundary: Vec<[i64; 2]>, layer_count: usize) -> Self {
Self {
boundary: boundary.clone(),
place_boundary: boundary,
layer_count,
joints: Recorder::new(StableVec::new()),
segments: Recorder::new(StableVec::new()),
arcs: Recorder::new(StableVec::new()),

View File

@ -52,6 +52,7 @@ impl Board {
.into_iter()
.map(|p| [p.x as i64, p.y as i64])
.collect(),
dsn.pcb.structure.layers.len(),
layer_names,
net_names,
);
@ -251,7 +252,7 @@ impl Board {
}
fn layer(board: &Board, layers: &[Layer], name: &str, front: bool) -> usize {
let image_layer = board.layer_id(name);
let image_layer = board.layer_id(name).unwrap();
if front {
image_layer