From fd28da56dae573df141edc25059e62788eef8304 Mon Sep 17 00:00:00 2001 From: safvanedge Date: Thu, 15 Oct 2026 08:16:46 +0530 Subject: [PATCH] Type: keep text selection highlight visible while Command/Ctrl is held (#5542) * Type: keep text selection highlight visible while Command/Ctrl is held * Type: match Photoshop 1px dark frame and handle styling for type transform * Merge main; keep the transform box and underline colours from the theme tokens The selection-highlight fix stands on its own; the hard-coded box and underline colours and the removed reference point were unrelated to it. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: safvanedge <241307593+safvanedge@users.noreply.github.com> Co-authored-by: Brandon Thomas Co-authored-by: Claude Opus 5.5 --- crates/ui-egui/src/type_tool.rs | 20 ++++++++++---------- crates/ui-egui/src/type_tool_tests.rs | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index bee49293b5..5641127489 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -1014,20 +1014,20 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX } // Frame: paragraph text shows its box with handles; point text an underline per line. let mods = crate::workspace_ui::sticky_mods(app, painter.ctx().input(|i| i.modifiers)); - if crate::type_transform::draw(app, painter, xf, mods) { - return; - } + let has_transform = crate::type_transform::draw(app, painter, xf, mods); let shape = app.session.active().and_then(|s| text_layer(&s.doc, id).map(|t| t.shape.clone())); let frame = Stroke::new(1.0, t.accent); match shape { Some(photocraft_doc::text::TextShape::Box { x, y, width, height }) => { - let c = [scr_t(x, y), scr_t(x + width, y), scr_t(x + width, y + height), scr_t(x, y + height)]; - painter.add(egui::Shape::closed_line(c.to_vec(), frame)); - let mids = [c[0].lerp(c[1], 0.5), c[1].lerp(c[2], 0.5), c[2].lerp(c[3], 0.5), c[3].lerp(c[0], 0.5)]; - for p in c.iter().chain(mids.iter()) { - let r = egui::Rect::from_center_size(*p, egui::vec2(7.0, 7.0)); - painter.rect_filled(r, 0.0, Color32::WHITE); - painter.rect_stroke(r, 0.0, frame, egui::StrokeKind::Inside); + if !has_transform { + let c = [scr_t(x, y), scr_t(x + width, y), scr_t(x + width, y + height), scr_t(x, y + height)]; + painter.add(egui::Shape::closed_line(c.to_vec(), frame)); + let mids = [c[0].lerp(c[1], 0.5), c[1].lerp(c[2], 0.5), c[2].lerp(c[3], 0.5), c[3].lerp(c[0], 0.5)]; + for p in c.iter().chain(mids.iter()) { + let r = egui::Rect::from_center_size(*p, egui::vec2(7.0, 7.0)); + painter.rect_filled(r, 0.0, Color32::WHITE); + painter.rect_stroke(r, 0.0, frame, egui::StrokeKind::Inside); + } } } _ => { diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index 0f5be9e2fa..725d2890fb 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -779,6 +779,23 @@ fn temporary_type_transform_visibility_and_cursors() { assert!(crate::type_transform::visible(h.state(), sticky)); } +#[test] +fn type_selection_highlight_visible_with_command_held() { + let (app, _id) = transform_app(false, false, Affine::IDENTITY); + let mut h = harness(1.0, app); + if let Some(ed) = h.state_mut().ui.text_edit.as_mut() { + ed.anchor = 0; + ed.caret = 5; + } + hold(&mut h, type_command()); + h.run(); + let accent = crate::theme::Tokens::get(&h.ctx).accent; + let expected_fill = egui::Color32::from_rgba_unmultiplied(accent.r(), accent.g(), accent.b(), 110); + let has_selection_highlight = + h.output().shapes.iter().any(|clipped| if let egui::Shape::Path(path) = &clipped.shape { path.fill == expected_fill && path.closed } else { false }); + assert!(has_selection_highlight, "selection highlight must remain visible while Ctrl/Cmd is held"); +} + /// Real canvas input: layer-local scaling stays correct across orientation, zoom, HiDPI and flip. #[test] fn temporary_type_transform_scales_without_reflow_or_leaving_type() {