mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900) (#8969)
* Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900) * Image › Adjustments: ⌥ while choosing an item opens it at its last settings (#8900) * Tests: the ⌥-click test picks the Image menu's Adjustments row, not the panel tab (#8900) * Image › Adjustments: an item opened with ⌥ lands in Edit › Search's recents too (#8900) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Brandon Thomas
parent
3a870ca3ce
commit
2b83be6c81
@@ -279,6 +279,33 @@ pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
||||
Some(app.ui.open_dialog(crate::state::DialogKind::Command, fields))
|
||||
}
|
||||
|
||||
/// Opens the dialog for `command` at the settings last confirmed in it (Photoshop's ⌥ plus the
|
||||
/// adjustment's shortcut, ⌥⌘L / ⌥⌘M, #8900), or at the neutral ones when there are none or they
|
||||
/// don't fit the current colour mode.
|
||||
pub fn open_last(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
||||
let id = open(app, command)?;
|
||||
let kind = command.strip_prefix(PREFIX).unwrap_or_default();
|
||||
let mode = curve_mode(app, kind);
|
||||
let Some(last) = app.session.prefs().dialogs.get(command).cloned() else { return Some(id) };
|
||||
let Ok(adjustment) = photocraft_engine::adjust_params::from_params(kind, &last, None, mode) else { return Some(id) };
|
||||
if let (Some(dialog), Value::Object(values)) = (app.ui.dialog_mut(id), photocraft_engine::adjust_params::to_params(&adjustment)) {
|
||||
dialog.fields.retain(|k, _| k.starts_with("__"));
|
||||
dialog.fields.extend(values);
|
||||
}
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/// Remember an adjustment dialog's settings after a successful OK, for [`open_last`]. Editing a
|
||||
/// smart filter's settings is not a use of the dialog's own (as `filter_dialog::remember`).
|
||||
pub(crate) fn remember(app: &mut PhotocraftApp, fields: &Map<String, Value>) {
|
||||
if fields.contains_key("__smartFilter") {
|
||||
return;
|
||||
}
|
||||
let Some(command) = fields.get("__command").and_then(Value::as_str).filter(|c| has_dialog(c)) else { return };
|
||||
let values = crate::filter_dialog::params_of(fields);
|
||||
app.session.prefs.edit(|prefs| prefs.dialogs.insert(command.into(), values));
|
||||
}
|
||||
|
||||
/// The Curves dialog's single-channel target: the Channels panel's, plus the Layers panel's mask
|
||||
/// selection — both ways of selecting a mask must give the same dropdown (#5335).
|
||||
fn isolated(app: &PhotocraftApp) -> Option<photocraft_engine::channel_cmds::IsolatedCurveChannel> {
|
||||
|
||||
@@ -855,7 +855,11 @@ pub fn confirm(app: &mut PhotocraftApp, id: u64) -> Result<Value, String> {
|
||||
app.ui.view.guide_layout = params;
|
||||
}
|
||||
if result.is_ok() {
|
||||
crate::filter_dialog::remember(app, &cmd, &d.fields);
|
||||
if crate::adjust_dialog::owns(&d.fields) {
|
||||
crate::adjust_dialog::remember(app, &d.fields);
|
||||
} else {
|
||||
crate::filter_dialog::remember(app, &cmd, &d.fields);
|
||||
}
|
||||
// A step dialog of a playing action: the action continues after it.
|
||||
crate::actions::resume(app, &d.fields)?;
|
||||
} else {
|
||||
|
||||
@@ -1293,8 +1293,19 @@ pub fn menu_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) -> f32 {
|
||||
}
|
||||
if let Some(id) = clicked {
|
||||
let ctx = ui.ctx().clone();
|
||||
let id = alt_click(id, ctx.input(|i| i.modifiers.alt));
|
||||
if let Err(e) = invoke(app, &ctx, &id, json!({})) {
|
||||
let alt = ctx.input(|i| i.modifiers.alt);
|
||||
// ⌥ while choosing an Image › Adjustments item opens it at its last settings (#8900).
|
||||
let r = if alt && crate::adjust_dialog::has_dialog(&id) {
|
||||
let opened = crate::adjust_dialog::open_last(app, &id).map(|_| Value::Null).ok_or_else(|| "no document".to_string());
|
||||
// Edit › Search's recents, as `invoke` notes a plain click.
|
||||
if opened.is_ok() {
|
||||
crate::palette::note_recent(&ctx, &id);
|
||||
}
|
||||
opened
|
||||
} else {
|
||||
invoke(app, &ctx, &alt_click(id, alt), json!({}))
|
||||
};
|
||||
if let Err(e) = r {
|
||||
app.ui.status = e;
|
||||
app.ui.status_error = true;
|
||||
}
|
||||
|
||||
@@ -239,7 +239,15 @@ pub fn dispatch_pressed(app: &mut PhotocraftApp, ctx: &egui::Context, focus: Foc
|
||||
let by_position = physical_key
|
||||
.filter(|p| POSITIONAL.contains(p))
|
||||
.and_then(|p| table.iter().find(|(_, sc)| sc.modifiers.command && sc.logical_key == p && key_matches(sc, p, *modifiers)));
|
||||
by_position.or_else(|| table.iter().find(|(_, sc)| key_matches(sc, *key, *modifiers))).map(|(id, _)| (id.clone(), *key == Key::Tab))
|
||||
let bound =
|
||||
by_position.or_else(|| table.iter().find(|(_, sc)| key_matches(sc, *key, *modifiers))).map(|(id, _)| (id.clone(), *key == Key::Tab, false));
|
||||
// Photoshop: ⌥ added to an adjustment's own key opens it at its last settings (⌥⌘L,
|
||||
// ⌥⌘M, #8900), unless that key is bound to something else.
|
||||
bound.or_else(|| {
|
||||
let plain = egui::Modifiers { alt: false, ..*modifiers };
|
||||
let last = modifiers.alt.then(|| table.iter().find(|(id, sc)| crate::adjust_dialog::has_dialog(id) && key_matches(sc, *key, plain))).flatten();
|
||||
last.map(|(id, _)| (id.clone(), false, true))
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@@ -250,7 +258,7 @@ pub fn dispatch_pressed(app: &mut PhotocraftApp, ctx: &egui::Context, focus: Foc
|
||||
i.events.remove(at);
|
||||
Some(hit)
|
||||
});
|
||||
let Some((id, tab)) = next else { break };
|
||||
let Some((id, tab, last)) = next else { break };
|
||||
if tab {
|
||||
// egui already read this Tab as "focus the next widget" (Photoshop's Tab hides the
|
||||
// panels instead, #1313): the shortcut took it, so cancel that move.
|
||||
@@ -258,7 +266,7 @@ pub fn dispatch_pressed(app: &mut PhotocraftApp, ctx: &egui::Context, focus: Foc
|
||||
}
|
||||
let id = clip_key_command(app, delete_key_command(app, id));
|
||||
let owner = key_owner(app, ctx);
|
||||
dispatch(app, ctx, &id);
|
||||
dispatch_as(app, ctx, &id, last);
|
||||
ran = true;
|
||||
if key_owner(app, ctx) != owner {
|
||||
break;
|
||||
@@ -345,6 +353,11 @@ fn label(id: &str) -> String {
|
||||
|
||||
/// Run a shortcut's command like its menu item (dialogs included), or say why it can't run.
|
||||
pub fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, id: &str) {
|
||||
dispatch_as(app, ctx, id, false);
|
||||
}
|
||||
|
||||
/// [`dispatch`]; with `last`, an adjustment dialog opens at its last confirmed settings.
|
||||
fn dispatch_as(app: &mut PhotocraftApp, ctx: &egui::Context, id: &str, last: bool) {
|
||||
let action = id.strip_prefix(crate::actions::SHORTCUT_PREFIX);
|
||||
let command_id = if action.is_some() { "actions.play" } else { id };
|
||||
let outcome = if !crate::menus::is_enabled(app, command_id) {
|
||||
@@ -364,7 +377,11 @@ pub fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, id: &str) {
|
||||
if v.get("failed").is_some_and(|f| f.is_object()) { Err(app.ui.status.clone()) } else { Ok(v) }
|
||||
})
|
||||
} else if crate::adjust_dialog::has_dialog(id) {
|
||||
crate::adjust_dialog::open(app, id);
|
||||
if last {
|
||||
crate::adjust_dialog::open_last(app, id);
|
||||
} else {
|
||||
crate::adjust_dialog::open(app, id);
|
||||
}
|
||||
Ok(serde_json::Value::Null)
|
||||
} else {
|
||||
crate::menus::invoke(app, ctx, id, json!({}))
|
||||
|
||||
@@ -846,3 +846,117 @@ fn plain_comma_and_semicolon_bindings_follow_the_typed_character_on_azerty() {
|
||||
tap(&mut h, egui::Key::Comma, egui::Key::M);
|
||||
assert_eq!(logged(&h), ["tools.decreaseBrushHardness"]);
|
||||
}
|
||||
|
||||
/// #8900: ⌥ added to an adjustment's shortcut opens its dialog with the settings last confirmed
|
||||
/// there (Photoshop's ⌥⌘L for Levels, ⌥⌘M for Curves); the plain shortcut still opens at the
|
||||
/// defaults, Cancel remembers nothing, each adjustment remembers its own, and a key assigned to
|
||||
/// another command in Edit › Keyboard Shortcuts stays that command's.
|
||||
#[test]
|
||||
fn alt_adjustment_shortcut_reopens_the_last_confirmed_settings() {
|
||||
use photocraft_doc::ColorMode;
|
||||
use photocraft_engine::adjust_params::{default_for, from_params, to_params};
|
||||
let mut h = harness();
|
||||
put_focus(&mut h, Place::Canvas);
|
||||
let top = |h: &Harness<'_, PhotocraftApp>| h.state().ui.dialogs.last().map(|d| (d.id, d.fields.clone())).expect("a dialog");
|
||||
let set = |h: &mut Harness<'_, PhotocraftApp>, id: u64, values: serde_json::Value| {
|
||||
let d = h.state_mut().ui.dialog_mut(id).unwrap();
|
||||
d.fields.extend(values.as_object().unwrap().clone());
|
||||
h.run_steps(2);
|
||||
};
|
||||
let levels = to_params(&from_params("levels", &json!({"inBlack": 30, "gamma": 1.3}), None, ColorMode::Rgb).unwrap());
|
||||
let level_defaults = to_params(&default_for("levels", ColorMode::Rgb).unwrap());
|
||||
assert_ne!(levels["inBlack"], level_defaults["inBlack"]);
|
||||
|
||||
press(&mut h, "Cmd+L");
|
||||
let (id, _) = top(&h);
|
||||
set(&mut h, id, json!({"inBlack": 30, "gamma": 1.3}));
|
||||
crate::dialogs::confirm(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
|
||||
press(&mut h, "Cmd+L");
|
||||
let (id, fields) = top(&h);
|
||||
assert_eq!((&fields["inBlack"], &fields["gamma"]), (&level_defaults["inBlack"], &level_defaults["gamma"]), "⌘L opens at the defaults");
|
||||
crate::dialogs::cancel(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
|
||||
press(&mut h, "Cmd+Alt+L");
|
||||
assert_eq!(logged(&h), ["image.adjustments.levels", "image.adjustments.levels", "image.adjustments.levels"]);
|
||||
let (id, fields) = top(&h);
|
||||
assert_eq!((&fields["inBlack"], &fields["gamma"]), (&levels["inBlack"], &levels["gamma"]), "⌥⌘L opens at the last OK'd settings");
|
||||
set(&mut h, id, json!({"inBlack": 50}));
|
||||
crate::dialogs::cancel(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
press(&mut h, "Cmd+Alt+L");
|
||||
let (id, fields) = top(&h);
|
||||
assert_eq!(fields["inBlack"], levels["inBlack"], "Cancel remembers nothing");
|
||||
crate::dialogs::cancel(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
|
||||
// Curves has nothing of its own yet: its defaults, not Levels' settings.
|
||||
let curve_defaults = to_params(&default_for("curves", ColorMode::Rgb).unwrap());
|
||||
press(&mut h, "Cmd+Alt+M");
|
||||
let (id, fields) = top(&h);
|
||||
assert_eq!(fields["__adjust"], json!("curves"));
|
||||
assert_eq!(fields["points"], curve_defaults["points"], "⌥⌘M with no Curves confirmed yet");
|
||||
let curve = json!({"points": [[0, 0], [100, 150], [255, 255]]});
|
||||
set(&mut h, id, curve.clone());
|
||||
crate::dialogs::confirm(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
press(&mut h, "Cmd+Alt+M");
|
||||
let (id, fields) = top(&h);
|
||||
let curves = to_params(&from_params("curves", &curve, None, ColorMode::Rgb).unwrap());
|
||||
assert_eq!(fields["points"], curves["points"], "⌥⌘M opens at the last OK'd curve");
|
||||
crate::dialogs::cancel(h.state_mut(), id).unwrap();
|
||||
h.run_steps(2);
|
||||
take_log(&h.ctx);
|
||||
|
||||
h.state_mut().session.edit_prefs(|p| {
|
||||
p.shortcuts.insert("image.adjustments.desaturate".into(), "Cmd+Alt+L".into());
|
||||
});
|
||||
press(&mut h, "Cmd+Alt+L");
|
||||
assert_eq!(logged(&h), ["image.adjustments.desaturate"], "a key assigned to another command is that command's");
|
||||
assert!(h.state().ui.dialogs.is_empty());
|
||||
}
|
||||
|
||||
/// #8900: in Photoshop ⌥ held while choosing an Image › Adjustments item opens it at its last
|
||||
/// settings too; a plain click still opens it at the defaults.
|
||||
#[test]
|
||||
fn alt_choosing_an_adjustment_from_the_menu_reopens_the_last_settings() {
|
||||
use photocraft_doc::ColorMode;
|
||||
use photocraft_engine::adjust_params::{default_for, from_params, to_params};
|
||||
let mut h = harness();
|
||||
h.state_mut().session.edit_prefs(|p| {
|
||||
p.dialogs.insert("image.adjustments.levels".into(), json!({"inBlack": 30, "gamma": 1.3}));
|
||||
});
|
||||
let levels = to_params(&from_params("levels", &json!({"inBlack": 30, "gamma": 1.3}), None, ColorMode::Rgb).unwrap());
|
||||
let defaults = to_params(&default_for("levels", ColorMode::Rgb).unwrap());
|
||||
let choose = |h: &mut Harness<'_, PhotocraftApp>, m: Modifiers| {
|
||||
h.get_by_label("Image").click();
|
||||
h.run_steps(3);
|
||||
// The Image menu's row, not the Adjustments panel's tab in the dock on the right.
|
||||
let adjustments = h.get_all_by_label_contains("Adjustments").map(|n| n.rect()).min_by(|a, b| a.left().total_cmp(&b.left())).unwrap().center();
|
||||
h.hover_at(adjustments);
|
||||
h.run_steps(30);
|
||||
let at = h.get_by_label_contains("Levels…").rect().center();
|
||||
h.hover_at(at);
|
||||
h.run_steps(2);
|
||||
h.event(egui::Event::ModifiersChanged(m));
|
||||
h.event(egui::Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: m });
|
||||
h.run_steps(1);
|
||||
h.event(egui::Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: m });
|
||||
h.run_steps(1);
|
||||
h.event(egui::Event::ModifiersChanged(Modifiers::NONE));
|
||||
h.run_steps(3);
|
||||
let d = h.state().ui.dialogs.last().expect("Levels opened").clone();
|
||||
assert_eq!(d.fields["__adjust"], json!("levels"));
|
||||
crate::dialogs::cancel(h.state_mut(), d.id).unwrap();
|
||||
h.run_steps(2);
|
||||
(d.fields["inBlack"].clone(), d.fields["gamma"].clone())
|
||||
};
|
||||
assert_eq!(choose(&mut h, Modifiers::NONE), (defaults["inBlack"].clone(), defaults["gamma"].clone()), "a click opens at the defaults");
|
||||
assert_eq!(choose(&mut h, Modifiers::ALT), (levels["inBlack"].clone(), levels["gamma"].clone()), "⌥-click opens at the last settings");
|
||||
// The plain click noted Levels already: put another command in front of it first.
|
||||
crate::palette::note_recent(&h.ctx, "file.new");
|
||||
choose(&mut h, Modifiers::ALT);
|
||||
assert_eq!(crate::palette::recent_ids(&h.ctx).first().map(String::as_str), Some("image.adjustments.levels"), "Edit › Search's most recent");
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity: tools, handles, modifiers and feel
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (Crop default mode: moving the image under the box snaps the frame's edges and centre, #8580; View › Extras hides the Move transform controls, #6699; Hand tool ⌘ / ⌥ are Zoom In / Zoom Out while held, #6434; the wheel steps value fields, #5473; a dragged guide shows its X or Y beside the pointer, #7680; Type options bar "T" icon scrubs the font size, #6437; Move tool: dragged selected pixels take their own outline as the selection, #7538; artboard backgrounds remain below child Free Transform previews; ⌫/Delete removes the selected smart filter, #4751; Object Finder foreground/background discrimination (#6548); rounded triangle, polygon and star corners; shape Fill/Stroke popovers accept hex RGB values, #6176; a guide let go over either ruler is removed, #6286; Crop rows corrected: saved crop presets and Delete Cropped Pixels off are done, #6562; marching ants in device pixels, #7489; Animated Zoom: holding the Zoom tool zooms continuously, #6210; shape Density and Feather, TOOL-214-12, #5846; Pen Auto Add/Delete on the path being drawn, #6140; Show Transform Controls frames every selected layer; guide keys and clicks; menu bar from the keyboard, #4988; Crop tool Content-Aware Fill; on-canvas corner radius widgets, TOOL-214-12; Curvature Pen edits the active path; Clip Thumbnails to Layer Bounds; Pen ⇧ 45°; last selected tool restored at launch; Layers panel filter-type dropdown with Kind, Name and Mode, #5048; Free Transform box and marching ants follow the selection, #5297; History: the trash deletes the selected snapshot, rows drag onto the trash, #5324; smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed; selection-modifier and ⌥-wheel rows match their tests) · **Target:** Adobe Photoshop 2026
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (⌥ + an adjustment's shortcut reopens it at its last settings, ⌥⌘L / ⌥⌘M, #8900; Crop default mode: moving the image under the box snaps the frame's edges and centre, #8580; View › Extras hides the Move transform controls, #6699; Hand tool ⌘ / ⌥ are Zoom In / Zoom Out while held, #6434; the wheel steps value fields, #5473; a dragged guide shows its X or Y beside the pointer, #7680; Type options bar "T" icon scrubs the font size, #6437; Move tool: dragged selected pixels take their own outline as the selection, #7538; artboard backgrounds remain below child Free Transform previews; ⌫/Delete removes the selected smart filter, #4751; Object Finder foreground/background discrimination (#6548); rounded triangle, polygon and star corners; shape Fill/Stroke popovers accept hex RGB values, #6176; a guide let go over either ruler is removed, #6286; Crop rows corrected: saved crop presets and Delete Cropped Pixels off are done, #6562; marching ants in device pixels, #7489; Animated Zoom: holding the Zoom tool zooms continuously, #6210; shape Density and Feather, TOOL-214-12, #5846; Pen Auto Add/Delete on the path being drawn, #6140; Show Transform Controls frames every selected layer; guide keys and clicks; menu bar from the keyboard, #4988; Crop tool Content-Aware Fill; on-canvas corner radius widgets, TOOL-214-12; Curvature Pen edits the active path; Clip Thumbnails to Layer Bounds; Pen ⇧ 45°; last selected tool restored at launch; Layers panel filter-type dropdown with Kind, Name and Mode, #5048; Free Transform box and marching ants follow the selection, #5297; History: the trash deletes the selected snapshot, rows drag onto the trash, #5324; smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed; selection-modifier and ⌥-wheel rows match their tests) · **Target:** Adobe Photoshop 2026
|
||||
|
||||
How PhotoCraft's direct manipulation compares with Photoshop 2026 (27.11.0): the toolbox, the
|
||||
handles and modifier keys of each gesture, nudging, snapping, numeric precision, navigation,
|
||||
@@ -207,6 +207,7 @@ Triangle and Curvature Pen (minus legacy Rounded Rectangle and Targeted Adjustme
|
||||
| Layer and selection keys: ⌘, toggles visibility, ⌘\ targets the mask, / locks transparency, ⌘⌥2 loads luminosity, ⌘⌥⌫ fills from history, Tab / ⇧Tab rename the next layer, ⌥-click a thumbnail fits the layer | done (2026-10-11) | |
|
||||
| Tool keys: ← / → turn the brush tip (1°, ⇧ 15°), ⇧-click with Type starts new text over old, Space+F / ⇧Space+F cycle the pasteboard colour | done (2026-10-11) | |
|
||||
| Type formatting keys while editing text | done: ⇧⌘K, U, B, I and / toggle All Caps, Underline, Faux Bold, Faux Italic, Strikethrough (off when every selected character has it, else on); ⌥↑/↓ leading ∓2 pt, ⇧⌥↑/↓ baseline shift ±2 pt (10 with ⌘); ⌥←/→ tracking ±20 over a selection (100 with ⌘; a collapsed caret still kerns); ⌥⇧⌘H toggles Hyphenate for the touched paragraphs. One `type.setStyle` step per press (new `leadingBy`/`baselineShiftBy`/`trackingBy`; auto leading starts from its laid-out value); the global ⇧⌘K/U/B/I never fire while typing. Differs: a collapsed caret in non-empty text changes nothing, since the model can't hold a pending style for the next characters typed (empty text takes it as its typing style) (`type_tool.rs`, `input_tests.rs` `type_tool_format_keys_*`) | |
|
||||
| ⌥ + an adjustment's shortcut opens it at its last settings (⌥⌘L Levels, ⌥⌘M Curves, and so ⌥⌘U, ⌥⌘B) | done (#8900): the settings of the last OK, kept in the preferences; Cancel and smart filter edits keep nothing; a key assigned to another command stays that command's. ⌥ while choosing the item from Image › Adjustments does the same | `shortcut_dispatch/tests.rs` `alt_adjustment_shortcut_reopens_the_last_confirmed_settings`, `alt_choosing_an_adjustment_from_the_menu_reopens_the_last_settings` |
|
||||
| Guides: click selects, Delete deletes, right-click menu, ⌥-click turns a guide, double-click opens Guides, Grid & Slices | done (2026-10-12) | a guide let go over either ruler is removed, not only off the canvas (#6286) |
|
||||
|
||||
## Panels and workspace
|
||||
@@ -293,3 +294,4 @@ Paragraph; the rest open as in-app floating windows.
|
||||
| 2026-10-16 | minor | View › Extras (⌘H) hides the Move tool's transform controls and their handles stop responding; ⌘H again restores them, and Show Transform Controls keeps its setting (#6699) |
|
||||
| 2026-10-15 | minor | ⌫/Delete with a smart filter row selected removes that filter (the Smart Filters row: all of them), as the Delete button does, instead of the smart object (#4751) |
|
||||
| 2026-10-14 | minor | Rounded corners on live triangles, polygons and stars: Properties field and on-canvas widgets (TOOL-214-12) |
|
||||
| 2026-10-23 | minor | ⌥ + an adjustment's shortcut (⌥⌘L Levels, ⌥⌘M Curves) opens its dialog at the settings of its last OK, as in Photoshop (#8900); no percentage changed |
|
||||
|
||||
Reference in New Issue
Block a user