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

+30
View File
@@ -18,7 +18,9 @@ pub mod dialogs_assembly;
pub mod dialogs_plastic;
pub mod dialogs_sheet;
pub mod dim_view;
pub mod documents;
pub mod gpu;
pub mod home;
pub mod icons;
pub mod inference;
#[cfg(test)]
@@ -164,6 +166,10 @@ pub struct SolveApp {
shot_token: u64,
styled: bool,
fitted: bool,
/// The start page.
pub home: home::HomeState,
/// The other open designs (tabs).
pub docs: documents::Documents,
}
impl SolveApp {
@@ -199,6 +205,8 @@ impl SolveApp {
shot_token: 0,
styled: false,
fitted: false,
home: home::HomeState::default(),
docs: documents::Documents::default(),
}
}
@@ -219,6 +227,7 @@ impl SolveApp {
"visual_style": self.ui.visual_style,
"browser_collapsed": self.tree.collapsed,
"browser_expanded": self.tree.expanded,
"recent": self.home.recent,
})
.to_string()
}
@@ -254,6 +263,11 @@ impl SolveApp {
};
self.tree.collapsed = keys("browser_collapsed");
self.tree.expanded = keys("browser_expanded");
self.home.recent = v
.get("recent")
.and_then(Value::as_array)
.map(|a| a.iter().filter_map(Value::as_str).take(home::MAX_RECENT).map(str::to_string).collect())
.unwrap_or_default();
}
pub fn with_control(mut self, rx: Receiver<ControlRequest>) -> Self {
@@ -271,6 +285,11 @@ impl SolveApp {
self.cam = Camera::default();
self.fit_view();
}
if matches!(id, "doc.open" | "SaveDocumentCommand" | "SaveDocumentAsCommand")
&& let Some(p) = self.session.path.clone()
{
self.home.add_recent(&p);
}
}
Err(e) => {
self.session.echo(format!("{id}: {e}"));
@@ -290,6 +309,7 @@ impl SolveApp {
/// that have them, otherwise run it with defaults.
pub fn start(&mut self, id: &str) {
self.tool = None;
self.home.open = false;
if matches!(id, "FusionImportCommandFromToolbar" | "ParaMeshInsertAlignCommand") {
if let Some(p) = self.services.pick_open.as_ref().and_then(|f| f()) {
self.insert_path(&p);
@@ -455,6 +475,8 @@ impl SolveApp {
}
pub fn open_path(&mut self, path: &str) {
self.home.open = false;
documents::prepare_open(self);
if self.run("doc.open", json!({ "path": path })).is_ok() {
self.fit_view();
}
@@ -599,6 +621,14 @@ impl SolveApp {
pub fn ui(&mut self, ui: &mut egui::Ui) {
let t0 = now_ms();
toolbar::app_bar(self, ui);
documents::prompt(self, ui.ctx());
if self.home.open {
home::show(self, ui);
if self.home.open {
self.frame_ms = now_ms() - t0;
return;
}
}
toolbar::toolbar(self, ui);
if self.ui.show_timeline {
timeline::timeline(self, ui);