Capture Position question: starting a command or saving while components were moved without capturing asks Capture Position / Revert / Cancel, then goes ahead; ui.capture control method

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 12:52:22 +00:00
1 parent 9bd7c8eb33
commit fec99468d2
6 files changed
+122

No files matched your search

+77
View File
@@ -36,6 +36,8 @@ pub struct TreeState {
pub picked_canvases: Vec<u64>,
/// The Edit Canvas / Calibrate panel.
pub canvas_panel: Option<CanvasPanel>,
/// What waits for the Capture Position question: a command id, or [`SAVE`].
pub capture_prompt: Option<String>,
/// The last row clicked (for Shift ranges).
anchor: Option<String>,
/// Row keys in drawing order this frame (for Shift ranges).
@@ -446,6 +448,7 @@ pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
crate::context_menu::open_for(app, at, target);
}
occurrence_panel(app, ui.ctx());
capture_prompt(app, ui.ctx());
canvas_panel(app, ui.ctx());
redefine_panel(app, ui.ctx());
}
@@ -1430,6 +1433,80 @@ fn panel_pos(app: &SolveApp) -> Pos2 {
app.viewport.rect.map(|r| r.left_top() + vec2(16.0, 16.0)).unwrap_or(pos2(270.0, 140.0))
}
/// The Capture Position question stands for saving.
pub const SAVE: &str = "__save";
/// Does starting `what` (a command id or [`SAVE`]) need the Capture Position question first?
/// Yes while components were moved without capturing their positions, except for the commands
/// that capture, revert or keep moving.
pub fn needs_capture(app: &SolveApp, what: &str) -> bool {
!app.session.pending_moves.is_empty()
&& !matches!(what, "SnapshotCmd" | "AsBuiltPositionsCmd" | "occurrence.move" | "UndoCommand" | "RedoCommand")
&& app.tree.capture_prompt.is_none()
}
/// Answer the Capture Position question: `capture`, `revert` or `cancel`. The waiting command
/// (or save) then goes ahead, unless cancelled.
pub fn answer_capture(app: &mut SolveApp, action: &str) -> Result<(), String> {
let Some(what) = app.tree.capture_prompt.clone() else { return Err("no Capture Position question is open".into()) };
match action {
"capture" => drop(app.run("SnapshotCmd", json!({}))),
"revert" => drop(app.run("AsBuiltPositionsCmd", json!({}))),
"cancel" => {
app.tree.capture_prompt = None;
return Ok(());
}
other => return Err(format!("unknown answer `{other}` (capture, revert, cancel)")),
}
app.tree.capture_prompt = None;
app.tree.occurrence_move = None;
if what == SAVE {
crate::toolbar::save(app);
} else {
app.start(&what);
}
Ok(())
}
fn capture_prompt(app: &mut SolveApp, ctx: &egui::Context) {
if app.tree.capture_prompt.is_none() {
return;
}
let n = app.session.pending_moves.len();
let mut answer: Option<&str> = None;
egui::Window::new("Capture Position")
.id(egui::Id::new("sc_capture_prompt"))
.collapsible(false)
.resizable(false)
.anchor(egui::Align2::CENTER_CENTER, vec2(0.0, -80.0))
.show(ctx, |ui| {
ui.label(if n == 1 {
"A component was moved but its position isn't captured.".to_string()
} else {
format!("{n} components were moved but their positions aren't captured.")
});
ui.label(RichText::new("Capture keeps the new position; Revert puts it back.").color(Tokens::get().text_dim));
ui.add_space(6.0);
ui.horizontal(|ui| {
if ui.button("Capture Position").clicked() {
answer = Some("capture");
}
if ui.button("Revert").clicked() {
answer = Some("revert");
}
if ui.button("Cancel").clicked() {
answer = Some("cancel");
}
});
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
answer = Some("cancel");
}
});
if let Some(a) = answer {
let _ = answer_capture(app, a);
}
}
/// Where an occurrence's origin is now (with its pending move).
fn occurrence_origin(app: &SolveApp, occurrence: u64) -> Option<solvecraft_engine::geom::Vec3> {
let o = app.session.doc.occurrences.iter().find(|o| o.id == occurrence)?;