Sketch: a preference to stop a quick double Esc from finishing the sketch (#12069) (#12694)

* claim: #12069

* wip: Sketch preference: a quick double Esc can be kept from finishing the sketch (#12069)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* wip: profile key count includes the new sketch preference (#12069)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
PaddleStrokeandClaude Opus 5.5 authored and GitHub committed 2026-11-07 17:08:58 +00:00
1 parent a6c0e8a959
commit 6f5894683c
9 files changed
+108 -4

No files matched your search

@@ -0,0 +1,38 @@
{
"en": {
"prefs.sketch.escape_twice_finishes": "Double Esc finishes the sketch",
"prefs.sketch.escape_twice_finishes_note": "Two quick presses of Esc end the sketch. Off: Esc only cancels the tool or the selection, and Finish Sketch ends the sketch."
},
"ar": {
"prefs.sketch.escape_twice_finishes": "الضغط المزدوج على Esc ينهي الرسم التخطيطي",
"prefs.sketch.escape_twice_finishes_note": "ضغطتان سريعتان على Esc تنهيان الرسم التخطيطي. عند الإيقاف: يلغي Esc الأداة أو التحديد فقط، وينهي أمر «إنهاء الرسم» الرسم التخطيطي."
},
"de": {
"prefs.sketch.escape_twice_finishes": "Doppeltes Esc beendet die Skizze",
"prefs.sketch.escape_twice_finishes_note": "Zweimal schnell Esc drücken beendet die Skizze. Aus: Esc bricht nur das Werkzeug oder die Auswahl ab, und Skizze beenden schließt die Skizze."
},
"fr": {
"prefs.sketch.escape_twice_finishes": "Un double Échap termine l'esquisse",
"prefs.sketch.escape_twice_finishes_note": "Deux pressions rapides sur Échap terminent l'esquisse. Désactivé : Échap annule seulement l'outil ou la sélection, et Terminer l'esquisse ferme l'esquisse."
},
"he": {
"prefs.sketch.escape_twice_finishes": "לחיצה כפולה על Esc מסיימת את הסקיצה",
"prefs.sketch.escape_twice_finishes_note": "שתי לחיצות מהירות על Esc מסיימות את הסקיצה. כבוי: Esc רק מבטל את הכלי או את הבחירה, ו'סיום סקיצה' מסיים את הסקיצה."
},
"ja": {
"prefs.sketch.escape_twice_finishes": "Esc を 2 回押してスケッチを終了",
"prefs.sketch.escape_twice_finishes_note": "Esc をすばやく 2 回押すとスケッチを終了します。オフ: Esc はツールや選択の取り消しだけを行い、スケッチは「スケッチを終了」で終了します。"
},
"ko": {
"prefs.sketch.escape_twice_finishes": "Esc를 두 번 누르면 스케치 종료",
"prefs.sketch.escape_twice_finishes_note": "Esc를 빠르게 두 번 누르면 스케치를 끝냅니다. 끄면: Esc는 도구나 선택만 취소하고, 스케치는 스케치 종료로 끝냅니다."
},
"vi": {
"prefs.sketch.escape_twice_finishes": "Nhấn Esc hai lần để kết thúc bản phác thảo",
"prefs.sketch.escape_twice_finishes_note": "Nhấn nhanh Esc hai lần sẽ kết thúc bản phác thảo. Tắt: Esc chỉ hủy công cụ hoặc vùng chọn, và Hoàn tất phác thảo sẽ kết thúc bản phác thảo."
},
"zh": {
"prefs.sketch.escape_twice_finishes": "连按两次 Esc 结束草图",
"prefs.sketch.escape_twice_finishes_note": "快速按两次 Esc 会结束草图。关闭时:Esc 只取消工具或选择,用“完成草图”结束草图。"
}
}
+5
View File
@@ -1643,6 +1643,10 @@ preference_settings! {
/// Opening or editing a sketch turns the view to face it (the default). Off: the view stays as
/// it is, and Look At faces the sketch when asked.
sketch_open_look_at: bool = true, PAGE_SKETCH;
/// Two quick presses of Esc in a sketch, with nothing left to cancel, finish it (the default,
/// as before). Off: Esc only cancels the tool, dialog or selection and the sketch stays open;
/// Finish Sketch ends it (#12069).
escape_twice_finishes_sketch: bool = true, PAGE_SKETCH;
/// A sketch started on a face of a body in another component than the active one activates
/// that component first, so the sketch belongs to it (`sketch.create` `activate_part`,
/// #10215). Off (the default): the sketch belongs to the active component, as before.
@@ -7823,6 +7827,7 @@ mod tests {
("project_options_dialog", "prefs.sketch.project_options"),
("sketch_face_activates_part", "prefs.sketch_face_activates_part"),
("sketch_open_look_at", "prefs.sketch_open_look_at"),
("escape_twice_finishes_sketch", "prefs.sketch.escape_twice_finishes"),
("selection_shift_add_only", "prefs.shift_add_ctrl_toggle"),
("prefer_free_sketch_points", "prefs.sketch.prefer_free_points"),
("select_other_hidden", "prefs.sketch.select_other_hidden"),
+7
View File
@@ -205,6 +205,13 @@ fn opening(app: &mut SolveApp, ui: &mut egui::Ui) {
Some("prefs.sketch_open_look_at_note"),
&mut app.preferences.sketch_open_look_at,
);
check(
ui,
"escape_twice_finishes_sketch",
"prefs.sketch.escape_twice_finishes",
Some("prefs.sketch.escape_twice_finishes_note"),
&mut app.preferences.escape_twice_finishes_sketch,
);
picture_group(ui, app.tr("prefs.sketch_exit_view"), |ui| {
let label = app.tr("prefs.sketch_exit_view");
let mut indexed = crate::prefs_search::texts_hit(ui, &[label]);
+1
View File
@@ -56,6 +56,7 @@ const PROFILE_KEYS: &[&str] = &[
"dialog_buttons",
"dialog_place",
"direct_interaction",
"escape_twice_finishes_sketch",
"free_orbit",
"free_orbit_upright",
"hidden_toolbar_tabs",
@@ -17,7 +17,7 @@ fn error(h: &mut Harness, id: &str, params: Value) -> String {
#[test]
fn a_profile_holds_input_and_interface_settings_but_not_the_machine() {
let keys: Vec<&str> = keys().collect();
assert_eq!(keys.len(), 102, "Appearance includes its two system-theme slots");
assert_eq!(keys.len(), 103, "Appearance includes its two system-theme slots");
for key in [
"nav",
"mouse_gestures",
@@ -787,6 +787,7 @@ fn profile_keys_preserve_the_baseline_and_explicit_additions() {
"dialog_buttons",
"dialog_place",
"direct_interaction",
"escape_twice_finishes_sketch",
"free_orbit",
"free_orbit_upright",
"hidden_toolbar_tabs",
@@ -880,7 +881,7 @@ fn profile_keys_preserve_the_baseline_and_explicit_additions() {
];
let mut actual: Vec<_> = keys().collect();
actual.sort_unstable();
assert_eq!(expected.len(), 102);
assert_eq!(expected.len(), 103);
assert_eq!(actual, expected);
}
+49
View File
@@ -1218,6 +1218,55 @@ mod tests {
assert!(h.app.session.active_sketch.is_none());
}
/// With "Double Esc finishes the sketch" off (#12069), any number of quick presses only cancel
/// the tool and the selection, and the sketch stays open, blank or not; Finish Sketch still
/// ends it. On (the default) keeps the double press finishing it.
#[test]
fn escape_never_leaves_the_sketch_when_the_double_press_preference_is_off() {
assert!(crate::prefs::Prefs::default().escape_twice_finishes_sketch, "the default keeps today's double Esc");
let mut h = crate::scenario::Harness::new();
h.call("ui.prefs", json!({"escape_twice_finishes_sketch": false}));
assert!(!h.app.preferences.escape_twice_finishes_sketch, "set through the control channel");
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
h.settle();
let sketch = h.app.session.active_sketch;
assert!(sketch.is_some());
// Blank: a double Esc would take it away when the preference is on.
for _ in 0..10 {
h.step(&json!({"key":"Escape"})).unwrap();
}
h.settle();
assert_eq!(h.app.session.active_sketch, sketch, "ten quick presses keep the blank sketch open");
h.app.run("sketch.rectangle.two_point", json!({"p0":[0,0],"p1":[40,30]})).unwrap();
h.settle();
h.step(&json!({"start":"sketch.line"})).unwrap();
assert!(h.app.tool.is_some());
for _ in 0..4 {
h.step(&json!({"key":"Escape"})).unwrap();
}
h.settle();
assert!(h.app.tool.is_none(), "Esc still puts the tool down");
assert!(h.app.session.selection.is_empty(), "and clears the selection");
assert_eq!(h.app.session.active_sketch, sketch, "the sketch stays open");
h.app.finish_sketch();
h.settle();
assert!(h.app.session.active_sketch.is_none(), "Finish Sketch still ends it");
assert!(crate::scenario::handle_at("timeline:Sketch1").is_some(), "the sketch is kept");
// Back on, a quick double press finishes again.
h.call("ui.prefs", json!({"escape_twice_finishes_sketch": true}));
h.step(&json!({"click": {"handle": "timeline:Sketch1"}, "double": true})).unwrap();
h.settle();
assert!(h.app.session.active_sketch.is_some());
h.frames(60);
h.step(&json!({"key":"Escape"})).unwrap();
h.step(&json!({"key":"Escape"})).unwrap();
h.settle();
assert!(h.app.session.active_sketch.is_none(), "on: a double Esc finishes the sketch");
}
/// A double Esc on a sketch left blank takes it away (no empty sketch in the browser or the
/// timeline, one undo step) and leaves the view alone; one line is enough to keep it.
#[test]
+3 -2
View File
@@ -3696,9 +3696,10 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
continue;
}
// Two quick presses in a sketch finish it (keeping it, like Finish Sketch);
// the first one has already cancelled whatever was active.
// the first one has already cancelled whatever was active. A preference turns
// this off, so Esc never leaves the sketch (#12069).
let now = ctx.input(|i| i.time);
let again = app.last_escape.is_some_and(|t| now - t <= ESCAPE_TWICE_S);
let again = app.preferences.escape_twice_finishes_sketch && app.last_escape.is_some_and(|t| now - t <= ESCAPE_TWICE_S);
app.last_escape = Some(now);
// Not while a tool is still out (its first Esc only ended a chain): this press
// puts it down, and the next quick one finishes.
+1
View File
@@ -886,6 +886,7 @@ One setting per line, sorted by name. A change that adds a setting adds its own
- `dimension_input` (`view` (default): a sketch dimension's value is typed in a box on the dimension; `window`: placing a dimension with the Dimension tool, or double-clicking one, opens a small window at it with the value field (handle `dimwin:value`), arrows that step the value (`dimwin:up`, `dimwin:down`), a check (`dimwin:ok`, also Enter) and a cross (`dimwin:cancel`, also Esc), #7246)
- `dimension_step_deg` (default 1, 0.001…180: what the dimension window's arrows, Up/Down and the wheel over its field add to an angle, in degrees)
- `dimension_step_mm` (default 1, 0.001…1000: what the window's arrows, Up/Down and the wheel over its field add to a length, in millimetres, written in the unit the value is written in)
- `escape_twice_finishes_sketch` (default true: in a sketch, two quick presses of Esc (0.4 s apart or less) with no tool or dialog left to cancel finish it, as before; false: Esc only cancels the tool, dialog or selection and the sketch stays open until Finish Sketch, #12069)
- `extrude_arrow_face_stops` (default true: dragging Extrude's distance arrow stops at flat faces parallel to the profile that lie over it, takes their exact distance and outlines them, #2398; false: the drag moves in round steps only, as before)
- `extrude_auto_to_object` (default true: in the Extrude dialog, with a profile picked, a face, vertex or parallel plane picked off its plane switches the extent to To Object, unless the Profiles box was clicked into; it applies to Extrude dialogs opened afterwards)
- `frame_rate_details` (default false: with the frame rate, a frame-time graph of the frames drawn with the triangles over it, a frame-time histogram and what the GPU drew; clicking the readout switches it)
+1
View File
@@ -39,6 +39,7 @@ Two columns of checkboxes.
|---|---|---|---|---|---|---|
| Sketching on a part's face makes that part active | Checkbox | `Prefs.sketch_face_activates_part` | `false` | Existing `prefs.sketch_face_activates_part_note`: activate the other component before creating the sketch; it stays active. Off keeps the active component. | none | Retained newer control; first row, immediately before turn-to-face. |
| Turn to face a sketch when it opens | Checkbox | `Prefs.sketch_open_look_at` | `true` | none | none | |
| Double Esc finishes the sketch | Checkbox | `Prefs.escape_twice_finishes_sketch` | `true` | `prefs.sketch.escape_twice_finishes_note`: two quick presses of Esc end the sketch; off, Esc only cancels the tool or the selection. | none | #12069; immediately after turn-to-face. |
| When a sketch ends: Return to the view before / Keep (default) / Unless turned (return if not rotated) | Three picture cards (196 x 152.5), a before / sketch / after trio each | `Prefs.sketch_exit_view` (`SketchExitView`: Previous, Keep, UnlessRotated) | Keep | none | `sketch-exit-previous`, `sketch-exit-keep`, `sketch-exit-unless-turned` | Card labels: "Return to the view before" / "Keep" with sub-label "default" / "Unless turned" with sub-label "return if not rotated". The 30x30 panels are nested SVGs inside the figure. |
## Marks in the sketch