Canvas: a dropped press keeps Type editing and the Crop Image box

A dropped press on the Type tool no longer ends editing and switches tools:
the type being edited stays in editing, with the caret and selection it had
before the press, and the press does not count toward a double or triple
click. A dropped handle or move press with Crop Image puts the box back as it
was at the press and keeps earlier adjustments.
This commit is contained in:
dotCooCoo
2026-11-03 16:03:39 +07:00
committed by bflatastic
parent 641ac7e301
commit 9932096122
4 changed files with 86 additions and 0 deletions
+31
View File
@@ -184,6 +184,16 @@ impl Tool for CropImageTool {
}
}
/// A dropped press puts the box back as it was at the press; earlier changes to it stay.
fn drop_press(&mut self, cx: &ToolContext) -> Vec<Action> {
if let Some(Drag::Handle(_, from) | Drag::Move(_, from)) = self.drag.take()
&& let Some((id, _, _)) = self.current(cx)
{
self.rect = Some((Some(id), from));
}
vec![]
}
fn claims_key(&self, cx: &ToolContext, key: ToolKey) -> bool {
matches!(key, ToolKey::Enter | ToolKey::Escape) && self.current(cx).is_some()
}
@@ -368,6 +378,27 @@ mod tests {
assert!(t.overlays(&cx).is_empty() && !t.claims_key(&cx, ToolKey::Enter));
}
#[test]
fn a_dropped_press_puts_the_box_back_as_it_was_at_the_press() {
let (d, s) = doc_with_image((50.0, 60.0));
let p = paint();
let cx = cx(&d, &s, &p);
let mut t = CropImageTool::default();
drag(&mut t, &cx, (50.0, 60.0), (80.0, 70.0));
assert_eq!(t.options()["rect"], json!([80.0, 70.0, 170.0, 90.0]));
// A handle pressed and dragged, then the press dropped: the box is the adjusted one.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 250.0, 160.0));
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 200.0, 130.0));
assert!(t.drop_press(&cx).is_empty());
assert!(!t.busy());
assert_eq!(t.options()["rect"], json!([80.0, 70.0, 170.0, 90.0]));
// So is a moved box.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 150.0, 110.0));
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 120.0, 100.0));
assert!(t.drop_press(&cx).is_empty());
assert_eq!(t.options()["rect"], json!([80.0, 70.0, 170.0, 90.0]));
}
#[test]
fn crop_box_option_requires_exactly_four_finite_numeric_values() {
let mut tool = CropImageTool::default();
+3
View File
@@ -189,6 +189,9 @@ pub struct TypeTool {
snap: DrawSnap,
/// The press landed in the edited text: dragging selects.
selecting: bool,
/// The caret and anchor from before a press in type, kept until its release; a dropped press
/// puts them back.
before_press: Option<(usize, usize)>,
/// Click counting for double/triple click (position of the last click, count).
clicks: (Option<Point>, u8),
/// Styled copy of the last copied range (pasting the same plain text keeps its styles).
+35
View File
@@ -603,6 +603,41 @@ fn a_drag_across_type_not_being_edited_selects_its_text() {
assert_eq!((tool.editing, tool.sel()), (Some(id), (7, 7)));
}
#[test]
fn a_dropped_press_keeps_the_type_in_editing() {
let (d, id, mut tool) = editing("Hello world");
let (sel, p) = (Selection::default(), paint());
let c = cx(&d, &sel, &p);
let t = TypeTool::text(&c, id).unwrap().clone();
let lay = vectorcraft_text::layout(FontDb::global(), &t);
let pt = |i: usize| {
let (a, b) = vectorcraft_text::caret_position(&lay, i);
t.xf * a.midpoint(b)
};
let (at, to) = (pt(3), pt(7));
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, at.x + 0.1, at.y));
tool.pointer(&c, &PointerEvent::new(PointerKind::Drag, to.x + 0.1, to.y));
assert!(tool.busy());
assert_eq!(tool.sel(), (3, 7));
// The press is dropped: the caret and selection are as they were before it.
let out = tool.drop_press(&c);
assert!(!out.iter().any(|a| matches!(a, Action::SwitchTool(_))));
assert!(!tool.busy());
assert_eq!((tool.editing, tool.sel()), (Some(id), (0, 0)));
// The dropped press does not count toward a double or triple click.
assert_eq!(tool.clicks, (None, 0));
tool.pointer(&c, &PointerEvent::new(PointerKind::Up, at.x + 0.1, at.y));
assert_eq!((tool.editing, tool.sel()), (Some(id), (0, 0)));
// A Shift-press that extends the selection, dropped, leaves the caret where a click put it.
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, at.x + 0.1, at.y));
tool.pointer(&c, &PointerEvent::new(PointerKind::Up, at.x + 0.1, at.y));
assert_eq!(tool.sel(), (3, 3));
tool.pointer(&c, &PointerEvent { mods: SHIFT, ..PointerEvent::new(PointerKind::Down, to.x + 0.1, to.y) });
assert_eq!(tool.sel(), (3, 7));
tool.drop_press(&c);
assert_eq!((tool.editing, tool.sel()), (Some(id), (3, 3)));
}
#[test]
fn select_all_and_styled_paste() {
let (d, _, mut tool) = editing("abc");
+17
View File
@@ -29,6 +29,20 @@ impl Tool for TypeTool {
fn claims_key(&self, _: &ToolContext, key: ToolKey) -> bool {
key == ToolKey::Escape && self.editing.is_some()
}
/// A dropped press drops only the selection drag or the type it was placing: the type being
/// edited stays in editing, with the caret and selection it had before the press.
fn drop_press(&mut self, _cx: &ToolContext) -> Vec<Action> {
if let Some((caret, anchor)) = self.before_press.take() {
(self.caret, self.anchor) = (caret, anchor);
}
self.selecting = false;
self.clicks = (None, 0);
self.press = None;
self.held = None;
self.drag = None;
self.snap.clear();
vec![]
}
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
match ev.kind {
PointerKind::Down => {
@@ -36,6 +50,7 @@ impl Tool for TypeTool {
let same = self.clicks.0.is_some_and(|q| (q - ev.pos).hypot() <= cx.tol(4.0));
let n = if same { self.clicks.1 + 1 } else { 1 };
self.clicks = (Some(ev.pos), n);
self.before_press = Some((self.caret, self.anchor));
let out = self.move_to(byte, ev.mods.shift);
self.goal_x = None;
if n >= 3 {
@@ -51,6 +66,7 @@ impl Tool for TypeTool {
// as it does in the type being edited.
if let Some(out) = self.edit_at(cx, ev.pos) {
self.selecting = true;
self.before_press = Some((self.caret, self.anchor));
return out;
}
self.press = Some((ev.pos, self.snap.press(cx, ev.pos, self.editing.as_slice(), None)));
@@ -71,6 +87,7 @@ impl Tool for TypeTool {
vec![]
}
PointerKind::Up => {
self.before_press = None;
if self.selecting {
self.selecting = false;
return vec![];