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 <noreply@anthropic.com>
This commit is contained in:
zrsix1
2026-10-19 13:31:56 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent fe64e5a2bd
commit c78ec47f4b
2 changed files with 39 additions and 1 deletions
+12 -1
View File
@@ -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;
+27
View File
@@ -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};