ui: the viewport draws the analysis setup's symbols, with toggles

The setup's symbols (fieldcraft_render::glyphs) are built with the display
data and drawn over the model in every style, at the drawn nodes (a results
plot's deformed shape), muted over results; the clipping radius holds them.
Theme tokens give their colours in light and dark. The browser's Loads & BCs
and Interactions rows get eye toggles (ui.set show_loads, show_interactions),
also in the view menu. The GPU viewport draws reference point names with the
interface's font; the bottom left says which definitions are sampled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 00:34:33 -04:00
1 parent 68a9553f53
commit 5665cc3d8c
8 files changed
+183 -21

No files matched your search

+30
View File
@@ -33,9 +33,11 @@ pub fn show(app: &mut ModelApp, ui: &mut egui::Ui) {
gradient(&painter, rect, t.viewport_top, t.viewport_bottom);
if app.viewport.gpu.is_some() {
paint_gpu(app, ui, &painter, rect);
labels(app, &painter, &t);
} else {
paint_cpu(app, ui, &painter, rect);
}
sampled_note(app, &painter, rect, &t);
if app.display().bounds.is_empty() && app.busy().is_none() {
start_screen(app, ui, rect, &t);
}
@@ -176,6 +178,32 @@ fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R
}
}
/// The scene's labels (reference point names) beside their points, with the interface's font.
/// The CPU renderer draws them into its image.
fn labels(app: &mut ModelApp, painter: &egui::Painter, t: &Tokens) {
let labels: Vec<(Vec3, String)> = app.scene().1.labels.iter().map(|l| (l.p, l.text.clone())).collect();
let offset = fieldcraft_render::LABEL_OFFSET as f32;
for (p, text) in labels {
if let Some(at) = to_screen(app, p) {
let galley = painter.layout_no_wrap(text, egui::FontId::proportional(12.0), t.overlay_text);
let r = Rect::from_min_size(at + vec2(offset, -offset - galley.size().y), galley.size()).expand2(vec2(4.0, 1.0));
painter.rect_filled(r, 3.0, t.overlay);
painter.galley(r.min + vec2(4.0, 1.0), galley, t.overlay_text);
}
}
}
/// Bottom left: the definitions whose symbols are a sample of their nodes or faces.
fn sampled_note(app: &ModelApp, painter: &egui::Painter, rect: Rect, t: &Tokens) {
let show = app.ui.show();
let notes = &app.glyph_notes();
if notes.is_empty() || !(show.loads || show.interactions) {
return;
}
let text = format!("Symbols sampled: {}", notes.join("; "));
painter.text(rect.left_bottom() + vec2(10.0, -10.0), egui::Align2::LEFT_BOTTOM, text, egui::FontId::proportional(11.0), t.text_dim);
}
/// The orientation gizmo's arm length and tip radius (points).
const AXIS_LENGTH: f32 = 38.0;
const AXIS_RADIUS: f32 = 11.0;
@@ -321,6 +349,8 @@ fn nav_bar(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
ui.checkbox(&mut app.ui.show_geometry, "Geometry");
ui.checkbox(&mut app.ui.show_mesh, "Mesh");
ui.checkbox(&mut app.ui.show_nodes, "Nodes");
ui.checkbox(&mut app.ui.show_loads, "Loads & BCs");
ui.checkbox(&mut app.ui.show_interactions, "Interactions");
ui.separator();
ui.checkbox(&mut app.ui.show_tree, "Browser");
ui.checkbox(&mut app.ui.show_timeline, "Timeline");