Merge pull request #5576 from dotCooCoo/fix/pattern-done-gives-back-lent-type-tool

Type: Done during a Cmd press does not save empty point type
This commit is contained in:
BFlat
2026-11-04 18:52:15 +07:00
committed by GitHub
3 changed files with 108 additions and 4 deletions
+26 -1
View File
@@ -6,7 +6,7 @@
use serde_json::{Value, json};
use vectorcraft_geom::Affine;
use vectorcraft_render::{RenderOptions, Renderer};
use vectorcraft_tools::{PointerEvent, PointerKind};
use vectorcraft_tools::{Mods, PointerEvent, PointerKind};
use crate::cmd::maskedit::MASK_EDIT_LAYER;
use crate::tests::{history, select_with_a_step_to_redo};
@@ -234,6 +234,31 @@ fn empty_point_type_is_not_saved_into_the_mask() {
}
}
/// The same while a Cmd/Ctrl press has lent the Selection tool to the Type tool: the command before
/// the release gives the Type tool back first, so the empty point type is still discarded.
#[test]
fn empty_point_type_is_not_saved_into_the_mask_during_a_cmd_press() {
for cmd in ["transparency.stopEditingOpacityMask", "object.exitIsolation"] {
let mut s = session();
s.prefs.placeholder_text = false;
let obj = editing_mask(&mut s);
let v = ViewInfo::default();
s.select_tool("type", v).unwrap();
for kind in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(kind, 250.0, 250.0), v).unwrap();
}
let press = Mods { cmd: true, ..Mods::default() };
s.pointer(&PointerEvent::new(PointerKind::Down, 280.0, 280.0).with_mods(press), v).unwrap();
assert_eq!(s.tool_id(), "selection", "{cmd}: the press lends the Selection tool");
s.execute(cmd, &json!({})).unwrap();
assert_eq!(s.tool_id(), "type", "{cmd}: the Type tool is back");
assert!(!s.tool_wants_text(), "{cmd}");
s.pointer(&PointerEvent::new(PointerKind::Up, 280.0, 280.0).with_mods(press), v).unwrap();
let art = &mask(&s, obj).unwrap().art;
assert!(art.children().is_none() && !matches!(art.kind, vectorcraft_doc::NodeKind::Text(_)), "{cmd}: the mask keeps only the square");
}
}
/// Releasing the masks of a group and of an object inside it puts each mask's art directly above
/// its object, as releasing one mask at a time does.
#[test]
+67 -1
View File
@@ -5,7 +5,7 @@ use vectorcraft_color::Paint;
use vectorcraft_doc::pattern::{PATTERN_EDIT_LAYER, RepeatKind, TileType};
use vectorcraft_doc::{Node, NodeKind};
use vectorcraft_geom::{Point, Rect};
use vectorcraft_tools::{PointerEvent, PointerKind};
use vectorcraft_tools::{Mods, PointerEvent, PointerKind};
use crate::tests_menucmds::place_undecodable_png;
use crate::*;
@@ -134,6 +134,72 @@ fn empty_point_type_is_not_saved_into_the_pattern() {
}
}
/// The same while a Cmd/Ctrl press has lent the Selection tool to the Type tool: Done or Exit
/// Isolation Mode before the release gives the Type tool back first, so the empty point type is
/// still discarded, and the release that follows leaves the tile as it is.
#[test]
fn empty_point_type_is_not_saved_into_the_pattern_during_a_cmd_press() {
for cmd in ["object.pattern.done", "object.exitIsolation"] {
let mut s = session();
s.prefs.placeholder_text = false;
let r = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
sel(&mut s, &[r]);
s.execute("object.pattern.make", &json!({"name": "P"})).unwrap();
let v = ViewInfo::default();
s.select_tool("type", v).unwrap();
for kind in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(kind, 200.0, 200.0), v).unwrap();
}
let press = Mods { cmd: true, ..Mods::default() };
s.pointer(&PointerEvent::new(PointerKind::Down, 300.0, 300.0).with_mods(press), v).unwrap();
assert_eq!(s.tool_id(), "selection", "{cmd}: the press lends the Selection tool");
s.execute(cmd, &json!({})).unwrap();
assert_eq!(s.tool_id(), "type", "{cmd}: the Type tool is back");
assert!(!s.tool_wants_text(), "{cmd}");
s.pointer(&PointerEvent::new(PointerKind::Up, 300.0, 300.0).with_mods(press), v).unwrap();
let art = &doc(&s).pattern("P").unwrap().art;
assert_eq!(art.len(), 1, "{cmd}: the tile keeps only the square");
assert!(!matches!(art[0].kind, NodeKind::Text(_)), "{cmd}");
}
}
/// Done during a drag of the lent Selection tool keeps the drag as its own step before the tile is
/// saved, so no drag is left open for the release to end. With Real-time Drawing and Editing off,
/// the drag's last position is applied first, as the release would apply it.
#[test]
fn done_during_a_lent_selection_drag_keeps_the_drag_as_its_own_step() {
for real_time in [true, false] {
let mut s = session();
s.prefs.placeholder_text = false;
s.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap();
let r = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
sel(&mut s, &[r]);
s.execute("object.pattern.make", &json!({"name": "P"})).unwrap();
let st = s.doc().unwrap();
let b = st.doc.bounds_of(&st.selection.objects, false).expect("the tile art is selected");
let c = b.center();
let v = ViewInfo::default();
s.select_tool("type", v).unwrap();
for kind in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(kind, 200.0, 200.0), v).unwrap();
}
let press = Mods { cmd: true, ..Mods::default() };
s.pointer(&PointerEvent::new(PointerKind::Down, c.x, c.y).with_mods(press), v).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Drag, c.x + 20.0, c.y).with_mods(press), v).unwrap();
assert!(s.in_interaction(), "{real_time}: the lent Selection tool drags the square");
s.execute("object.pattern.done", &json!({})).unwrap();
assert!(!s.in_interaction(), "{real_time}: the drag is kept before the tile is saved");
assert_eq!(s.tool_id(), "type", "{real_time}");
s.pointer(&PointerEvent::new(PointerKind::Up, c.x + 20.0, c.y).with_mods(press), v).unwrap();
let labels = s.doc().unwrap().history.undo.iter().map(|e| e.label.as_str()).collect::<Vec<_>>();
assert_eq!(labels.last(), Some(&"Done Editing Pattern"), "{real_time}: {labels:?}");
assert!(labels.contains(&"Move"), "{real_time}: {labels:?}");
let art = &doc(&s).pattern("P").unwrap().art;
assert_eq!(art.len(), 1, "{real_time}: the tile keeps only the square");
assert!(!matches!(art[0].kind, NodeKind::Text(_)), "{real_time}");
}
}
#[test]
fn cancel_new_pattern_removes_it() {
let mut s = session();
+15 -2
View File
@@ -861,9 +861,22 @@ impl Session {
/// End the Type tool's text editing before a command saves the art of an editing layer (pattern
/// editing's Done, Stop Editing Opacity Mask): point type its click placed that is still empty is
/// discarded first, so it is not saved into the pattern tile or the mask.
/// discarded first, so it is not saved into the pattern tile or the mask. A selection tool that a
/// Cmd press lent is given back first, so the Type tool behind it ends its edit too; a drag of the
/// lent tool is kept as its own step, as its release would keep it.
pub(crate) fn end_text_edit(&mut self) {
if self.in_tool_actions || !self.tool.wants_text() {
if self.in_tool_actions {
return;
}
if self.lender.is_some() {
if let Err(e) = self.apply_actions(vec![Action::Commit]) {
log::warn!("keeping the lent tool's drag before ending the text edit: {e}");
}
if let Err(e) = self.give_back_tool(self.last_view) {
log::warn!("giving back the lent tool before ending the text edit: {e}");
}
}
if !self.tool.wants_text() {
return;
}
let acts = self.with_tool_cx(self.last_view, |t, cx| t.deactivate(cx));