Make Selection: Rendering and Operation groups; the dialog's step is Selection Change (#12917)

The Pen's Make Selection dialog (canvas menu, options bar) now has
Photoshop's layout: Rendering with Feather Radius in pixels and
Anti-aliased, then Operation with New / Add to / Subtract from /
Intersect with Selection as radio buttons. The last three are greyed out
without a selection. The fields and params are the same as before.

OK with New Selection names the step it added "Selection Change".
Cmd/Ctrl+Enter, the Paths panel and the combining operations keep
"Make Selection" (not measured).
This commit is contained in:
rmsff committed 2026-11-11 02:08:55 +03:00
1 parent de9add28b2
commit 8824661f47
2 files changed
+65

No files matched your search

@@ -1261,6 +1261,7 @@ fn pen_dialog(app: &mut PhotocraftApp, command: &str, label: &str, name: &str) {
if command == "path.toSelection" {
// OK deselects and hides the path, as ⌘↩ does (#6079).
fields.insert("__deselectPath".into(), json!(true));
fields.insert(MAKE_SELECTION.into(), json!(true));
}
if command == "path.fill" {
let fg = app.session.tools.foreground;
@@ -1281,6 +1282,63 @@ fn pen_dialog(app: &mut PhotocraftApp, command: &str, label: &str, name: &str) {
spec_dialog(app, command, label, spec, fields);
}
/// Marks the Make Selection dialog [`pen_dialog`] opens. It keeps the schema dialog's fields, so OK
/// and Alt+Reset work as before; only the body is Photoshop's layout (#12917).
const MAKE_SELECTION: &str = "__makeSelection";
/// The Make Selection operations, in Photoshop's order: (`path.toSelection` mode, label).
const MAKE_SELECTION_OPERATIONS: [(&str, &str); 4] =
[("replace", "New Selection"), ("add", "Add to Selection"), ("subtract", "Subtract from Selection"), ("intersect", "Intersect with Selection")];
/// Is this dialog the Make Selection dialog?
pub(crate) fn owns_make_selection(fields: &serde_json::Map<String, Value>) -> bool {
fields.contains_key(MAKE_SELECTION)
}
/// Photoshop's Make Selection dialog: Rendering (Feather Radius, Anti-aliased) and Operation.
/// Without a selection there is nothing to combine with, so only New Selection is available.
pub(crate) fn make_selection_body(app: &PhotocraftApp, ui: &mut egui::Ui, f: &mut serde_json::Map<String, Value>) {
let has_selection = app.session.active().is_some_and(|st| st.doc.selection.is_some());
ui.spacing_mut().item_spacing.y = 6.0;
crate::widgets::section_label(ui, tl!("Rendering"));
crate::widgets::hairline(ui);
ui.horizontal(|ui| {
ui.label(tl!("Feather Radius:"));
let mut feather = f.get("feather").and_then(Value::as_f64).unwrap_or(0.0) as f32;
crate::widgets::value_field(ui, &mut feather, 0.0..=250.0, "", 64.0).accessible_name(tl!("Feather Radius:"));
ui.label(tl!("pixels"));
// The schema dialog's rounding, so OK sends what it sent.
f.insert("feather".into(), json!((feather * 10.0).round() / 10.0));
});
let mut anti_alias = f.get("antiAlias").and_then(Value::as_bool).unwrap_or(true);
crate::widgets::checkbox(ui, &mut anti_alias, tl!("Anti-aliased"));
f.insert("antiAlias".into(), json!(anti_alias));
ui.add_space(4.0);
crate::widgets::section_label(ui, tl!("Operation"));
crate::widgets::hairline(ui);
let mut mode = f.get("mode").and_then(Value::as_str).unwrap_or("replace").to_string();
// A combining mode set from outside, or a selection gone while the dialog was open.
if !has_selection {
mode = "replace".into();
}
for (key, label) in MAKE_SELECTION_OPERATIONS {
ui.add_enabled_ui(key == "replace" || has_selection, |ui| ui.radio_value(&mut mode, key.to_string(), tl!(label)));
}
f.insert("mode".into(), json!(mode));
}
/// OK in the Make Selection dialog: a new selection is the History step Selection Change, as in
/// Photoshop (#12917). ⌘↩, the Paths panel and the combining operations keep the engine's Make
/// Selection (not measured). Only a step this run added is renamed: `revision` is from before it.
pub(crate) fn name_make_selection_step(app: &mut PhotocraftApp, fields: &serde_json::Map<String, Value>, params: &Value, revision: Option<u64>) {
if !owns_make_selection(fields) || params.get("mode").and_then(Value::as_str).is_some_and(|m| m != "replace") {
return;
}
if let Some(st) = app.session.active_mut().filter(|st| Some(st.revision) != revision) {
st.history.set_current_label("Selection Change");
}
}
/// The Pen options bar's Make Selection (Path mode, #5703): the canvas menu's Make Selection…
/// dialog for the path being drawn (committed first, as the menu does) or the active path.
pub(crate) fn make_selection(app: &mut PhotocraftApp) -> Result<(), String> {
+7
View File
@@ -357,6 +357,9 @@ fn command_body_more(app: &mut PhotocraftApp, ui: &mut egui::Ui, d: &Dialog, fie
_ if crate::smart_filter_dialog::owns(fields) => crate::smart_filter_dialog::body(app, ui, fields),
_ if crate::adjust_dialog::owns(fields) => crate::adjust_dialog::body(app, ui, fields),
_ if crate::blur_pins::owns(fields) => crate::blur_pins::body(ui, fields),
_ if crate::canvas_tool_menu::owns_make_selection(fields) => {
first_field_focused(ui, id, |ui| crate::canvas_tool_menu::make_selection_body(app, ui, fields))
}
_ if fields.contains_key("__filter") => filter_body(app, ui, d, fields, id),
_ if crate::new_guide_ui::owns(fields) => crate::new_guide_ui::body(ui, fields),
_ if crate::equalize_ui::owns(fields) => crate::equalize_ui::body(ui, fields),
@@ -1008,6 +1011,7 @@ fn confirm_generic_command(app: &mut PhotocraftApp, d: Dialog) -> Result<Value,
return Err(e);
}
};
let revision = app.session.active().map(|st| st.revision);
let result = crate::actions::run_step_dialog(app, &d.fields, &cmd, params.clone());
if let Ok(r) = &result {
report_file_errors(app, &d, r);
@@ -1020,6 +1024,9 @@ fn confirm_generic_command(app: &mut PhotocraftApp, d: Dialog) -> Result<Value,
Some(p) if result.is_ok() && running => p.committing = true,
_ => app.filter_preview = None,
}
if result.is_ok() && cmd == "path.toSelection" {
crate::canvas_tool_menu::name_make_selection_step(app, &d.fields, &params, revision);
}
if result.is_ok()
&& cmd == "path.toSelection"
&& d.fields.contains_key("__deselectPath")