Selection tools: ⌥ with nothing selected draws a new selection

With #1106's engine change, subtract or intersect with no selection
selects nothing. The UI turned ⌥ (and ⇧⌥) into subtract (intersect)
even with nothing selected, so ⌥-dragging a marquee or lasso on a clean
canvas would select nothing; in Photoshop ⌥ then only draws the marquee
from its centre. A modifier-made subtract or intersect is now a new
selection when nothing is selected; an options-bar Subtract or Intersect
is kept, and the cursor badge follows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
This commit is contained in:
Claude committed 2026-10-08 20:44:25 +00:00
1 parent 6197270230
commit d38ecebd13
3 files changed
+36 -1

No files matched your search

+1 -1
View File
@@ -2928,7 +2928,7 @@ pub fn extra_windows(app: &mut PhotocraftApp, ctx: &egui::Context) {
/// Selection mode from the options bar, overridden by modifier keys (⇧ add, ⌥ subtract, ⇧⌥ intersect).
/// The cursor badge announces the same mode (`tool_feedback`).
pub(crate) fn selection_mode(app: &PhotocraftApp, m: egui::Modifiers) -> &'static str {
crate::tool_feedback::selection_mode(Tool::Lasso, app.ui.selection_mode, m)
crate::tool_feedback::document_selection_mode(app, Tool::Lasso, m)
}
/// A polygonal lasso click adds a vertex; clicking near the first vertex closes the polygon. The
+22
View File
@@ -303,3 +303,25 @@ fn cmd_alt_drag_floats_a_copy() {
assert!(alpha(&d, 12, 20) == 1.0, "the original stays");
assert!(alpha(&d, 50, 20) == 1.0, "the copy dropped 25 px right");
}
#[test]
fn alt_with_nothing_selected_draws_a_new_selection() {
// #1106: with no selection, ⌥ has nothing to subtract from, so a marquee drawn with it held
// still selects (Photoshop: ⌥ then only draws from the centre). With a selection it subtracts.
let mut h = harness(Tool::RectMarquee);
mods(&mut h, Modifiers::ALT);
press_at(&mut h, 100.0, 100.0, Modifiers::ALT);
move_to(&mut h, 140.0, 130.0);
release_at(&mut h, 140.0, 130.0, Modifiers::ALT);
mods(&mut h, Modifiers::NONE);
assert!(h.state().session.active().unwrap().doc.selection.as_ref().is_some_and(|s| !s.content_bounds().is_empty()), "a selection was made");
// Now ⌥ subtracts from it.
let before = selection(&h);
mods(&mut h, Modifiers::ALT);
press_at(&mut h, 0.0, 0.0, Modifiers::ALT);
move_to(&mut h, 400.0, 400.0);
release_at(&mut h, 400.0, 400.0, Modifiers::ALT);
mods(&mut h, Modifiers::NONE);
let after = h.state().session.active().unwrap().doc.selection.as_ref().map(|s| s.content_bounds());
assert!(after.is_none_or(|r| r.is_empty() || r != before), "⌥ subtracted: {before:?} -> {after:?}");
}
+13
View File
@@ -82,6 +82,19 @@ pub fn selection_mode(tool: Tool, bar: u8, m: egui::Modifiers) -> &'static str {
}
}
/// [`selection_mode`] for the active document: with nothing selected, ⌥ (or ⇧⌥) has nothing to
/// subtract from or intersect with, so it draws a new selection, as in Photoshop (where ⌥ then
/// only draws a marquee from its centre). An options-bar Subtract or Intersect is kept (#1106).
pub fn document_selection_mode(app: &PhotocraftApp, tool: Tool, m: egui::Modifiers) -> &'static str {
let mode = selection_mode(tool, app.ui.selection_mode, m);
let selected = app.session.active().and_then(|st| st.doc.selection.as_ref()).is_some_and(|s| !s.content_bounds().is_empty());
if m.alt && !selected && matches!(mode, "subtract" | "intersect") && selection_mode(tool, app.ui.selection_mode, egui::Modifiers::NONE) == "replace" {
"replace"
} else {
mode
}
}
/// The badge the cursor of `tool` shows with modifiers `m` (None for non-selection tools or New).
pub fn badge(app: &PhotocraftApp, tool: Tool, m: egui::Modifiers) -> Option<Badge> {
if !is_selection_tool(tool) {