Skip to content

Shortcuts for every command, Last Filter, New from Clipboard, Camera Raw presets, crop ratios, History panel - #3

Merged
itsjavi merged 13 commits into
mainfrom
feat/shortcuts-and-workflow
Oct 8, 2026
Merged

itsjavi merged 13 commits into
mainfrom
feat/shortcuts-and-workflow

Conversation

@itsjavi

@itsjavi itsjavi commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Five workflow features from the upstream review (backlog doc-1), ported or reimplemented: shortcuts for every menu command plus Filter › Last Filter, File › New from Clipboard, Camera Raw section resets and presets, custom crop ratios with the crop buttons moved next to the ratio, and a History panel. One commit per task or upstream PR, with co-author credit.

Merge note: merge this after the upstream-fixes PR. TASK-22 lists TASK-10 (Camera Raw speed) as a dependency for ordering only; the two touch different Camera Raw code.

TASK-18: shortcuts for every menu command, and Last Filter (⌘F)

  • Ports upstream Keyboard Shortcuts: any menu command can take a key, and a key can be cleared robbietilton/Compositor#186. A More Menu Commands section in Edit › Keyboard Shortcuts lists every menu command without a default key by its "Menu › Item" title (assignableShortcut). Each row gets a ⊗ that clears its key. Saved overrides load one at a time, so a refused one drops alone instead of resetting all of them. Delete is recorded as U+007F, so the Fill and Content-Aware Fill entries are found again.
  • Added on top of the port:
    • The app-menu and Open Recent commands the port missed: Check for Updates, Hide Compositor, Show All and Clear Menu. Hide needed this because ⌘H belongs to Show Transform Controls.
    • The ⌥⌘H entry was titled "Hide Compositor" but is Hide Others, so it's renamed. An override saved under the old id is dropped on load.
    • A saved value that doesn't decode at all now drops alone too (separate fix: commit). Upstream still reset everything in that case.
  • Ports Filter › Last Filter (⌘F) robbietilton/Compositor#192 on top. Filter › Last Filter (⌘F) runs the last Filter-menu filter again with the same settings, without the panel, as one undo step. Upstream's test that read ⌘F from the real menu bar can't run under CompositorTestHost, so a test of the shortcut entry replaces it.

TASK-13: File › New from Clipboard

  • Ports Adding the feature 'New from Clipboard' robbietilton/Compositor#201. It opens a new project the size of the copied image, with the image as the only layer, in one undo step ("New from Clipboard"). Pixels copied in an open project come first, the same way Paste takes them.
  • From Cmd-V on an empty project starts the canvas from the copied image robbietilton/Compositor#176 this takes only the handling of image files copied in Finder. Their URLs win over the icon Finder also copies as image data, and they open the way a Dock drop does (one project each). A copied file that isn't an image doesn't count. Cmd-V on an empty project starts the canvas from the copied image robbietilton/Compositor#176's ⌘V-on-an-empty-project behavior isn't included.
  • The shortcut is ⌥⌘N, not upstream's ⇧⌘V, which is Photoshop's Paste in Place.
  • The item is disabled without an image on the clipboard. macOS doesn't announce clipboard changes, so the app checks the change count when it becomes active and every second while it's active. The check looks only at pasteboard types and NSPasteboardItem.detectedMetadata content types, never at the contents, so it can't trigger macOS's clipboard-access prompt. The command reads the clipboard only when chosen.
  • The tests use NSPasteboard.withUniqueName() pasteboards and never touch the general pasteboard.

TASK-22: Camera Raw section resets and presets

  • Ports Camera Raw: reset each section from its header robbietilton/Compositor#187. Each section header has a reset button, dimmed while the section is at its defaults. A reset puts the section back to its defaults, including choices and midpoints. A section whose eye was off shows again, so the next change isn't made unseen.
  • Ports Camera Raw presets: save the panel's settings under a name robbietilton/Compositor#198. A Presets menu at the top of the panel can save the current settings as a named preset, and apply, rename or delete saved ones.
    • Presets live in one JSON file in Application Support (the sandbox container), so the project format doesn't change.
    • A preset this build can't read stays in the file.
    • An unreadable file is moved aside to .bak before it's replaced.
    • Missing fields take their defaults.
    • Geometry guide lines aren't saved in presets.
  • The settings types become Codable. Two hunks needed reduced context because the fork's Camera Raw files import CPixels.

TASK-23: custom crop ratios, and the crop buttons next to the ratio picker

TASK-25: History panel

  • Reimplements only the History panel from closed Add Photoshop-style Quick Selection, Magnetic Lasso, and History robbietilton/Compositor#146. Its Quick Selection, Magnetic Lasso and wand changes are left out.
  • DocumentHistory gains stepNames, position and jump(to:). A jump moves entries between the undo and redo stacks in one go, trims once, and restores the document once instead of replaying every Undo.
  • A Layers | History switch above the side panel shows the panel, and the choice is remembered.
    • Rows list the steps by name. The first row is the state before the oldest step kept.
    • Clicking a row goes back or forward to that step. The steps after it stay, dimmed, for Redo, and a new edit drops them, as in Photoshop.
    • Renaming a layer or editing an adjustment switches back to Layers, since both happen there.

Testing

Only the relevant suites, as one final run:

swift build --disable-keychain
swift test --disable-keychain --filter 'KeyboardShortcutTests|LastFilterTests|NewFromClipboardTests|CameraRawResetTests|CameraRawPresetTests|CropRatioTests|CropTests|HistoryTests'

Result: 62 tests in 8 suites passed. After that run, two tests were added and checked with --filter 'CropRatioTests|KeyboardShortcutTests' (18 tests passed): the undecodable-shortcut test and the hosted crop bar checking Custom…. make dev builds, and the Dev app launches with every menu shortcut resolving.

Not run: SelectionClipboardTests, because it writes the real general pasteboard. Paste's change is only moving its image reading into EditorSession.pasteboardImage.

Screenshots

The Keyboard Shortcuts window, Camera Raw panel and crop bar can't be reached from launch, so they were rendered offscreen in throwaway test runs (NSHostingView + cacheDisplay). "Before" images come from a throwaway worktree of main (caf0627). The window captures come from the Dev app.

Keyboard Shortcuts, top of the list. Before / after: clear buttons, plus the New from Clipboard and Last Filter entries.

Keyboard Shortcuts, scrolled to the end of Menus. Before / after: the new More Menu Commands section.

Camera Raw panel, with Exposure, Contrast and Vibrance edited on a two-color test image. Before / after: the Presets menu, and a reset button in each header (dimmed for Color Grading, which is at its defaults).

Crop bar on a 300×200 image. Before:

Crop bar before

After, with the buttons next to the ratio picker:

Crop bar after

After, with a typed 9:20 chosen:

Crop bar with a custom ratio

The Custom… form (after):

Side panel, after five edits and one Undo, rendered offscreen. Before (Layers only) / after (History selected; the undone step is dimmed):

Dev app with a 1200×800 fixture PNG opened. Before:

Window before

After, with Layers selected:

Window after

After, with History selected:

Window with History

New from Clipboard has no screenshot: reaching it needs an image on the real clipboard.

Manual checks still needed

  • ⌘F runs Last Filter in the real menu bar, and nothing else takes ⌘F. The test host's menu bar isn't the app's.
  • A key assigned under More Menu Commands works from the menu bar, and a cleared key stops working.
  • File › New from Clipboard greys out and re-enables as the clipboard changes (copy text, then a screenshot), and ⌥⌘N opens the screenshot.
  • An image file copied in Finder opens via New from Clipboard in the signed, sandboxed build. The tests ran unsandboxed; the pasteboard does hand out sandbox extensions for file URLs.
  • Whether macOS shows its paste-permission alert the first time New from Clipboard reads another app's clipboard.
  • The Camera Raw Presets alerts (name, replace, delete) inside the floating panel.
  • Crop › Custom… opens its popover from the picker; typing a ratio and pressing Return adds it; it's listed again after a relaunch.
  • Clicking History rows in the running app, and the panel switching back to Layers for Rename Layer… and Edit Adjustment….

Expected overlaps with other PRs

CompositorApp.swift (menu items), UI/KeyboardShortcuts.swift (robbietilton#186 reworks the registry: new menu items without a default key need an entry in ShortcutDefinition.assignableMenuCommands and .assignableShortcut("Menu › Item"); ones with a default key need an entry in ShortcutDefinition.all, or a Debug assertion fires), ContentView.swift (one line for the side panel, one for the filter panel), Document/Filters.swift, Document/EditorSession.swift, README.md.


Upstream (robbietilton/Compositor):

itsjavi and others added 12 commits October 7, 2026 23:18
Only commands that shipped with a key could be remapped in Edit › Keyboard
Shortcuts, a saved key couldn't be removed, and one saved entry that no longer
fit threw away every other override. The Delete-key menu entries were also
looked up as U+0008, so changing Fill or Content-Aware Fill did nothing.

Ported from upstream: a More Menu Commands section lists every menu command
without a default key by its "Menu › Item" title, each row gets a clear
button, saved overrides load one at a time so a bad one drops alone, and
Delete is recorded as U+007F. Also covers the commands the port left out
(Check for Updates, Hide Compositor, Show All, Open Recent › Clear Menu), and
names the ⌥⌘H entry Hide Others, which is what that key does.

Ports robbietilton#186

Co-authored-by: Fvzion <jairocampos7@outlook.com>
Running the same filter twice meant reopening its panel and pressing OK again.
As in Photoshop, ⌘F now applies the last Filter-menu filter again with the
settings it was applied with, without the panel, as one undo step. A filter
with nothing to apply closes without leaving an invisible edit open.

The upstream test that read ⌘F from the app's real menu bar can't run under
the test host, whose menu bar isn't the app's; a test of the shortcut entry
replaces it, and the menu bar stays a manual check.

Ports robbietilton#192

Co-authored-by: Rezaul Arif <rezaulkarimarif.ctG@gmail.com>
Starting a project from a screenshot or a copied image took New Canvas,
Return and Paste. New from Clipboard opens a project the size of the copied
image, with it as the only layer, in one undo step; pixels copied in an open
project come first, as Paste takes them. Image files copied in Finder open as
dropping them on the Dock icon does, and a copied file that isn't an image
doesn't count, though Finder copies its icon as image data.

The shortcut is ⌥⌘N rather than upstream's ⇧⌘V, which Photoshop uses for
Paste in Place. The menu item is disabled without an image on the clipboard;
nothing announces clipboard changes, so the app checks the change count each
second while it's active and then looks only at the kinds of data and the
copied files' types, which doesn't make macOS ask for clipboard access. The
tests use pasteboards of their own, never the person's clipboard.

Ports robbietilton#201 and the handling of
files copied in Finder from robbietilton#176

Co-authored-by: Timo Grevers <timo@omniscale.nl>
Co-authored-by: oasido <asido.ofek@gmail.com>
Camera Raw could only be reset as a whole, and a look had to be rebuilt by
hand for every photo. Each section header now has a reset button that puts
that section back to its defaults, choices and midpoints included, and shows
it again if its eye was off, so the next change isn't made unseen.

A Presets menu at the top of the panel saves the settings under a name and
applies, renames or deletes saved looks. Presets live in one JSON file in
Application Support, not in projects, so the project format doesn't change.
A preset this build can't read stays in the file, an unreadable file is moved
aside to .bak before it is replaced, and missing fields take their defaults.
Geometry guide lines stay out of presets: they belong to the image they were
drawn on. The settings types become Codable for this.

Ports robbietilton#187 and
robbietilton#198

Co-authored-by: Fvzion <jairocampos7@outlook.com>
The crop ratio picker offered a fixed list, so 9:20 or 2.39:1 couldn't be
cropped to. Its new Custom… entry takes a width and a height; the ratio is
chosen, applied to the frame, and listed after the built-in ones from then
on, newest first and across tabs and launches, like the tool's switches.

Cancel and Apply Crop sat at the far end of the bar, away from the ratio they
act with; they now follow the ratio picker, with the crop size after them so
its changing width never moves them.

Requested upstream in robbietilton#165
and robbietilton#202
Undo steps could only be walked one at a time. A History panel, switched to
from above the Layers panel, lists every step by name, oldest first, after the
state before the oldest step kept. Clicking one goes back or forward to it in
one move, restoring the document once rather than step by step; the steps
after it stay, dimmed, for Redo, and a new edit drops them, as in Photoshop.
Renaming a layer or editing an adjustment brings the Layers panel back, since
both happen there.

Reimplements the History panel from the closed
robbietilton#146 on the existing
DocumentHistory, without that PR's selection tools.

Co-authored-by: vfxbro <faqyouman@gmail.com>
The shortcut loader dropped a saved override the rules now refuse on its own,
but a value that didn't decode at all (damaged, or edited by hand) still failed
the whole dictionary and reset every shortcut. Each entry now decodes on its
own, and one that can't be read is skipped like any other refused entry.
The ratio picker's Custom… entry asks for a ratio instead of being one; hosting the crop bar shows the choice going back to the ratio it had while the form opens.
…ages

Plans, notes, verification and final summaries for the shortcuts and workflow tasks, and the offscreen and Dev-app captures their notes and the PR show.
…shortcuts-and-workflow

Conflicts: README (custom crop ratios next to Image Rotation) and the Layer
menu (Show/Hide Layer keeps its assignable shortcut beside #2's Show/Hide All
Other Layers).

The menu commands #1 and #2 added had no way to take a key, which this PR
promises for every menu command, so they're now in More Menu Commands: File ›
Export As…, Edit › Stroke…, Image › Image Rotation's three items, and Layer ›
Apply Layer Mask, Show or Hide All Other Layers, Merge Visible, Flatten
Image, Align, Distribute and Layer Style. Unsharp Mask and High Pass were
already covered by the generated Filter list.
Conflict: ToolDefaults. This PR made isTesting visible to CustomCropRatios; #6 added the settings store beside it. Both kept.
unsavedWorkIsNeverReplacedWithoutAsking checked that the reload question was pending after a fixed 0.9 s sleep. On CI's slower runners the watcher's event sometimes came later, failing main (74ff4b6, and before at 5b0a588, a docs-only commit) and PR #3. It now waits for the question with the suite's eventually helper, as its sibling tests do.
@itsjavi
itsjavi merged commit f00ece1 into main Oct 8, 2026
1 check passed
@itsjavi
itsjavi deleted the feat/shortcuts-and-workflow branch October 8, 2026 01:24
itsjavi added a commit that referenced this pull request Oct 8, 2026
…eam-fixes

Conflict: SelectionClipboard's Paste. This PR admits a pasted image against
DocumentLimits before decoding it (pastedImage); #3 moved Paste onto a
shared pasteboardImage helper. Paste keeps the admission; New from Clipboard
keeps the helper, since it checks the limits itself before opening the image.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant