mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-14 14:12:44 +00:00
snap_ui::begin chose the snap gesture from app.ui.tool, the selected tool, not the tool the press acts as. A ⌘-drag with a painting tool (temporary Move) or with a selection tool outside the selection (moves the layer) got no moving box and no smart-guide/guide/grid snapping. The ⌘ resolution in canvas::tool_event moves into canvas::event_tool, which begin now uses together with canvas::command_moves_layer. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
2 files changed
+40
-16
No files matched your search
@@ -3812,14 +3812,7 @@ pub fn tool_event(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers)
|
||||
if crate::magnetic_lasso_ui::pointer(app, ev, mods) {
|
||||
return;
|
||||
}
|
||||
// ⌘ held is the Move tool (`hold_keys::cmd_moves`). The canvas resolves the held key before
|
||||
// the event (`tool_override`); automation and tests send the modifier with the event.
|
||||
let tool = match app.active_tool() {
|
||||
t if app.tool_override.is_none() && mods.command && crate::hold_keys::cmd_moves(t) && app.ui.transform.is_none() && app.ui.text_edit.is_none() => {
|
||||
Tool::Move
|
||||
}
|
||||
t => t,
|
||||
};
|
||||
let tool = event_tool(app, mods);
|
||||
if tool == Tool::Eyedropper {
|
||||
match ev {
|
||||
ToolEvent::Down { x, y, .. } | ToolEvent::Move { x, y, .. } => {
|
||||
@@ -3864,7 +3857,7 @@ pub fn tool_event(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers)
|
||||
let _ = app.run("layer.pickAt", json!({"x": x, "y": y, "target": target, "mode": mode}));
|
||||
// Snapping (and the drag's box) started before the pick: point it at what moves.
|
||||
if app.session.active().map(|st| st.selected_layers()) != before {
|
||||
crate::snap_ui::retarget_move(app, [x, y]);
|
||||
crate::snap_ui::retarget_move(app, [x, y], mods);
|
||||
}
|
||||
}
|
||||
// A locked layer: no drag, and Photoshop's message once the pointer moves (`move_lock`).
|
||||
@@ -4119,6 +4112,18 @@ pub fn selection_drag_kind(app: &PhotocraftApp, tool: Tool, p: [f64; 2], mods: e
|
||||
(!clicky && !mods.command && selection_mode(app, mods) == "replace").then_some(false)
|
||||
}
|
||||
|
||||
/// The tool a pointer event with `mods` goes to: ⌘ held is the Move tool (`hold_keys::cmd_moves`).
|
||||
/// The canvas resolves the held key before the event (`tool_override`); automation and tests send
|
||||
/// the modifier with the event.
|
||||
pub(crate) fn event_tool(app: &PhotocraftApp, mods: egui::Modifiers) -> Tool {
|
||||
match app.active_tool() {
|
||||
t if app.tool_override.is_none() && mods.command && crate::hold_keys::cmd_moves(t) && app.ui.transform.is_none() && app.ui.text_edit.is_none() => {
|
||||
Tool::Move
|
||||
}
|
||||
t => t,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a ⌘ (⌘⌥) press with selection tool `tool` at `p` move the whole layer (a duplicate with
|
||||
/// ⌥), as the Move tool would? Outside the selection or without one; inside it, ⌘ drags the
|
||||
/// selected pixels instead (`selection_drag_kind`).
|
||||
|
||||
@@ -143,18 +143,21 @@ fn override_held(mods: egui::Modifiers) -> bool {
|
||||
/// The Move tool's press at `p` picked other layers (Auto-Select): the drag moves those, so its
|
||||
/// box, what it snaps and what it leaves out of the targets are theirs, not the ones selected
|
||||
/// before (a group or an empty layer selected before had no box at all).
|
||||
pub(crate) fn retarget_move(app: &mut PhotocraftApp, p: [f64; 2]) {
|
||||
begin(app, p);
|
||||
pub(crate) fn retarget_move(app: &mut PhotocraftApp, p: [f64; 2], mods: egui::Modifiers) {
|
||||
begin(app, p, mods);
|
||||
}
|
||||
|
||||
/// Start snapping for a drag beginning at `p` (called on pointer down).
|
||||
fn begin(app: &mut PhotocraftApp, p: [f64; 2]) {
|
||||
/// Start snapping for a drag beginning at `p` with `mods` held (called on pointer down).
|
||||
fn begin(app: &mut PhotocraftApp, p: [f64; 2], mods: egui::Modifiers) {
|
||||
app.prefs_rt.snap = None;
|
||||
app.prefs_rt.snap_lines.clear();
|
||||
if app.session.active().is_none() {
|
||||
return;
|
||||
}
|
||||
let tool = app.ui.tool;
|
||||
// The tool the press acts as: ⌘ makes a painting tool the Move tool, and a selection tool
|
||||
// outside the selection moves the layer as the Move tool does (#2681).
|
||||
let tool = crate::canvas::event_tool(app, mods);
|
||||
let moves_layer = crate::canvas::command_moves_layer(app, tool, p, mods);
|
||||
let tol = tolerance(app);
|
||||
let gesture = if let Some(t) = &app.ui.transform {
|
||||
if t.mode == crate::state::TransformMode::Distort {
|
||||
@@ -170,7 +173,7 @@ fn begin(app: &mut PhotocraftApp, p: [f64; 2]) {
|
||||
}
|
||||
} else if tool == Tool::Move && crate::rulers::guide_at(app, p[0], p[1]).is_some() {
|
||||
Some((Gesture::Guide, Vec::new()))
|
||||
} else if tool == Tool::Move {
|
||||
} else if tool == Tool::Move || moves_layer {
|
||||
let exclude = app.session.active().map(|s| s.selected_layers()).unwrap_or_default();
|
||||
moving_rect(app).map(|rect| (Gesture::Move { rect }, exclude))
|
||||
} else if tool == Tool::Crop
|
||||
@@ -242,7 +245,7 @@ fn apply(app: &mut PhotocraftApp, p: [f64; 2]) -> [f64; 2] {
|
||||
pub fn filter_event(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers) -> ToolEvent {
|
||||
match ev {
|
||||
ToolEvent::Down { x, y, pressure } => {
|
||||
begin(app, [x, y]);
|
||||
begin(app, [x, y], mods);
|
||||
// Moves snap their delta, which is zero at the start: keep the press point.
|
||||
let p = match app.prefs_rt.snap.as_ref().map(|s| &s.gesture) {
|
||||
Some(Gesture::Point) => {
|
||||
@@ -409,6 +412,22 @@ mod tests {
|
||||
assert!(app.prefs_rt.snap.is_none());
|
||||
}
|
||||
|
||||
/// ⌘ turns a painting tool, or a marquee outside the selection, into the Move tool for the
|
||||
/// drag: it gets the Move tool's box and smart-guide snapping, not the selected tool's.
|
||||
#[test]
|
||||
fn command_drag_temporary_move_snaps_like_the_move_tool() {
|
||||
for tool in [Tool::Brush, Tool::RectMarquee] {
|
||||
let mut app = app_with_box();
|
||||
app.ui.tool = tool;
|
||||
let cmd = egui::Modifiers { mac_cmd: true, command: true, ..Default::default() };
|
||||
crate::canvas::tool_event(&mut app, ToolEvent::Down { x: 60.0, y: 60.0, pressure: 1.0 }, cmd);
|
||||
let gesture = app.prefs_rt.snap.as_ref().map(|s| s.gesture.clone());
|
||||
assert_eq!(gesture, Some(Gesture::Move { rect: [40.0, 40.0, 90.0, 80.0] }), "{tool:?}: the mover's box");
|
||||
crate::canvas::tool_event(&mut app, ToolEvent::Up { x: 323.0, y: 100.0 }, cmd);
|
||||
assert_eq!(mover_bounds(&app).x0, 300, "{tool:?}: snapped to the target's edge");
|
||||
}
|
||||
}
|
||||
|
||||
/// The drag's box and snapping belong to the layer Auto-Select picks on the press, not to the
|
||||
/// one selected before: another layer (its box would move instead), or an empty layer (no box,
|
||||
/// no snapping at all).
|
||||
|
||||
Reference in new issue
Block a user