mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user