Shape tools: Shift and Alt pressed during the drag constrain it, as in Photoshop (#668) (#984)

Shape drags only read the modifiers held at the press, so pressing Shift
mid-drag (the usual way) didn't square a rectangle or keep a polygon's
proportions, and Alt mid-drag didn't draw from the centre. They now follow
the modifiers held at each pointer event, like the marquees.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-08 03:17:44 -04:00 committed by GitHub
parent 74c7980146
commit 1ae11a4bb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 2 deletions

View File

@ -2293,7 +2293,7 @@ fn draw_drag_preview(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &View
trail.feed(&d.points, app.session.tools.brush.size);
trail.draw(painter, doc_rect, xf.flip, col);
}
t if crate::vector_ui::is_shape_tool(t) => crate::vector_ui::draw_shape_preview(app, painter, xf, t, d.start, last, d.modifiers),
t if crate::vector_ui::is_shape_tool(t) => crate::vector_ui::draw_shape_preview(app, painter, xf, t, d.start, last, d.live),
Tool::RectMarquee | Tool::EllipseMarquee | Tool::ObjectSelection => {
// Marching ants, visible on any pixels (#172).
let (a, b) = marquee.unwrap_or((d.start, last));
@ -2714,7 +2714,7 @@ pub(crate) fn finish_gesture(app: &mut PhotocraftApp, d: Drag) {
}
match d.tool {
Tool::ObjectSelection => crate::retouch_ui::finish_object_selection(app, d.start, [end[0], end[1]], d.modifiers),
t if crate::vector_ui::is_shape_tool(t) => crate::vector_ui::finish_shape(app, t, d.start, [end[0], end[1]], d.modifiers),
t if crate::vector_ui::is_shape_tool(t) => crate::vector_ui::finish_shape(app, t, d.start, [end[0], end[1]], d.live),
Tool::PathSelection => crate::vector_ui::path_selection_finish(app, d.start, [end[0], end[1]]),
Tool::Type | Tool::VerticalType => crate::type_tool::pointer_up(app, d.start, [end[0], end[1]]),
Tool::Brush | Tool::Pencil | Tool::Eraser => {

View File

@ -938,6 +938,30 @@ mod tests {
assert_eq!(app.session.active().unwrap().doc.layers.len(), 4);
}
#[test]
fn shift_pressed_during_a_shape_drag_constrains_it() {
// #668: ⇧ only counted when held at the press; Photoshop constrains when it is pressed
// mid-drag (and stops when it is released).
use crate::canvas::{ToolEvent, tool_event};
for (tool, sides) in [(Tool::Polygon, 6), (Tool::Rectangle, 4), (Tool::EllipseShape, 0)] {
let mut app = app();
app.ui.tool = tool;
app.ui.tool_options.polygon_sides = 6;
let none = egui::Modifiers::NONE;
tool_event(&mut app, ToolEvent::Down { x: 10.0, y: 10.0, pressure: 1.0 }, none);
tool_event(&mut app, ToolEvent::Move { x: 40.0, y: 30.0, pressure: 1.0 }, none);
tool_event(&mut app, ToolEvent::Move { x: 70.0, y: 40.0, pressure: 1.0 }, egui::Modifiers::SHIFT);
tool_event(&mut app, ToolEvent::Up { x: 70.0, y: 40.0 }, egui::Modifiers::SHIFT);
let st = app.session.active().unwrap();
let l = st.doc.layer(st.active_layer.unwrap()).unwrap();
assert!(matches!(l.content, LayerContent::Shape(_)), "{tool:?} ({sides} sides): a shape layer");
let b = l.surface().unwrap().content_bounds();
// The drag is 60 × 30; constrained, the shape's box is 60 × 60 (a hexagon is narrower
// than its box, so the height tells).
assert!(b.height().abs_diff(60) <= 1 && b.width() <= 61, "{tool:?}: ⇧ mid-drag keeps proportions: {b:?}");
}
}
#[test]
fn shape_preview_is_the_committed_path() {
let mut app = app();