View: Escape that cancels a ruler or Tools move ends nothing else

An Escape press that cancels a ruler-origin drag or a floating Tools
panel move no longer also closes Edit Toolbar's drawer, leaves pattern
edit, isolation or Global Edit, closes the tool flyout or resets a
rotated view. A later Escape does that action as usual.
This commit is contained in:
dotCooCoo
2026-10-31 02:52:22 +07:00
committed by bflatastic
parent dcd4b437a5
commit 030baab798
3 changed files with 142 additions and 6 deletions
+3 -4
View File
@@ -205,9 +205,10 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
presentation_keys(app, ctx, escape_pair);
return;
}
let escape_ended_gesture = crate::canvas::ruler_origin_dragging(ctx) || crate::tools_float::escape_cancelled_move(ctx);
let typing = ctx.egui_wants_keyboard_input();
// Escape closes Edit Toolbar's drawer first (egui drops a drag from it on Escape already).
if app.ui.tools_drawer && !typing && ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Escape)) {
if app.ui.tools_drawer && !typing && !escape_ended_gesture && ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Escape)) {
app.ui.tools_drawer = false;
}
let view = app.view_info();
@@ -248,7 +249,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
ctx.input_mut(|i| i.consume_key(Modifiers::NONE, k));
ctx.input_mut(|i| i.consume_key(Modifiers::SHIFT, k));
}
if k == Key::Escape && !busy && !claimed {
if k == Key::Escape && !busy && !claimed && !escape_ended_gesture {
if app.session.active().is_some_and(|d| d.doc.pattern_edit.is_some()) {
let _ = app.run("object.pattern.done", json!({}));
} else if app.session.active().is_some_and(|d| d.isolation.is_some()) {
@@ -264,8 +265,6 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
} else if matches!(app.ui.screen_mode, 1 | 2)
&& !egui::Popup::is_any_open(ctx)
&& !text_focused_in_previous_frame(ctx)
&& !crate::canvas::ruler_origin_dragging(ctx)
&& !crate::tools_float::escape_cancelled_move(ctx)
&& ctx.input(|i| {
i.events.iter().any(|e| {
matches!(e, egui::Event::Key { key: Key::Escape, pressed: true, repeat: false, modifiers, .. } if *modifiers == Modifiers::NONE)
+3 -2
View File
@@ -34,13 +34,14 @@ pub(super) fn escape_press(ctx: &egui::Context) -> Option<bool> {
}
/// Does this Escape reset the view's rotation? It does when the view is rotated, no popup is open,
/// the ruler origin is not being dragged, and the press is the second of a pair (`second`) or the
/// Rotate View tool is chosen.
/// neither a ruler-origin drag nor a floating Tools move is ending, and the press is the second of
/// a pair (`second`) or the Rotate View tool is chosen.
pub(super) fn escape_resets_rotation(app: &VectorcraftApp, ctx: &egui::Context, second: bool) -> bool {
app.view().is_some_and(|v| v.rotation != 0.0)
&& (second || app.session.tool_id() == "rotateView")
&& !egui::Popup::is_any_open(ctx)
&& !crate::canvas::ruler_origin_dragging(ctx)
&& !crate::tools_float::escape_cancelled_move(ctx)
}
/// The keys in Presentation Mode, where nothing is edited: Escape leaves the mode, and ← and → go to
+136
View File
@@ -80,6 +80,80 @@ fn in_screen_mode(ctx: &egui::Context, mode: u8) -> VectorcraftApp {
app
}
fn begin_tools_move(app: &mut VectorcraftApp, ctx: &egui::Context) -> (Option<[f32; 2]>, egui::Pos2) {
app.run("window.panel.float", json!({"panel": "tools", "x": 120, "y": 140})).unwrap();
frame(app, ctx, vec![]);
frame(app, ctx, vec![]);
let original = app.ui.toolbar_pos;
let area = ctx.memory(|m| m.area_rect(egui::Id::unique("toolbar-floating"))).expect("the floating Tools area");
let title = area.min + egui::vec2(20.0, 17.0);
let to = title + egui::vec2(100.0, 80.0);
let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, ctx, vec![egui::Event::PointerMoved(title), button(title, true)]);
for step in 1..=6 {
frame(app, ctx, vec![egui::Event::PointerMoved(title + (to - title) * (step as f32 / 6.0))]);
}
assert_ne!(app.ui.toolbar_pos, original, "the Tools panel moved");
(original, to)
}
fn begin_ruler_origin_drag(app: &mut VectorcraftApp, ctx: &egui::Context) -> egui::Pos2 {
app.ui.view.rulers = true;
frame(app, ctx, vec![]);
let corner = ctx.read_response(egui::Id::unique("ruler-corner")).expect("the rulers' corner").rect.center();
let to = app.canvas_rect.unwrap().center();
let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, ctx, vec![egui::Event::PointerMoved(corner)]);
frame(app, ctx, vec![button(corner, true)]);
frame(app, ctx, vec![egui::Event::PointerMoved(to)]);
assert!(crate::canvas::ruler_origin_dragging(ctx));
to
}
fn release_drag(app: &mut VectorcraftApp, ctx: &egui::Context, to: egui::Pos2) {
frame(
app,
ctx,
vec![egui::Event::PointerButton { pos: to, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() }],
);
}
fn start_escape_action(app: &mut VectorcraftApp, ctx: &egui::Context, action: &str) {
match action {
"drawer" => app.ui.tools_drawer = true,
"pattern" => {
app.run("object.pattern.make", json!({"name": "Dots", "width": 20, "height": 20})).unwrap();
}
"isolation" => {
app.run("shape.rectangle", json!({"x": 130, "y": 40, "width": 20, "height": 20})).unwrap();
app.run("select.all", json!({})).unwrap();
app.run("object.group", json!({})).unwrap();
app.run("object.isolate", json!({})).unwrap();
}
"global" => {
app.run("shape.rectangle", json!({"x": 130, "y": 40, "width": 20, "height": 20})).unwrap();
app.run("select.globalEdit", json!({})).unwrap();
}
"flyout" => {
app.ui.flyout = Some(0);
ctx.data_mut(|d| d.insert_temp(egui::Id::unique("flyout-tools"), vec!["selection".to_string(), "directSelection".to_string()]));
}
_ => panic!("unknown Escape action"),
}
assert!(escape_action_active(app, action), "{action} starts");
}
fn escape_action_active(app: &VectorcraftApp, action: &str) -> bool {
match action {
"drawer" => app.ui.tools_drawer,
"pattern" => app.session.active().unwrap().doc.pattern_edit.is_some(),
"isolation" => app.session.active().unwrap().isolation.is_some(),
"global" => app.session.active().unwrap().global_edit.is_some(),
"flyout" => app.ui.flyout.is_some(),
_ => false,
}
}
#[test]
fn escape_leaves_both_full_screen_modes_without_editing() {
for mode in [1, 2] {
@@ -200,6 +274,68 @@ fn a_floating_tools_move_takes_escape_before_full_screen_exits() {
}
}
#[test]
fn cancelling_tools_move_keeps_rotation_in_both_full_screen_modes() {
for mode in [1, 2] {
for tool in ["rotateView", "selection"] {
let ctx = egui::Context::default();
let mut app = in_screen_mode(&ctx, mode);
app.select_tool(tool);
app.run("view.rotate", json!({"angle": 30})).unwrap();
if tool == "selection" {
frame(&mut app, &ctx, escape());
assert_eq!(app.ui.screen_mode, 0, "first Escape leaves mode {mode}");
assert_eq!(app.view().unwrap().rotation, 30.0);
}
let (original, to) = begin_tools_move(&mut app, &ctx);
frame(&mut app, &ctx, escape());
assert_eq!(app.ui.toolbar_pos, original, "{tool} in mode {mode}");
assert_eq!(app.view().unwrap().rotation, 30.0, "{tool} in mode {mode}");
release_drag(&mut app, &ctx, to);
frame(&mut app, &ctx, escape());
assert_eq!(app.view().unwrap().rotation, 0.0, "later Escape resets rotation");
}
}
}
#[test]
fn cancelling_tools_move_keeps_the_next_escape_action() {
for mode in [0, 1, 2] {
for action in ["drawer", "pattern", "isolation", "global", "flyout"] {
let ctx = egui::Context::default();
let mut app = in_screen_mode(&ctx, mode);
let (original, to) = begin_tools_move(&mut app, &ctx);
start_escape_action(&mut app, &ctx, action);
frame(&mut app, &ctx, escape());
assert_eq!(app.ui.toolbar_pos, original, "move cancelled in mode {mode}, {action}");
assert!(escape_action_active(&app, action), "Escape keeps {action} in mode {mode}");
assert_eq!(app.ui.screen_mode, mode, "mode {mode}, {action}");
release_drag(&mut app, &ctx, to);
frame(&mut app, &ctx, escape());
assert!(!escape_action_active(&app, action), "later Escape ends {action} in mode {mode}");
}
}
}
#[test]
fn cancelling_ruler_origin_drag_keeps_the_next_escape_action() {
for mode in [0, 1, 2] {
for action in ["pattern", "isolation", "flyout"] {
let ctx = egui::Context::default();
let mut app = in_screen_mode(&ctx, mode);
let to = begin_ruler_origin_drag(&mut app, &ctx);
start_escape_action(&mut app, &ctx, action);
frame(&mut app, &ctx, escape());
assert!(escape_action_active(&app, action), "Escape keeps {action} in mode {mode}");
assert_eq!(app.ui.screen_mode, mode, "mode {mode}, {action}");
release_drag(&mut app, &ctx, to);
assert!(!crate::canvas::ruler_origin_dragging(&ctx));
frame(&mut app, &ctx, escape());
assert!(!escape_action_active(&app, action), "later Escape ends {action} in mode {mode}");
}
}
}
#[test]
fn a_popup_takes_escape_before_full_screen_exits() {
let ctx = egui::Context::default();