Viewport: one framing rule for open, results, fit, home and standard views

Every fit frames what is drawn (the deformed shape and the setup's symbols
included) with Camera::fit_in in the part of the viewport the overlays
leave free: the results legend, the selection bar, the gizmo and the
navigation bar (framing::free_part). Standard views fit too, so a long
beam's front view keeps both ends (#147); fit and home leave the legend
clear (#76). The view frames itself in the home view when another model,
sample or design is opened (Session::opened; the same design opened again
after an edit keeps the camera) (#233), and when results come up with part
of the model out of sight (#223). ui.inspect's layout reports the legend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 22:29:44 -04:00
1 parent 5758dc7911
commit ad84a52ed2
6 files changed
+332 -35

No files matched your search

+29 -5
View File
@@ -28,6 +28,9 @@ pub fn show(app: &mut ModelApp, ui: &mut egui::Ui) {
let rect = ui.available_rect_before_wrap();
app.viewport.rect = Some(rect);
app.place("viewport", rect);
let t = Tokens::get(app.ui.dark);
app.viewport.overlays = overlays(app, ui, rect, &t);
app.apply_reframe();
let response = ui.allocate_rect(rect, Sense::click_and_drag());
let boxing = app.pick.box_mode && app.pick.filter.boxes();
navigate(app, ui, &response, rect, boxing);
@@ -44,7 +47,6 @@ pub fn show(app: &mut ModelApp, ui: &mut egui::Ui) {
hover(app, &response);
}
context_menu(app, &response);
let t = Tokens::get(app.ui.dark);
let painter = ui.painter_at(rect);
gradient(&painter, rect, t.viewport_top, t.viewport_bottom);
if app.viewport.gpu.is_some() {
@@ -69,6 +71,19 @@ pub fn show(app: &mut ModelApp, ui: &mut egui::Ui) {
nav_bar(app, ui, rect, &t);
}
/// Where this frame's overlays go over the viewport `rect`: the results legend, the selection
/// bar (where it was drawn last), the orientation gizmo and the navigation bar.
fn overlays(app: &mut ModelApp, ui: &egui::Ui, rect: Rect, t: &Tokens) -> Vec<Rect> {
let mut out: Vec<Rect> = [axis_view_rect(rect), nav_bar_rect(rect)].into_iter().filter(|r| rect.contains_rect(*r)).collect();
if let Some(plot) = app.plot() {
let legend = crate::results_panel::legend_rect(&plot, &ui.painter_at(rect), rect, t);
app.place("legend", legend);
out.push(legend);
}
out.extend(ui.ctx().memory(|m| m.area_rect(egui::Id::new(SELECTION_BAR))));
out
}
/// The empty viewport: open a file or a sample (the samples are ready to solve).
fn start_screen(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
let mut open = None;
@@ -184,9 +199,11 @@ fn context_menu(app: &mut ModelApp, response: &egui::Response) {
});
}
const SELECTION_BAR: &str = "fc_selection_bar";
/// The selection bar, top centre: what a click picks, box selection, and what is selected.
fn selection_bar(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
let area = egui::Area::new(egui::Id::new("fc_selection_bar"))
let area = egui::Area::new(egui::Id::new(SELECTION_BAR))
.fixed_pos(pos2(rect.center().x, rect.top() + 10.0))
.pivot(egui::Align2::CENTER_TOP)
.constrain_to(rect)
@@ -404,10 +421,14 @@ fn view_along(d: Vec3) -> StandardView {
best.0
}
fn axis_view_rect(rect: Rect) -> Rect {
Rect::from_center_size(pos2(rect.right() - 68.0, rect.top() + 68.0), Vec2::splat(116.0))
}
/// The orientation gizmo (top right): the six signed axes in the current view, back to front;
/// clicking a tip looks along it, the house returns to the home view.
fn axis_view(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
let area = Rect::from_center_size(pos2(rect.right() - 68.0, rect.top() + 68.0), Vec2::splat(116.0));
let area = axis_view_rect(rect);
if !rect.contains_rect(area) {
return;
}
@@ -473,11 +494,14 @@ const NAV: [(&str, &str, &str); 7] = [
("style", "ui.style", "Visual style and what is shown"),
];
fn nav_bar_rect(rect: Rect) -> Rect {
Rect::from_center_size(pos2(rect.center().x, rect.bottom() - 22.0), vec2(NAV.len() as f32 * 30.0 + 10.0, 30.0))
}
/// The navigation bar, bottom centre: navigation modes, fit, home, projection, and the display
/// menu (style, geometry, mesh, nodes).
fn nav_bar(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
let w = NAV.len() as f32 * 30.0 + 10.0;
let bar = Rect::from_center_size(pos2(rect.center().x, rect.bottom() - 22.0), vec2(w, 30.0));
let bar = nav_bar_rect(rect);
if !rect.contains_rect(bar) {
return;
}