From c78ec47f4bcc2d1a49086bcbc8a6d0d8bdce8ee3 Mon Sep 17 00:00:00 2001 From: zrsix1 <139825666+zrsix1@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:31:56 -0300 Subject: [PATCH] Type: a click inside the paragraph box off the glyphs moves the caret (#7606) While editing paragraph text, a click inside the box that missed the glyphs (left of a line's start, or below the last line) committed the edit. Like Photoshop, it now places the caret at the nearest position; a click outside the box still commits. Co-authored-by: Claude Opus 5.5 --- crates/ui-egui/src/type_tool.rs | 13 ++++++++++++- crates/ui-egui/src/type_tool_tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index d07b8bb161..ef021438f3 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -237,6 +237,15 @@ fn resize_box(app: &mut PhotocraftApp, ed: &TextEdit, i: u8, x: f64, y: f64) { let _ = app.run("type.edit", json!({"layer": ed.layer, "box": [o.x, o.y, x1 - x0, y1 - y0], "coalesce": ed.session})); } +/// True when document point (`x`, `y`) lies inside the paragraph box of type layer `id` (false +/// for point text). +fn inside_box(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64) -> bool { + let Some((bx, by, w, h)) = box_shape(app, id) else { return false }; + let Some((_, aff, _)) = layout(app, id) else { return false }; + let (tx, ty) = to_text(&aff, x, y); + tx >= bx && tx <= bx + w && ty >= by && ty <= by + h +} + /// Pointer down with the Type tool. Returns true when the press was consumed (no box drag). pub fn pointer_down(app: &mut PhotocraftApp, x: f64, y: f64, mods: egui::Modifiers) -> bool { if let Some(ed) = app.ui.text_edit.clone() { @@ -247,7 +256,9 @@ pub fn pointer_down(app: &mut PhotocraftApp, x: f64, y: f64, mods: egui::Modifie } return true; } - if hit_layer(app, x, y) == Some(id) { + // Photoshop: anywhere inside the paragraph box places the caret, even off the glyphs + // (left of a line's start, below the last line); only a click outside it commits. + if hit_layer(app, x, y) == Some(id) || inside_box(app, id, x, y) { let off = hit_offset(app, id, x, y); if let Some(e) = app.ui.text_edit.as_mut() { e.caret = off; diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index 20e59a01d6..9b840236f0 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -964,6 +964,33 @@ fn temporary_type_transform_free_scale_center_scale_move_skew_and_rotation() { } } +/// Editing paragraph text, a click inside the box but off the glyphs moves the caret to the +/// nearest offset, as in Photoshop; a click outside the box still commits. +#[test] +fn click_inside_the_paragraph_box_off_the_glyphs_moves_the_caret() { + use crate::canvas::{ToolEvent, tool_event}; + // The box is 240 × 180; the two 32 pt lines only fill its top. + let (mut app, id) = transform_app(true, false, Affine::translate(400.0, 350.0)); + let (bx, by, _, h) = super::box_shape(&app, id).unwrap(); + let (l, aff, _) = layout(&mut app, id).unwrap(); + let click = |app: &mut PhotocraftApp, tx: f32, ty: f32| { + let p = aff.apply(Point::new(f64::from(tx), f64::from(ty))); + tool_event(app, ToolEvent::Down { x: p.x, y: p.y, pressure: 1.0 }, Modifiers::NONE); + tool_event(app, ToolEvent::Up { x: p.x, y: p.y }, Modifiers::NONE); + }; + let layers = app.session.active().unwrap().doc.layers.len(); + // Near the bottom of the box, just right of its left edge. + assert!(!photocraft_text::text_point_inside(&l, bx + 8.0, by + h - 8.0, 6.0), "the point is off the glyphs"); + click(&mut app, bx + 8.0, by + h - 8.0); + let ed = app.ui.text_edit.clone().expect("still editing"); + // "Hello é世界\n" is 10 characters: the start of the second line. + assert_eq!((ed.layer, ed.caret, ed.anchor), (id.0, 10, 10)); + // Outside the box the edit commits, without starting new text. + click(&mut app, bx - 40.0, by + h - 8.0); + assert!(app.ui.text_edit.is_none()); + assert_eq!(app.session.active().unwrap().doc.layers.len(), layers); +} + #[test] fn temporary_type_transform_noop_cancel_and_stale_capture() { use crate::canvas::{ToolEvent, tool_event};