Skip to content
Open
54 changes: 51 additions & 3 deletions crates/gdsr-viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::thread;

use crate::drawable::Drawable;
use crate::drawable::{Drawable, WorldBBox};
use crate::panels;
use crate::quick_pick::{QuickPick, QuickPickResult};
use crate::recent::{RecentProjectItem, RecentProjects};
Expand Down Expand Up @@ -119,15 +119,23 @@ impl ViewerApp {
cell.elements.clear();
cell.layers.clear();
cell.spatial_grid = None;
cell.tessellation_cache.clear();
cell.cell_stats = cell.library.get_cell(name).map(gdsr::CellStats::from_cell);

let depth = cell.render_depth;
if depth == 0 {
cell.elements_loading = false;
return;
}

if let Some(cell_data) = cell.library.get_cell(name) {
let cell_data = cell_data.clone();
let library = cell.library.clone();
let (tx, rx) = mpsc::channel();
let stream_depth = Some((depth - 1) as usize);

thread::spawn(move || {
cell_data.stream_elements(None, &library, &tx);
cell_data.stream_elements(stream_depth, &library, &tx);
});

cell.element_receiver = Some(rx);
Expand All @@ -139,7 +147,21 @@ impl ViewerApp {
/// Adjusts the viewport to fit all currently loaded elements.
fn zoom_to_fit(&mut self) {
if let Some(cell) = self.cell.as_ref() {
if let Some(bounds) = viewport::compute_bounds(&cell.elements) {
let bounds = if cell.render_depth == 0 {
cell.selected_cell.as_ref().and_then(|name| {
let c = cell.library.get_cell(name)?;
let (min_pt, max_pt) = gdsr::Dimensions::bounding_box(c);
Some(WorldBBox::new(
min_pt.x().absolute_value(),
min_pt.y().absolute_value(),
max_pt.x().absolute_value(),
max_pt.y().absolute_value(),
))
})
} else {
viewport::compute_bounds(&cell.elements)
};
if let Some(bounds) = bounds {
let rect =
egui::Rect::from_min_size(egui::Pos2::ZERO, egui::Vec2::new(800.0, 600.0));
self.viewport.zoom_to_fit(&bounds, rect);
Expand Down Expand Up @@ -328,6 +350,7 @@ impl eframe::App for ViewerApp {
});

// Bottom activity bar
let mut depth_changed = false;
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
ui.horizontal(|ui| {
let is_tree = self.side_panel_tab == SidePanelTab::Cells
Expand Down Expand Up @@ -411,6 +434,19 @@ impl eframe::App for ViewerApp {
ui.selectable_value(&mut self.grid_spacing, preset, label);
}
});
if let Some(cell) = &mut self.cell {
let prev_depth = cell.render_depth;
if ui.small_button("+").clicked() && cell.render_depth < 99 {
cell.render_depth += 1;
}
ui.label(format!("Depth: {}", cell.render_depth));
if ui.small_button("−").clicked() && cell.render_depth > 0 {
cell.render_depth -= 1;
}
if cell.render_depth != prev_depth {
depth_changed = true;
}
}
if self.ruler.active {
ui.label("Ruler: click to place point (Esc to cancel)");
}
Expand All @@ -421,6 +457,13 @@ impl eframe::App for ViewerApp {
});
});

if depth_changed {
if let Some(name) = self.cell.as_ref().and_then(|c| c.selected_cell.clone()) {
self.select_cell(&name);
}
self.render_cache.clear();
}

// Cell picker (⌘P)
self.cell_picker.set_items(
self.cell
Expand Down Expand Up @@ -498,6 +541,9 @@ impl eframe::App for ViewerApp {
let grid_spacing = self.grid_spacing;
let hovered_element = &mut self.hovered_element;
let query_buf = &mut self.query_buf;
let render_depth = cell.as_ref().map_or(1, |c| c.render_depth);
let selected_cell_name: Option<String> =
cell.as_ref().and_then(|c| c.selected_cell.clone());
egui::CentralPanel::default().show(ctx, |ui| {
let mut empty_cache = std::collections::HashMap::new();
let (elements, spatial_grid, library, tessellation_cache) =
Expand All @@ -524,6 +570,8 @@ impl eframe::App for ViewerApp {
show_grid,
grid_spacing,
*hovered_element,
render_depth,
selected_cell_name.as_deref(),
);

let prev_hovered = *hovered_element;
Expand Down
100 changes: 100 additions & 0 deletions crates/gdsr-viewer/src/drawable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ pub struct DrawContext<'a> {
pub screen_pts_buf: &'a mut Vec<Pos2>,
/// When true, the element is drawn with a brighter fill and bolder outline.
pub highlight: bool,
/// When true, un-flattened cell references draw as outlined bounding boxes
/// with the cell name centered, instead of expanding their contents.
pub show_ref_bbox: bool,
}

/// Fill alpha for normal and highlighted elements.
Expand Down Expand Up @@ -826,6 +829,98 @@ impl Drawable for gdsr::Node {
}
}

/// Draws an un-flattened reference as an outlined bounding box with a centered cell name label.
fn draw_ref_as_bbox(reference: &gdsr::Reference, ctx: &mut DrawContext) {
let (label, bbox) = if let Some(cell_name) = reference.instance().as_cell() {
let Some(lib) = ctx.library else { return };
let Some(cell) = lib.get_cell(cell_name) else {
return;
};
let (min_pt, max_pt) = cell.bounding_box();
let bbox_poly = gdsr::Polygon::new(
vec![
min_pt,
gdsr::Point::new(max_pt.x(), min_pt.y()),
max_pt,
gdsr::Point::new(min_pt.x(), max_pt.y()),
min_pt,
],
Layer::new(0),
DataType::new(0),
);
let bbox_element = Element::Polygon(bbox_poly);
let mut merged: Option<WorldBBox> = None;
for el in reference.get_elements_in_grid(&bbox_element) {
if let Some(bb) = el.world_bbox() {
merged = Some(match merged {
Some(acc) => acc.merge(&bb),
None => bb,
});
}
}
match merged {
Some(bb) => (Some(cell_name.as_str()), bb),
None => return,
}
} else if let Some(element) = reference.instance().as_element() {
let mut merged: Option<WorldBBox> = None;
for el in reference.get_elements_in_grid(element) {
if let Some(bb) = el.world_bbox() {
merged = Some(match merged {
Some(acc) => acc.merge(&bb),
None => bb,
});
}
}
match merged {
Some(bb) => (None, bb),
None => return,
}
} else {
return;
};

if !bbox.overlaps(ctx.visible) {
return;
}

let s_min = ctx
.viewport
.world_to_screen(bbox.min_x, bbox.min_y, ctx.rect);
let s_max = ctx
.viewport
.world_to_screen(bbox.max_x, bbox.max_y, ctx.rect);
let screen_rect = Rect::from_two_pos(s_min, s_max);
let stroke_color = Color32::from_rgb(180, 180, 180);
ctx.rect_stroke(
screen_rect,
0.0,
Stroke::new(1.0, stroke_color),
StrokeKind::Outside,
);

if let Some(name) = label {
let sw = (s_max.x - s_min.x).abs();
let sh = (s_min.y - s_max.y).abs();
if sw >= 40.0 && sh >= 20.0 {
let char_count = name.len().max(1) as f32;
let fit_w = sw * 0.9 / (char_count * 0.6);
let fit_h = sh * 0.4;
let font_size = fit_w.min(fit_h).min(48.0);
if font_size >= 8.0 {
let center = screen_rect.center();
ctx.text(
center,
egui::Align2::CENTER_CENTER,
name,
FontId::monospace(font_size),
stroke_color,
);
}
}
}
}

impl Drawable for gdsr::Reference {
fn layer_keys(&self) -> Vec<(Layer, DataType)> {
match self.instance().as_element() {
Expand Down Expand Up @@ -860,6 +955,10 @@ impl Drawable for gdsr::Reference {
}

fn draw(&self, ctx: &mut DrawContext) {
if ctx.show_ref_bbox {
draw_ref_as_bbox(self, ctx);
return;
}
if let Some(element) = self.instance().as_element() {
for el in self.get_elements_in_grid(element) {
el.draw(ctx);
Expand Down Expand Up @@ -952,6 +1051,7 @@ pub fn draw_highlight(
tessellation_cache,
screen_pts_buf: &mut screen_pts_buf,
highlight: true,
show_ref_bbox: false,
};
element.draw(&mut ctx);
for (_, mesh) in layer_meshes {
Expand Down
Loading