Start page and document tabs

Start page (shown on launch without a file, and from the house button in the application bar):
New Design, Open, recent designs (kept in the preferences, newest first) and built-in samples,
each card with a picture rendered from the design in the background. Samples are scripts of
engine commands: the sample plate plus a sheet metal bracket, a hinge assembly with a revolute
joint, a plastic enclosure (lip, bosses, rest, snap fit), a bolted flange with a parametric
circular pattern and a vent plate with a parametric rectangular pattern (test: all build
without a failing feature).

Several open designs as tabs in the application bar: each keeps its own session (undo,
selection), camera and hidden/shown items; + and Ctrl+N open a new design, opening a file or a
sample uses a new tab unless the current design is blank, closing a tab with unsaved changes
asks Save / Don't Save / Cancel. Control channel: ui.documents and ui.home (docs/control-protocol.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 09:23:46 +00:00
1 parent c8da3b78f3
commit bdc15d2204
9 files changed
+1003 -12

No files matched your search

+8 -11
View File
@@ -78,6 +78,10 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
if click(ui, "redo", "Redo (Ctrl+Y)") {
let _ = app.run("RedoCommand", json!({}));
}
if click(ui, "home", "Start page") {
let rev = app.session.revision;
app.home.show(rev);
}
let theme_tip = if app.ui.dark { "Light theme" } else { "Dark theme" };
if click(ui, if app.ui.dark { "sun" } else { "moon" }, theme_tip) {
app.ui.dark = !app.ui.dark;
@@ -94,12 +98,9 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
d.insert_temp(egui::Id::new("sc_file_menu_opened"), pass);
});
}
// Document tab.
let name = format!("{}{}", app.session.doc.name, if app.session.is_dirty() { " •" } else { "" });
let tab = Rect::from_min_size(pos2(r.center().x - 110.0, r.top() + 5.0), vec2(220.0, r.height() - 5.0));
ui.painter().rect_filled(tab, egui::CornerRadius { nw: 5, ne: 5, sw: 0, se: 0 }, t.toolbar);
ui.painter().text(tab.center(), Align2::CENTER_CENTER, name, FontId::proportional(13.0), t.text);
// Document tabs.
let captions = if app.custom_titlebar { crate::titlebar::WIDTH } else { 0.0 };
crate::documents::tabs(app, ui, r, x + 16.0, r.right() - captions - 60.0);
ui.painter().text(
pos2(r.right() - 12.0 - captions, r.center().y),
Align2::RIGHT_CENTER,
@@ -130,8 +131,7 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
ui.add(egui::Button::new(label).shortcut_text(key).frame(false).min_size(vec2(180.0, 22.0))).clicked()
};
if item(ui, "New Design", "Ctrl+N") {
let _ = app.run("NewDocumentCommand", json!({}));
app.fit_view();
crate::documents::new_design(app);
close = true;
}
if item(ui, "Open…", "Ctrl+O") {
@@ -406,10 +406,7 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
Key::Z => drop(app.run("UndoCommand", json!({}))),
Key::Y => drop(app.run("RedoCommand", json!({}))),
Key::S => save(app),
Key::N => {
let _ = app.run("NewDocumentCommand", json!({}));
app.fit_view();
}
Key::N => crate::documents::new_design(app),
Key::O => {
if let Some(p) = app.services.pick_open.as_ref().and_then(|f| f()) {
app.open_path(&p);