fix(extrude): typed height is ignored on web (#1082)

On wasm the canvas holds focus, so a typed value reaches the dynamic
Distance field through CommandAppendChar+CommandFinalize — and
try_dyn_commit only commits as text when wants_text() && !
point_step_accepts_keywords(), or dyn_commit_as_text(). EXTRUDE's
Height step accepts keywords and never overrode dyn_commit_as_text
(unlike PRESSPULL and REVOLVE), so the value resolved as an in-plane
point whose height is ~0 and silently re-prompted. Commit the Height
step as text, matching its siblings; regression test drives the
exact web message sequence.
This commit is contained in:
Afonsop committed 2026-09-17 13:01:02 +01:00
1 parent a60b52a462
commit 9e4c3837dd
2 files changed
+101

No files matched your search

+91
View File
@@ -1680,4 +1680,95 @@ mod tests {
let _ = app.update(Message::ToggleRibbonDropdown("PROP_COLOR".to_string()));
let _ = app.view(wid);
}
/// Regression test for issue #1082: a height typed while EXTRUDE waits at
/// its Height prompt must extrude, including when the keystrokes arrive
/// through the global key-press subscription (`CommandAppendChar` → the
/// dynamic-input field buffer → `CommandFinalize`) instead of the focused
/// command-line widget (`CommandInput` → `CommandSubmit`). The former is
/// the only route on the web build, where the canvas — not the command
/// line — holds keyboard focus.
#[test]
fn extrude_typed_height_from_dyn_field() {
use crate::app::Message;
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
// A closed rectangle in the XY plane.
use acadrust::entities::LwVertex;
use acadrust::types::Vector2;
let rect = acadrust::LwPolyline {
vertices: [
(-10.0, -10.0),
(10.0, -10.0),
(10.0, 10.0),
(-10.0, 10.0),
]
.iter()
.map(|(x, y)| LwVertex::new(Vector2::new(*x, *y)))
.collect(),
is_closed: true,
..Default::default()
};
let handle = app.tabs[0]
.scene
.document
.add_entity(acadrust::EntityType::LwPolyline(rect))
.unwrap();
// Start EXTRUDE via the command line.
let _ = app.update(Message::CommandInput("EXTRUDE".to_string()));
let _ = app.update(Message::CommandSubmit);
assert_eq!(
app.tabs[0].active_cmd.as_ref().map(|c| c.name()),
Some("EXTRUDE")
);
// Pick the rectangle: the GUI route is an entity pick at a world point.
let _ = app.feed_command(crate::command::StepInput::EntityPick(
handle,
glam::DVec3::new(-10.0, -10.0, 0.0),
));
// Bare Enter finishes selection → Height step (DYN input is on by
// default, so the prompt shows a Distance field).
let _ = app.update(Message::CommandFinalize);
assert_eq!(
app.tabs[0].active_cmd.as_ref().map(|c| c.name()),
Some("EXTRUDE")
);
assert!(
app.dyn_input
&& app.tabs[0]
.dyn_fields
.iter()
.any(|f| f.role == crate::command::DynRole::Distance)
);
// The unfocused-input route for digits while dyn fields show:
// CommandAppendChar fills the Distance field buffer, not the
// command-line input.
for ch in ["5"] {
let _ = app.update(Message::CommandAppendChar(ch.to_string()));
}
assert_eq!(app.command_line.input, "");
assert!(
app.tabs[0]
.dyn_fields
.iter()
.any(|f| f.buffer.as_deref() == Some("5"))
);
// Enter commits the typed height through the dyn-field buffer.
let _ = app.update(Message::CommandFinalize);
let solid_count = app.tabs[0]
.scene
.document
.entities()
.filter(|e| matches!(e, acadrust::EntityType::Solid3D(_)))
.count();
assert!(solid_count > 0, "typed height must extrude");
assert!(
app.tabs[0].active_cmd.is_none(),
"EXTRUDE must finish after the typed height"
);
}
}
+10
View File
@@ -472,6 +472,16 @@ impl CadCommand for ExtrudeCommand {
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
Some((cursor - self.anchor).dot(self.profile_direction?))
}
// The Height step's dynamic box is a span along the profile normal, not a
// point in the drawing plane. Without this, a typed value committed from
// the box (the only route on web, where the canvas — not the command
// line — holds keyboard focus) is resolved into an in-plane point whose
// height projects to ~0 and is silently dropped. `on_text_input` parses
// the same string the command-line path would (keywords and
// `parse_typed_length`), so both entry routes behave identically (#1082).
fn dyn_commit_as_text(&self) -> bool {
self.step == ExtrudeStep::Height
}
fn is_selection_gathering(&self) -> bool {
self.step == ExtrudeStep::Pick
}