Prepare 0.2.0 usability release and refresh desktop demo
This commit is contained in:
1 parent
a094fa46b5
commit
ab05b24dee
54 files changed
+4888
-234
No files matched your search
+82
-13
@@ -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()));
|
||||
|
||||
Reference in new issue
Block a user