mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
This commit is contained in:
@@ -269,6 +269,9 @@ pub struct Drag {
|
||||
/// `Some(false)` moves the outline, `Some(true)` moves the floating piece (`select.float`), as
|
||||
/// every Move-tool drag with a selection does (`move_ui::moves_selected_pixels_with`).
|
||||
pub sel_move: Option<bool>,
|
||||
/// The last ⇧-locked offset of a selection-tool drag of the selection (`move_mods`, #6231);
|
||||
/// keeps the locked direction stable between events. The Move tool's own drags use `move_mods`.
|
||||
pub sel_axis: Option<[f64; 2]>,
|
||||
pub lasso: Option<crate::lasso_ui::Lasso>,
|
||||
}
|
||||
|
||||
@@ -286,6 +289,7 @@ impl Drag {
|
||||
released: egui::Modifiers::NONE,
|
||||
reposition: false,
|
||||
sel_move: None,
|
||||
sel_axis: None,
|
||||
lasso: None,
|
||||
}
|
||||
}
|
||||
@@ -5795,6 +5799,9 @@ fn tool_move(app: &mut PhotocraftApp, x: f64, y: f64, pressure: f32, mods: egui:
|
||||
// ⇧: straight 0/45/90° strokes, 45° gradient angles (stroke_constraint.rs).
|
||||
let last = d.points.last().map_or(d.start, |p| [p[0], p[1]]);
|
||||
let [x, y] = crate::stroke_constraint::constrain(d.tool, &mut d.constrain, d.start, last, [x, y], mods.shift, zoom);
|
||||
// ⇧: a selection-tool drag of the selection locks to multiples of 45° (#6231). The Move
|
||||
// tool's own drags were already locked by `move_mods::filter_event`.
|
||||
let [x, y] = crate::move_mods::selection_drag_to(d, [x, y], mods.shift && tool != Tool::Move);
|
||||
let moved = d.points.last().is_none_or(|p| (p[0] - x).abs() + (p[1] - y).abs() > 0.25);
|
||||
// A held pen repeats its point now and then, so the stroke's time keeps advancing.
|
||||
let held = app.stylus.last_point_ms().is_some_and(|t| app.stylus.clock_ms - t >= HOLD_REPEAT_MS)
|
||||
@@ -6226,6 +6233,7 @@ pub fn tool_event(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers)
|
||||
}
|
||||
let last = d.points.last().map_or(d.start, |p| [p[0], p[1]]);
|
||||
let [x, y] = crate::stroke_constraint::constrain(d.tool, &mut d.constrain, d.start, last, [x, y], mods.shift, zoom);
|
||||
let [x, y] = crate::move_mods::selection_drag_to(&mut d, [x, y], mods.shift && app.active_tool() != Tool::Move);
|
||||
// A Move-tool click (released where it was pressed) selects, it never moves: snapping
|
||||
// the release point would otherwise nudge the layer onto a nearby edge.
|
||||
if d.tool == Tool::Move && d.points.len() < 2 && matches!(raw, ToolEvent::Up { x, y } if [x, y] == d.start) {
|
||||
|
||||
@@ -655,6 +655,44 @@ fn drag_inside_the_selection_moves_the_outline() {
|
||||
assert_eq!(sel(&app), None);
|
||||
}
|
||||
|
||||
/// #6231: ⇧ held during a selection-tool drag of the selection locks the move to multiples of
|
||||
/// 45° (⇧ at the press still means add-to-selection, so the lock only applies mid-drag).
|
||||
#[test]
|
||||
fn shift_constrains_a_selection_drag_to_an_axis() {
|
||||
use crate::canvas::{ToolEvent, tool_event};
|
||||
let (mut app, _layer) = painted();
|
||||
let sel = |app: &PhotocraftApp| app.session.active().unwrap().doc.selection.as_ref().map(|s| s.content_bounds());
|
||||
let drag_shift = |app: &mut PhotocraftApp, from: [f64; 2], to: [f64; 2]| {
|
||||
tool_event(app, ToolEvent::Down { x: from[0], y: from[1], pressure: 1.0 }, Modifiers::NONE);
|
||||
tool_event(app, ToolEvent::Move { x: to[0], y: to[1], pressure: 1.0 }, Modifiers::SHIFT);
|
||||
tool_event(app, ToolEvent::Up { x: to[0], y: to[1] }, Modifiers::SHIFT);
|
||||
};
|
||||
drag_shift(&mut app, [20.0, 20.0], [40.0, 26.0]);
|
||||
assert_eq!(sel(&app), Some(Rect::new(30, 10, 50, 30)), "near-horizontal drag moves x only");
|
||||
drag_shift(&mut app, [40.0, 20.0], [44.0, 36.0]);
|
||||
assert_eq!(sel(&app), Some(Rect::new(30, 26, 50, 46)), "near-vertical drag moves y only");
|
||||
// Without ⇧ the drag is free (the outline already sits at (30,26,50,46)).
|
||||
drag(&mut app, [40.0, 30.0], [50.0, 20.0], Modifiers::NONE);
|
||||
assert_eq!(sel(&app), Some(Rect::new(40, 16, 60, 36)), "plain drag moves both axes");
|
||||
// A near-diagonal ⇧-drag locks to 45°, as in Photoshop: (13,11) projects onto (12,12).
|
||||
drag_shift(&mut app, [50.0, 26.0], [63.0, 37.0]);
|
||||
assert_eq!(sel(&app), Some(Rect::new(52, 28, 72, 48)), "diagonal drag locks to 45°");
|
||||
}
|
||||
|
||||
/// #6231: ⇧ also locks a ⌘-drag of the selected pixels to multiples of 45°.
|
||||
#[test]
|
||||
fn shift_constrains_a_floating_selection_drag() {
|
||||
use crate::canvas::{ToolEvent, tool_event};
|
||||
let (mut app, _layer) = painted();
|
||||
let offset = |app: &PhotocraftApp| photocraft_engine::float_cmds::floating(app.session.active().unwrap()).map(|f| f.offset);
|
||||
let cmd_shift = Modifiers::COMMAND | Modifiers::SHIFT;
|
||||
tool_event(&mut app, ToolEvent::Down { x: 20.0, y: 20.0, pressure: 1.0 }, Modifiers::COMMAND);
|
||||
tool_event(&mut app, ToolEvent::Move { x: 34.0, y: 22.0, pressure: 1.0 }, cmd_shift);
|
||||
tool_event(&mut app, ToolEvent::Up { x: 34.0, y: 22.0 }, cmd_shift);
|
||||
assert_eq!(offset(&app), Some((14, 0)), "locked to the horizontal axis");
|
||||
app.run("select.drop", json!({})).unwrap();
|
||||
}
|
||||
|
||||
/// ⌘-drag cuts the selected pixels into a floating piece (`select.float`): shown at the pointer
|
||||
/// while dragging, moved again by plain drags, put back by Undo, dropped by Deselect.
|
||||
#[test]
|
||||
|
||||
@@ -152,6 +152,23 @@ fn drag_to(app: &mut PhotocraftApp, p: [f64; 2], mods: egui::Modifiers) -> [f64;
|
||||
[start[0] + d[0], start[1] + d[1]]
|
||||
}
|
||||
|
||||
/// ⇧ while a selection tool drags the selection (#6231): lock the move to multiples of 45°, as
|
||||
/// Photoshop does ("Move a selection border") and as the Move tool's drags of selected pixels do
|
||||
/// ([`TRANSFORM_DIRECTIONS`]). The caller skips this when [`filter_event`] has already
|
||||
/// rewritten the event (the Move tool itself); it covers the `sel_move` drags the marquee and
|
||||
/// lasso tools start, including ⌘-drags of the selected pixels. The locked offset is kept on the
|
||||
/// drag between events so the direction doesn't flicker between two neighbours.
|
||||
pub(crate) fn selection_drag_to(d: &mut crate::canvas::Drag, p: [f64; 2], shift: bool) -> [f64; 2] {
|
||||
if d.sel_move.is_none() || !shift {
|
||||
d.sel_axis = None;
|
||||
return p;
|
||||
}
|
||||
let delta = [p[0] - d.start[0], p[1] - d.start[1]];
|
||||
let locked = constrain(delta, d.sel_axis, TRANSFORM_DIRECTIONS);
|
||||
d.sel_axis = Some(locked);
|
||||
[d.start[0] + locked[0], d.start[1] + locked[1]]
|
||||
}
|
||||
|
||||
/// The Move drag in progress is an ⌥-drag that has duplicated the layers (it now moves the copies).
|
||||
pub(crate) fn duplicated(app: &PhotocraftApp) -> bool {
|
||||
moving(app) && app.move_mods.dup_from.is_some()
|
||||
|
||||
Reference in New Issue
Block a user