mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 20:48:33 -06:00
MENU-EDIT-PASTESELECTED + KEY-SEARCH: paste selected settings, Find, fuller photo context menu
- Edit → Paste Selected Settings… (⇧⌘V): group checklist prefilled with the copied groups;
develop.paste {groups} pastes only the chosen copied groups.
- Edit → Find… (⌘F, view.focusSearch) focuses the search field.
- Photo context menu: Remove from Album (in an album), Create Version, Paste Selected
Settings…, Show in Finder, Export…, Export with Preset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
faa5478966
commit
8548c157ba
@@ -503,18 +503,32 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
Ok(json!({"groups": s.copy_groups}))
|
||||
}
|
||||
),
|
||||
cmd!("develop.paste", "Paste Edit Settings", ["Edit"], Some("Cmd+V"), "{ids?}", has_clipboard, |s, p| {
|
||||
let clip = s.clipboard.clone().unwrap_or_default();
|
||||
let ids = s.targets(p);
|
||||
let ops = ids
|
||||
.iter()
|
||||
.filter_map(|id| s.develop_of(*id).map(|d| (*id, d)))
|
||||
.filter_map(|(id, d)| s.develop_op(id, lightcraft_develop::apply_partial(&d, &clip, 1.0), "Paste Settings"))
|
||||
.collect::<Vec<_>>();
|
||||
let n = ops.len();
|
||||
s.commit("Paste Settings", Op::Batch { ops })?;
|
||||
Ok(json!({"changed": n}))
|
||||
}),
|
||||
cmd!(
|
||||
"develop.paste",
|
||||
"Paste Edit Settings",
|
||||
["Edit"],
|
||||
Some("Cmd+V"),
|
||||
"{ids?, groups?: [settingsGroup] (paste only these of the copied groups)}",
|
||||
has_clipboard,
|
||||
|s, p| {
|
||||
let mut clip = s.clipboard.clone().unwrap_or_default();
|
||||
if let Some(only) = groups_param(p) {
|
||||
// the clipboard holds whole groups: rebuild it with just the chosen (copied) ones
|
||||
let only: Vec<_> = only.into_iter().filter(|g| s.copy_groups.contains(g)).collect();
|
||||
let full = lightcraft_develop::apply_partial(&DevelopSettings::default(), &clip, 1.0);
|
||||
clip = lightcraft_develop::extract_groups(&full, &only);
|
||||
}
|
||||
let ids = s.targets(p);
|
||||
let ops = ids
|
||||
.iter()
|
||||
.filter_map(|id| s.develop_of(*id).map(|d| (*id, d)))
|
||||
.filter_map(|(id, d)| s.develop_op(id, lightcraft_develop::apply_partial(&d, &clip, 1.0), "Paste Settings"))
|
||||
.collect::<Vec<_>>();
|
||||
let n = ops.len();
|
||||
s.commit("Paste Settings", Op::Batch { ops })?;
|
||||
Ok(json!({"changed": n}))
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"develop.sync",
|
||||
"Sync Settings",
|
||||
|
||||
@@ -209,3 +209,22 @@ fn memory_report_counts_decoded_sources_and_renders() {
|
||||
assert_eq!(m.thumb_sources.count, 1);
|
||||
assert!(m.rendered.count >= 1, "the thumbnail render is cached");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_selected_settings_pastes_only_chosen_copied_groups() {
|
||||
let mut s = demo();
|
||||
let ids: Vec<_> = s.visible().iter().copied().take(2).collect();
|
||||
s.execute("library.select", &json!({"ids": [ids[0].0]})).unwrap();
|
||||
s.execute("develop.set", &json!({"values": {"light.exposure": 1.0, "color.vibrance": 30, "detail.sharpenAmount": 90}})).unwrap();
|
||||
s.execute("develop.copy", &json!({"groups": ["light", "color"]})).unwrap();
|
||||
// `detail` was not copied: asking for it pastes nothing for it
|
||||
s.execute("develop.paste", &json!({"ids": [ids[1].0], "groups": ["light", "detail"]})).unwrap();
|
||||
let d = s.develop_of(ids[1]).unwrap();
|
||||
let base = lightcraft_develop::DevelopSettings::default();
|
||||
assert_eq!(d.light.exposure, 1.0);
|
||||
assert_eq!(d.color.vibrance, base.color.vibrance);
|
||||
assert_ne!(d.detail.sharpen_amount, 90.0);
|
||||
// without groups: everything copied
|
||||
s.execute("develop.paste", &json!({"ids": [ids[1].0]})).unwrap();
|
||||
assert_eq!(s.develop_of(ids[1]).unwrap().color.vibrance, 30.0);
|
||||
}
|
||||
|
||||
@@ -550,6 +550,38 @@ mod tests {
|
||||
h.settle(SETTLE);
|
||||
}
|
||||
|
||||
/// ⇧⌘V opens Paste Selected Settings (prefilled with the copied groups); unchecking a group
|
||||
/// leaves it alone. ⌘F puts typing into the search field.
|
||||
#[test]
|
||||
fn paste_selected_settings_and_find() {
|
||||
let mut h = demo([1200.0, 900.0]);
|
||||
let t = Duration::from_secs(10);
|
||||
let ids: Vec<u64> = h.app.session.visible_cloned().iter().take(2).map(|p| p.0).collect();
|
||||
let exec = |h: &mut Headless, c: &str, p: Value| h.request("engine.execute", json!({"command": c, "params": p}), t);
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[0]]}));
|
||||
exec(&mut h, "develop.set", json!({"values": {"light.exposure": 1.0, "color.vibrance": 30}}));
|
||||
exec(&mut h, "develop.copy", json!({"groups": ["light", "color"]}));
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[1]]}));
|
||||
h.request("ui.key", json!({"key": "V", "cmd": true, "shift": true}), t);
|
||||
h.settle(SETTLE);
|
||||
let Some(crate::state::Dialog::PasteSettings { groups }) = &h.app.ui.dialog else { panic!("dialog not open: {:?}", h.app.ui.dialog) };
|
||||
assert_eq!(groups, &["light", "color"]);
|
||||
let r = h.request("ui.clickWidget", json!({"id": "pasteGroup:color"}), t);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert_eq!(h.request("ui.dialog.confirm", json!({}), t)["ok"], true);
|
||||
let d = h.app.session.develop_of(lightcraft_catalog::PhotoId(ids[1])).unwrap();
|
||||
assert_eq!((d.light.exposure, d.color.vibrance), (1.0, 0.0));
|
||||
// ⌘F, then typing filters the grid by text
|
||||
h.request("ui.set", json!({"view": "grid"}), t);
|
||||
h.request("ui.key", json!({"key": "F", "cmd": true}), t);
|
||||
h.settle(SETTLE);
|
||||
h.request("ui.text", json!({"text": "zz-no-such-photo"}), t);
|
||||
h.settle(SETTLE);
|
||||
assert_eq!(h.app.ui.search, "zz-no-such-photo");
|
||||
assert!(h.app.session.visible_cloned().is_empty());
|
||||
}
|
||||
|
||||
/// Info panel → Edit Capture Time…: a time-zone shift moves the selected photos.
|
||||
#[test]
|
||||
fn capture_time_dialog_shifts_time_zone() {
|
||||
|
||||
@@ -80,10 +80,13 @@ const LAYOUT: &[(&str, &[&str])] = &[
|
||||
"develop.copy",
|
||||
"dialog.copySettings",
|
||||
"develop.paste",
|
||||
"dialog.pasteSettings",
|
||||
"---",
|
||||
"library.selectAll",
|
||||
"library.selectNone",
|
||||
"---",
|
||||
"view.focusSearch",
|
||||
"---",
|
||||
"app.settings",
|
||||
],
|
||||
),
|
||||
|
||||
@@ -79,6 +79,8 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
("dialog.createPreset", "Create Preset…", Some("Cmd+Shift+P"), "Photo"),
|
||||
("dialog.autoStack", "Auto-Stack by Capture Time…", None, "Photo>Stack"),
|
||||
("dialog.copySettings", "Choose Edit Settings to Copy…", Some("Cmd+Shift+C"), "Edit"),
|
||||
("dialog.pasteSettings", "Paste Selected Settings…", Some("Cmd+Shift+V"), "Edit"),
|
||||
("view.focusSearch", "Find…", Some("Cmd+F"), "Edit"),
|
||||
("dialog.export", "Export…", Some("Cmd+Shift+E"), "File"),
|
||||
("dialog.mergeHdr", "HDR…", Some("Ctrl+H"), "Photo>Photo Merge"),
|
||||
("dialog.mergePanorama", "Panorama…", Some("Ctrl+M"), "Photo>Photo Merge"),
|
||||
@@ -511,6 +513,17 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
|
||||
app.ui.dialog = Some(Dialog::create_preset());
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"dialog.pasteSettings" => {
|
||||
let groups =
|
||||
app.session.copy_groups.iter().filter_map(|g| serde_json::to_value(g).ok().and_then(|v| v.as_str().map(str::to_string))).collect();
|
||||
app.ui.dialog = Some(Dialog::PasteSettings { groups });
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"view.focusSearch" => {
|
||||
// the search field lives in the top bar of the library and detail views alike
|
||||
app.ui.focus_search = true;
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"dialog.copySettings" => {
|
||||
let groups =
|
||||
app.session.copy_groups.iter().filter_map(|g| serde_json::to_value(g).ok().and_then(|v| v.as_str().map(str::to_string))).collect();
|
||||
@@ -625,6 +638,7 @@ pub fn ui_enabled(app: &LightcraftApp, id: &str) -> bool {
|
||||
app.session.active().is_some()
|
||||
}
|
||||
"app.exportPrevious" => app.session.active().is_some() && app.session.last_export.is_some(),
|
||||
"dialog.pasteSettings" => app.session.active().is_some() && app.session.clipboard.is_some(),
|
||||
"app.showInFinder" => {
|
||||
app.services.reveal.is_some()
|
||||
&& app
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
Dialog::AutoStack { .. } => "Auto-Stack by Capture Time",
|
||||
Dialog::CreatePreset { .. } => "Create Preset",
|
||||
Dialog::CopySettings { .. } => "Choose Edit Settings to Copy",
|
||||
Dialog::PasteSettings { .. } => "Paste Selected Settings",
|
||||
Dialog::Export { .. } => "Export",
|
||||
Dialog::Merge { opts } => opts.title(),
|
||||
Dialog::Settings { .. } => "Settings",
|
||||
@@ -233,6 +234,14 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
group_checklist(ui, "presetInclude", groups);
|
||||
}
|
||||
Dialog::CopySettings { groups } => group_checklist(ui, "copyGroup", groups),
|
||||
Dialog::PasteSettings { groups } => {
|
||||
let n = app.session.targets(&json!({})).len();
|
||||
ui.label(
|
||||
egui::RichText::new(format!("Paste into {n} photo{} — only settings that were copied are pasted", if n == 1 { "" } else { "s" }))
|
||||
.color(t.text_dim),
|
||||
);
|
||||
group_checklist(ui, "pasteGroup", groups);
|
||||
}
|
||||
Dialog::Export { opts, full_size, resize, preset_name, limit_kb, dir } => {
|
||||
use lightcraft_engine::export::{Anchor as P, ExportFormat as F, MetadataPolicy as M, SharpenAmount as A, SharpenFor as S};
|
||||
let n = app.session.selection.ids.len().max(1);
|
||||
@@ -589,6 +598,7 @@ pub fn confirm_dialog(app: &mut LightcraftApp, dlg: &Dialog) -> Result<serde_jso
|
||||
}),
|
||||
),
|
||||
Dialog::CopySettings { groups } => app.run("develop.copy", json!({"groups": groups})),
|
||||
Dialog::PasteSettings { groups } => app.run("develop.paste", json!({"groups": groups})),
|
||||
Dialog::Export { opts, full_size, resize, limit_kb, dir, .. } => {
|
||||
let mut p = export_dialog_params(opts, *full_size, resize, *limit_kb);
|
||||
p["dir"] = json!(dir);
|
||||
|
||||
@@ -467,12 +467,22 @@ pub fn context_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
}
|
||||
}
|
||||
});
|
||||
// in a (non-smart) album: take the selection out of it
|
||||
if let lightcraft_engine::LibrarySource::Album(aid) = app.session.source
|
||||
&& app.session.catalog.album(aid).is_some_and(|a| !a.folder && !a.is_smart())
|
||||
&& ui.button("Remove from Album").clicked()
|
||||
{
|
||||
let _ = app.run("album.removePhotos", json!({"id": aid.0}));
|
||||
}
|
||||
if ui.button("Rename…").clicked() {
|
||||
let _ = app.run("dialog.rename", json!({}));
|
||||
}
|
||||
if ui.button("Create Virtual Copy").clicked() {
|
||||
let _ = app.run("photo.virtualCopy", json!({}));
|
||||
}
|
||||
if ui.button("Create Version").clicked() {
|
||||
let _ = app.run("version.create", json!({}));
|
||||
}
|
||||
ui.menu_button("Stack", |ui| {
|
||||
let stacked = app.session.catalog.stack_of(id).is_some();
|
||||
let several = app.session.selection.ids.len() > 1;
|
||||
@@ -499,6 +509,9 @@ pub fn context_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
if ui.add_enabled(app.session.clipboard.is_some(), egui::Button::new("Paste Edit Settings")).clicked() {
|
||||
let _ = app.run("develop.paste", json!({}));
|
||||
}
|
||||
if ui.add_enabled(app.session.clipboard.is_some(), egui::Button::new("Paste Selected Settings…")).clicked() {
|
||||
let _ = app.run("dialog.pasteSettings", json!({}));
|
||||
}
|
||||
if ui.button("Reset Edits").clicked() {
|
||||
let _ = app.run("develop.reset", json!({}));
|
||||
}
|
||||
@@ -518,6 +531,26 @@ pub fn context_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
let _ = app.run("photo.rotateRight", json!({}));
|
||||
}
|
||||
ui.separator();
|
||||
if ui.add_enabled(crate::menus::ui_enabled(app, "app.showInFinder"), egui::Button::new("Show in Finder")).clicked() {
|
||||
let _ = app.run("app.showInFinder", json!({}));
|
||||
}
|
||||
if ui.button("Export…").clicked() {
|
||||
let _ = app.run("dialog.export", json!({}));
|
||||
}
|
||||
ui.menu_button("Export with Preset", |ui| {
|
||||
for (p, _) in app.session.all_export_presets() {
|
||||
if ui.button(&p.name).clicked() {
|
||||
match app.run("app.export", json!({"preset": p.name})) {
|
||||
Ok(r) => {
|
||||
let n = r["files"].as_array().map_or(0, Vec::len);
|
||||
app.toast(ui.ctx(), format!("Exported {n} photo{}", if n == 1 { "" } else { "s" }));
|
||||
}
|
||||
Err(e) => app.toast(ui.ctx(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
if ui.button("Delete Photo").clicked() {
|
||||
let _ = app.run("photo.delete", json!({}));
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
// search field (centred on the window)
|
||||
let sr = Rect::from_center_size(pos2(full.center().x, full.center().y), vec2(sw, 28.0));
|
||||
let id = egui::Id::new("search-field");
|
||||
if std::mem::take(&mut app.ui.focus_search) {
|
||||
ui.memory_mut(|m| m.request_focus(id));
|
||||
}
|
||||
let focused = ui.memory(|m| m.has_focus(id));
|
||||
ui.painter().rect(
|
||||
sr,
|
||||
|
||||
@@ -199,6 +199,9 @@ pub struct UiState {
|
||||
pub crop_overlay: CropOverlay,
|
||||
pub show_filenames: bool,
|
||||
pub search: String,
|
||||
/// Focus the search field on the next frame (Edit → Find…).
|
||||
#[serde(skip)]
|
||||
pub focus_search: bool,
|
||||
/// Selected curve channel in the Curve flyout.
|
||||
pub curve_channel: String,
|
||||
/// Selected mixer mode: "hue" | "saturation" | "luminance" | "all".
|
||||
@@ -329,6 +332,10 @@ pub enum Dialog {
|
||||
CopySettings {
|
||||
groups: Vec<String>,
|
||||
},
|
||||
/// Paste Selected Settings: which of the copied groups to paste.
|
||||
PasteSettings {
|
||||
groups: Vec<String>,
|
||||
},
|
||||
/// `resize` is used unless `full_size`; `limit_kb` 0 = no limit; `dir` empty = default export folder.
|
||||
Export {
|
||||
opts: lightcraft_engine::export::ExportOptions,
|
||||
@@ -382,6 +389,7 @@ impl Default for UiState {
|
||||
crop_overlay: CropOverlay::Thirds,
|
||||
show_filenames: true,
|
||||
search: String::new(),
|
||||
focus_search: false,
|
||||
curve_channel: "parametric".into(),
|
||||
mixer_mode: "hue".into(),
|
||||
grading_mode: "3way".into(),
|
||||
|
||||
+10
-11
@@ -45,10 +45,10 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| V. Preferences (PREF) | 4 | 1 | 3 | 3 | 1/1 (100%) | 3/4 (75%) |
|
||||
| W. Cloud & AI infrastructure (CLOUD / AI) | 0 | 1 | 1 | 2 | — | — |
|
||||
| X. Cross-cutting behaviours (BEHAV) | 10 | 3 | 4 | 1 | 7/8 (88%) | 3/5 (60%) |
|
||||
| Y. Menus | 54 | 17 | 14 | 8 | 39/47 (83%) | 13/23 (57%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 54 | 13 | 13 | 1 | 45/52 (87%) | 9/23 (39%) |
|
||||
| Y. Menus | 56 | 16 | 13 | 8 | 41/47 (87%) | 13/23 (57%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 56 | 13 | 11 | 1 | 47/52 (90%) | 9/23 (39%) |
|
||||
| Lightroom Classic extras | 4 | 20 | 55 | 9 | — | 3/21 (14%) |
|
||||
| **Total** | 260 | 81 | 159 | 37 | 178/198 (90%) | 72/144 (50%) |
|
||||
| **Total** | 264 | 80 | 156 | 37 | 182/198 (92%) | 72/144 (50%) |
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps
|
||||
@@ -85,9 +85,8 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| Select None | ⌘⇧A (+ ⌘D) | ⌘D | — |
|
||||
|
||||
**Still missing / broken:**
|
||||
- No command yet: F full-screen preview, ⇧⌘F window full screen, ⇧⌘V paste selected,
|
||||
⌘, settings, ⌘F focus search, ⌘G / ⇧⌘G stacks, A visualize spots, ⌃H / ⌃M merges, F1 help,
|
||||
⇧6–9 label + advance.
|
||||
- No command yet: F1 help, ⇧6–9 label + advance (verify the rest of the old list: full screen, settings, stacks,
|
||||
visualize spots and merges have commands now).
|
||||
- `H` opens Remove; Lightroom also uses it (Classic) to hide pins — pins toggle from View → Show Mask Pins.
|
||||
- ⌘M / ⌘H / ⌘Q / ⌘W rely on the platform window defaults (unverified).
|
||||
|
||||
@@ -522,11 +521,11 @@ visible menu bar yet: the menu model is only exposed through the control channel
|
||||
| MENU-EDIT-REDO | Redo | P0 | ✅ | `cmd:edit.redo` | |
|
||||
| MENU-EDIT-COPYPASTE | Copy / paste (edit settings) | P0 | ✅ | `cmd:develop.copy`, `cmd:develop.paste` | |
|
||||
| MENU-EDIT-CHOOSECOPY | Choose Edit Settings to Copy… | P0 | ✅ | `cmd:dialog.copySettings` | |
|
||||
| MENU-EDIT-PASTESELECTED | Paste Selected Settings | P0 | ⬜ | | |
|
||||
| MENU-EDIT-PASTESELECTED | Paste Selected Settings | P0 | ✅ | `cmd:dialog.pasteSettings`, `cmd:develop.paste` (`groups`) | checklist prefilled with the copied groups; only copied groups can be pasted |
|
||||
| MENU-EDIT-SELECTALL | Select All | P0 | ✅ | `cmd:library.selectAll` | |
|
||||
| MENU-EDIT-SELECTNONE | Select None | P0 | ✅ | `cmd:library.selectNone` | |
|
||||
| MENU-EDIT-SELECTBY | Select by flag / rating | P1 | ⬜ | | |
|
||||
| MENU-EDIT-FIND | Find… | P0 | 🟡 | `crates/ui-egui/src/panels/topbar.rs` | search field; no command to focus it |
|
||||
| MENU-EDIT-FIND | Find… | P0 | ✅ | `cmd:view.focusSearch`, `crates/ui-egui/src/panels/topbar.rs` | ⌘F focuses the search field |
|
||||
| MENU-VIEW-PHOTOGRID | Photo Grid | P0 | ✅ | `cmd:view.photoGrid` | |
|
||||
| MENU-VIEW-SQUAREGRID | Square Grid | P0 | ✅ | `cmd:view.squareGrid` | |
|
||||
| MENU-VIEW-DETAIL | Detail | P0 | ✅ | `cmd:view.detail` | |
|
||||
@@ -580,7 +579,7 @@ visible menu bar yet: the menu model is only exposed through the control channel
|
||||
| MENU-HELP-SHORTCUTS | Keyboard shortcuts | P1 | ✅ | `cmd:app.shortcuts` | |
|
||||
| MENU-HELP-FEEDBACK | Send feedback | P2 | ⬜ | | |
|
||||
| MENU-HELP-SYSINFO | System info | P2 | ⬜ | `cmd:library.info` | library info only |
|
||||
| MENU-CTX-GRID | Photo context menu | P0 | 🟡 | `crates/ui-egui/src/panels/grid.rs` (`context_menu`) | rate, flag, add to album, copy/paste, reset, rotate, delete; no label, remove from album, export, reveal, version, cover |
|
||||
| MENU-CTX-GRID | Photo context menu | P0 | 🟡 | `crates/ui-egui/src/panels/grid.rs` (`context_menu`) | rate, flag, label, add to / remove from album, rename, virtual copy, version, stack, copy / paste / paste selected, reset, merge, rotate, show in Finder, export / export with preset, delete; no set-as-album-cover |
|
||||
| MENU-CTX-DETAIL | Loupe context menu | P1 | 🟡 | `crates/ui-egui/src/panels/detail.rs` | same as grid; no zoom submenu |
|
||||
| MENU-CTX-ALBUM | Album / folder row menu | P0 | 🟡 | `crates/ui-egui/src/panels/left.rs` | add selected, rename, delete; no move-to, export album |
|
||||
| MENU-CTX-MASK | Mask / component menu | P0 | 🟡 | `crates/ui-egui/src/panels/masking.rs` | add/subtract component; rename/duplicate by command only |
|
||||
@@ -612,9 +611,9 @@ From `06-shortcuts.md` part 1. Evidence is our binding; conflicts are explained
|
||||
| KEY-ZOOM | Zoom in / out — ⌘= / ⌘− | P0 | ✅ | `cmd:view.zoomIn`, `cmd:view.zoomOut` | |
|
||||
| KEY-SELECTALL | Select all — ⌘A | P0 | ✅ | `cmd:library.selectAll` | |
|
||||
| KEY-SELECTNONE | Select none — ⌘D | P0 | ✅ | `cmd:library.selectNone` | secondary binding (primary ⌘⇧A) |
|
||||
| KEY-PASTESELECTED | Paste selected — ⇧⌘V | P0 | ⬜ | | no command |
|
||||
| KEY-PASTESELECTED | Paste selected — ⇧⌘V | P0 | ✅ | `cmd:dialog.pasteSettings` | |
|
||||
| KEY-PREFS | Settings — ⌘, | P0 | ✅ | `cmd:app.settings` | |
|
||||
| KEY-SEARCH | Search — ⌘F | P0 | ⬜ | | no focus-search command |
|
||||
| KEY-SEARCH | Search — ⌘F | P0 | ✅ | `cmd:view.focusSearch` | |
|
||||
| KEY-VISUALIZESPOTS | Visualize spots — A | P1 | ⬜ | | |
|
||||
| KEY-CYCLEOVERLAY | Cycle overlay — O | P0 | ✅ | `cmd:view.maskOverlay`, `cmd:view.maskOverlayMode`, `cmd:view.cropOverlay` | O toggles the mask overlay; ⇧O cycles mask overlay modes while masking, crop overlays elsewhere |
|
||||
| KEY-PHOTOSPANEL | Photos panel — P | P0 | 🟡 | `cmd:view.leftPanel` | bound to ⌘⇧L; P = pick |
|
||||
|
||||
Reference in New Issue
Block a user