M1: leaving Design ends the pen path and goes back to Move (fixes #307)

This commit is contained in:
Marco Arnulfo
2026-10-14 17:11:20 +02:00
parent 833f0d2c27
commit ed0e7a9b2d
3 changed files with 115 additions and 1 deletions
+20
View File
@@ -122,6 +122,8 @@ mod panel_reorder_tests;
#[cfg(test)]
mod panel_width_tests;
#[cfg(test)]
mod pen_mode_tests;
#[cfg(test)]
mod perf_tests;
#[cfg(test)]
mod plugin_panel_tests;
@@ -280,6 +282,8 @@ pub struct UiState {
pub left_width: f32,
pub right_width: f32,
pub right_tab: RightTab,
/// The tab of the previous frame, to see the right panel change mode.
pub last_right_tab: RightTab,
pub left_tab: LeftTab,
pub pages_open: bool,
pub expanded: BTreeSet<NodeId>,
@@ -400,6 +404,7 @@ impl Default for UiState {
left_width: PANEL_DEFAULT,
right_width: PANEL_DEFAULT,
right_tab: RightTab::Design,
last_right_tab: RightTab::Design,
left_tab: LeftTab::File,
pages_open: true,
expanded: BTreeSet::new(),
@@ -610,6 +615,19 @@ impl FigApp {
}
}
/// Leaving Design ends a pen path as Escape does and goes back to Move: Prototype and Inspect
/// don't draw (#307). Checked as the frame starts (the control channel) and after the panels
/// and shortcuts (the mode switcher, the tabs, Shift+E/Shift+D), before the canvas sees more.
fn end_pen_outside_design(&mut self) {
if self.ui.right_tab == self.ui.last_right_tab {
return;
}
self.ui.last_right_tab = self.ui.right_tab;
if self.ui.right_tab != RightTab::Design && self.session.tool.tool == figcraft_engine::tools::ToolId::Pen {
self.run("tool.set", json!({"tool": "move"}));
}
}
/// Once a frame, after the panels: a preview nothing asked for this frame ends.
fn end_unwanted_text_preview(&mut self, ctx: &egui::Context) {
if !std::mem::take(&mut self.ui.text_preview_wanted) {
@@ -984,6 +1002,7 @@ impl FigApp {
widgets::set_nudge(&ctx, self.session.prefs.nudge_small, self.session.prefs.nudge_big);
// The Inspect tab measures on hover without Alt.
self.session.tool.redlines = self.ui.right_tab == RightTab::Inspect;
self.end_pen_outside_design();
if self.ui.present.is_some() {
present::show(self, ui);
// What went wrong while presenting (a widget's plugin failing) shows over it.
@@ -1086,6 +1105,7 @@ impl FigApp {
variable_ui::picker(self, &ctx);
style_picker::show(self, &ctx);
self.end_unwanted_text_preview(&ctx);
self.end_pen_outside_design();
dialogs::show(self, &ctx);
self.toast_ui(&ctx);
// A rename started after the layers panel drew (Cmd/Ctrl+R, a double-click) opens its field
+94
View File
@@ -0,0 +1,94 @@
//! Leaving Design with a pen path open (#307): the path ends as Escape ends it, the tool goes back
//! to Move, and the canvas of Prototype or Inspect takes no more pen points. Driven by the panel
//! keys and by the toolbar's mode switcher.
use egui::accesskit::Role;
use egui::{Event, Key, Modifiers, Pos2, pos2};
use figcraft_engine::Session;
use figcraft_engine::tools::ToolId;
use serde_json::json;
use crate::RightTab;
use crate::layers_drag_tests::Ui;
fn press(u: &mut Ui, key: Key, modifiers: Modifiers) {
u.frame(vec![
Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers },
Event::Key { key, physical_key: None, pressed: false, repeat: false, modifiers },
]);
}
fn click(u: &mut Ui, p: Pos2) {
u.button(p, true);
u.button(p, false);
u.frame(vec![]);
}
fn layers(u: &Ui) -> usize {
let doc = u.app.session.doc().unwrap();
doc.node(u.app.session.page().unwrap()).unwrap().children.len()
}
/// The middle of the toolbar's mode button named `label`, found in the AccessKit tree.
fn mode_button(u: &mut Ui, label: &str) -> Pos2 {
u.ctx.enable_accesskit();
u.frame(vec![]);
let out = u.frame_output(vec![]);
let nodes = out.platform_output.accesskit_update.expect("an AccessKit update").nodes;
let n = nodes.iter().map(|(_, n)| n).find(|n| n.role() == Role::Button && n.label() == Some(label)).expect(label);
let b = n.bounds().expect("bounds");
pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
}
/// A pen path with two anchors still open on an empty page.
fn drawing() -> Ui {
let mut u = Ui::new(Session::new());
u.app.run("tool.set", json!({"tool": "pen"}));
click(&mut u, pos2(600.0, 400.0));
click(&mut u, pos2(700.0, 450.0));
assert_eq!(u.app.session.tool.pen.as_ref().map(|p| p.points.len()), Some(2), "the path is open");
u
}
#[test]
fn switching_mode_ends_the_pen_path_as_escape_does() {
let mut esc = drawing();
press(&mut esc, Key::Escape, Modifiers::NONE);
assert!(esc.app.session.tool.pen.is_none());
let finished = layers(&esc);
assert_eq!(finished, 1, "Escape keeps the two-anchor path");
// Shift+E is Prototype, Shift+D is Inspect.
for key in [Key::E, Key::D] {
let mut u = drawing();
press(&mut u, key, Modifiers::SHIFT);
assert_ne!(u.app.ui.right_tab, RightTab::Design, "{key:?}");
assert!(u.app.session.tool.pen.is_none(), "{key:?}: the path is finished");
assert_eq!(u.app.session.tool.tool, ToolId::Move, "{key:?}: back to Move");
assert_eq!(layers(&u), finished, "{key:?}: the path is kept as Escape keeps it");
// The canvas takes no more pen points.
click(&mut u, pos2(800.0, 300.0));
assert!(u.app.session.tool.pen.is_none(), "{key:?}: a click starts no path");
assert_eq!(layers(&u), finished, "{key:?}: a click adds nothing");
}
}
#[test]
fn the_mode_switcher_ends_the_pen_path_too() {
let finished = {
let mut esc = drawing();
press(&mut esc, Key::Escape, Modifiers::NONE);
layers(&esc)
};
// Design is already the mode while the pen draws: pressing it still leaves the pen, as it leaves
// the pencil of Draw.
for (label, tab) in [("Prototype", RightTab::Prototype), ("Inspect", RightTab::Inspect), ("Design", RightTab::Design)] {
let mut u = drawing();
let at = mode_button(&mut u, label);
click(&mut u, at);
assert_eq!(u.app.ui.right_tab, tab, "{label}");
assert!(u.app.session.tool.pen.is_none(), "{label}: the path is finished");
assert_eq!(u.app.session.tool.tool, ToolId::Move, "{label}: back to Move");
assert_eq!(layers(&u), finished, "{label}: the path is kept as Escape keeps it");
}
}
+1 -1
View File
@@ -174,7 +174,7 @@ pub fn show(app: &mut FigApp, ui: &mut Ui) {
}
1 => {
app.ui.right_tab = crate::RightTab::Design;
if app.session.tool.tool == ToolId::Pencil {
if matches!(app.session.tool.tool, ToolId::Pencil | ToolId::Pen) {
app.run("tool.set", json!({"tool": "move"}));
}
}