Prepare 0.2.0 usability release and refresh desktop demo

This commit is contained in:
bherbruck committed 2026-10-11 10:55:55 -04:00
1 parent a094fa46b5
commit ab05b24dee
54 files changed
+4888 -234

No files matched your search

+82 -13
View File
@@ -25,6 +25,8 @@ pub mod dialogs_plastic;
pub mod dialogs_sheet;
pub mod dialogs_surface;
pub mod dim_view;
#[cfg(test)]
mod dimension_create_ui_tests;
pub mod documents;
pub mod drag_snap;
pub mod fonts;
@@ -38,6 +40,7 @@ pub mod i18n;
mod i18n_ui_tests;
pub mod icons;
pub mod inference;
pub mod issue_report;
pub mod keymap;
#[cfg(test)]
mod menu_tests;
@@ -98,6 +101,7 @@ pub mod toolbar;
pub mod toolbar_custom;
pub mod tools;
pub mod viewport;
mod viewport_layout;
pub mod workspace;
use std::sync::mpsc::Receiver;
@@ -209,6 +213,8 @@ pub struct SolveApp {
pub keymap: keymap::Keymap,
/// The Help menu and the About window.
pub help: help::HelpState,
/// An editable local issue draft and optional captured app frame.
pub issue_report: issue_report::ReportState,
/// Toolbar buttons the user pinned, removed or reordered.
pub toolbar_custom: toolbar_custom::ToolbarCustom,
/// Preferences kept between runs, and their window.
@@ -247,6 +253,7 @@ pub struct SolveApp {
shot_token: u64,
styled: bool,
fitted: bool,
pending_fit: Option<(Camera, solvecraft_engine::geom::Aabb3, bool)>,
/// The start page.
pub home: home::HomeState,
pub file_load: file_load::FileLoad,
@@ -275,6 +282,7 @@ impl SolveApp {
sbox: shortcut_box::ShortcutBox::default(),
keymap: keymap::Keymap::default(),
help: help::HelpState::default(),
issue_report: issue_report::ReportState::default(),
toolbar_custom: toolbar_custom::ToolbarCustom::default(),
preferences: prefs::Prefs::default(),
prefs_window: prefs::PrefsWindow::default(),
@@ -300,6 +308,7 @@ impl SolveApp {
shot_token: 0,
styled: false,
fitted: false,
pending_fit: None,
home: home::HomeState::default(),
file_load: file_load::FileLoad::default(),
docs: documents::Documents::default(),
@@ -433,6 +442,7 @@ impl SolveApp {
Ok(_) => {
self.session.echo(format!("{} done", command_label(id)));
if matches!(id, "file.new" | "doc.open" | "doc.recover") {
dim_view::cancel();
self.menu.props = None;
sketch_attachment_ui::clear(self);
// The old document is already replaced: discard its editor without
@@ -471,10 +481,11 @@ impl SolveApp {
/// that have them, otherwise run it with defaults.
pub fn start(&mut self, id: &str) {
// A component moved without Capture Position: ask first (Capture, Revert or Cancel).
if browser::needs_capture(self, id) {
if browser::needs_capture(self, id) && !(id == "sketch.dimension" && self.session.active_sketch.is_some()) {
self.tree.capture_prompt = Some(id.to_string());
return;
}
dim_view::cancel();
self.tool = None;
self.home.open = false;
if matches!(id, "sketch.insert_dxf" | "sketch.insert_svg") {
@@ -649,6 +660,7 @@ impl SolveApp {
/// Finish the active sketch: stop editing, restore the history marker Edit Sketch moved and
/// return to the view from before the sketch.
pub fn finish_sketch(&mut self) {
dim_view::cancel();
self.tool = None;
let _ = self.run("sketch.finish", json!({}));
if let Some(marker) = self.pre_sketch_marker.take() {
@@ -741,6 +753,8 @@ impl SolveApp {
self.cam_anim = None;
self.viewport.orbit_snap = None;
self.viewport.orbit_free = None;
let bounds = solvecraft_engine::view::bounds(&self.session);
self.pending_fit = Some((self.cam, bounds, false));
self.cam = self.fitted(self.cam);
}
@@ -751,13 +765,39 @@ impl SolveApp {
cam.target = solvecraft_engine::geom::Vec3::ZERO;
cam.distance = 150.0;
} else {
cam.fit(&b);
cam = self.fitted_bounds(cam, &b);
}
cam
}
fn fitted_bounds(&self, mut cam: Camera, bounds: &solvecraft_engine::geom::Aabb3) -> Camera {
if let Some(layout) = &self.viewport.layout {
return viewport_layout::fit_bounds(cam, bounds, layout, self.ui.perspective).unwrap_or(cam);
}
let previous = cam;
cam.fit(bounds);
if cam.is_valid() { cam } else { previous }
}
fn resolve_fit(&mut self, ctx: &egui::Context) {
viewport::refresh_layout(self, ctx.pixels_per_point());
if self.viewport.layout.is_some()
&& let Some((cam, bounds, animate)) = self.pending_fit.take()
{
let to = if bounds.is_empty() { self.fitted(cam) } else { self.fitted_bounds(cam, &bounds) };
if animate {
self.animate_to(to);
} else {
self.cam = to;
self.cam_anim = None;
}
ctx.request_repaint();
}
}
/// Move the camera to `to` over half a second (ease in-out, orientation slerped).
pub fn animate_to(&mut self, to: Camera) {
self.pending_fit = None;
self.viewport.orbit_snap = None;
self.viewport.orbit_free = None;
if !to.is_valid() {
@@ -779,15 +819,23 @@ impl SolveApp {
/// Animated view changes for the axis view and navigation bar.
pub fn animate_view(&mut self, v: &str) {
self.pending_fit = None;
let mut to = self.cam;
match v {
// With something selected, Fit frames the selection.
"fit" => match viewport::selection_bounds(self) {
Some(b) => to.fit(&b),
None => to = self.fitted(to),
Some(b) => {
self.pending_fit = Some((to, b, true));
to = self.fitted_bounds(to, &b);
}
None => {
self.pending_fit = Some((to, solvecraft_engine::view::bounds(&self.session), true));
to = self.fitted(to);
}
},
"home" => {
to.set_view(StandardView::Iso);
self.pending_fit = Some((to, solvecraft_engine::view::bounds(&self.session), true));
to = self.fitted(to);
}
v => match StandardView::parse(v) {
@@ -795,11 +843,14 @@ impl SolveApp {
None => return,
},
}
let request = self.pending_fit.take();
self.animate_to(to);
self.pending_fit = request;
}
/// Stop any view animation where it is (the user took over the camera).
pub fn cancel_view_animation(&mut self) {
self.pending_fit = None;
self.cam_anim = None;
}
@@ -838,14 +889,19 @@ impl SolveApp {
if !self.synthetic.is_empty() {
ctx.request_repaint();
}
issue_report::tick(self, ctx);
self.collect_screenshots(ctx);
self.issue_screenshots(ctx);
toolbar::shortcuts(self, ctx);
if self.issue_report.draft.is_none() {
toolbar::shortcuts(self, ctx);
}
recovery_ui::tick(self);
for f in ctx.input(|i| i.raw.dropped_files.clone()) {
let p = f.path().to_string_lossy().to_string();
if !p.is_empty() {
self.drop_path(&p);
if self.issue_report.draft.is_none() {
for f in ctx.input(|i| i.raw.dropped_files.clone()) {
let p = f.path().to_string_lossy().to_string();
if !p.is_empty() {
self.drop_path(&p);
}
}
}
}
@@ -881,14 +937,26 @@ impl SolveApp {
pub fn ui(&mut self, ui: &mut egui::Ui) {
let t0 = now_ms();
let _language = crate::i18n::scope(&self.preferences.language);
let mut report_input = None;
issue_report::CadInput::block(self, ui, &mut report_input);
self.cad_ui(ui, &mut report_input);
drop(report_input);
issue_report::show(self, ui.ctx());
if !self.home.open {
agent_cursor::paint(self, ui.ctx());
}
self.frame_ms = now_ms() - t0;
}
fn cad_ui(&mut self, ui: &mut egui::Ui, report_input: &mut Option<issue_report::CadInput>) {
scenario::clear_handles();
toolbar::app_bar(self, ui);
issue_report::CadInput::block(self, ui, report_input);
file_load::show(self, ui.ctx());
documents::prompt(self, ui.ctx());
if self.home.open {
home::show(self, ui);
if self.home.open {
self.frame_ms = now_ms() - t0;
return;
}
}
@@ -905,7 +973,9 @@ impl SolveApp {
viewport::show(self, ui);
});
dialogs::show(self, ui.ctx());
issue_report::CadInput::block(self, ui, report_input);
sketch_palette::show(self, ui.ctx());
self.resolve_fit(ui.ctx());
params_dialog::show(self, ui.ctx());
recovery_ui::show(self, ui.ctx());
stand_up::show(self, ui.ctx());
@@ -916,8 +986,6 @@ impl SolveApp {
}
shortcut_box::show(self, ui.ctx());
prefs::show(self, ui.ctx());
agent_cursor::paint(self, ui.ctx());
self.frame_ms = now_ms() - t0;
}
fn drain_control(&mut self, ctx: &egui::Context) {
@@ -963,7 +1031,7 @@ impl SolveApp {
}
fn collect_screenshots(&mut self, ctx: &egui::Context) {
if self.pending_shots.is_empty() {
if self.pending_shots.is_empty() && !issue_report::pending(self) {
return;
}
let events: Vec<_> = ctx.input(|i| {
@@ -980,6 +1048,7 @@ impl SolveApp {
.collect()
});
for (token, image) in events {
issue_report::receive(self, token, image.clone());
if let Some(i) = self.pending_shots.iter().position(|(t, ..)| *t == token) {
let (_, path, reply, _) = self.pending_shots.remove(i);
let _ = reply.send(control::save_screenshot(&image, path.as_deref()));