* Clone Stamp and Healing Brush clone from a source set in another open document
Alt-clicking a clone source in one document and painting in another sampled the
painted document at the source point: a clone source slot held a point and a layer
but not the document. Slots now record the document (and its layer) they were set
in, and painting elsewhere samples that document, as in Photoshop.
- Engine: CloneSource gains `document` (serde default, so older slots read as
"the document being painted"). Clone Stamp, Healing Brush, the live stroke, the
Pattern Preview wrap and the brush-tip preview read the source document. Pixels
are converted to the painted document's colour mode and depth like Edit > Paste
(no ICC conversion, same as Paste). Current Layer / Current & Below use the
layer the source was set on (that document's active layer if it was deleted);
All Layers uses its composite.
- A source whose document was closed is an error asking for a new source, and
stores no aligned anchor, instead of silently sampling the wrong document.
- cloneSource.set takes an optional `"document": index` (with `source`);
cloneSource.list and set report each slot's document index.
- UI: the Clone Source panel names the source document (or says it was closed),
the source crosshair isn't drawn over another document, and the brush preview
cache includes the source document's revision.
Tests: engine tests for 8/16/32-bit with and without wrap, colour-mode conversion
(Gray > RGB16, RGB > CMYK), sampled layer, Healing Brush, live stroke vs commit,
preview, closed/invalid documents and cloneSource.set/list; a UI test that
Alt-clicks in one document and paints in another.
* Clone test: the aligned stroke past the smaller source document's edge leaves the destination unchanged
Review follow-up: with "aligned": false the stroke sampled inside the source, so it didn't test the edge its comment named, and nothing was asserted.
* Clone Stamp / Healing Brush: an empty Clone Source slot refuses to paint
clone_params fell back to ui.clone_source / ui.clone_offset when the active
slot was empty. Those coordinates carry no document, so after Alt-clicking in
one document and picking an empty slot in another, the stroke read the first
document's coordinates from the second one's pixels.
* fix: reject stale cross-document clone previews
* Clone Source: a copy opened after the source document closed isn't its source
A native copy keeps its document's ID, and a reopened document may keep its ID. A slot set in
a document that was then closed resolved that ID again once a copy was open, and painted from
the copy instead of reporting the closed source (CodeRabbit, clone_source.rs).
Session::close now marks the slots set in the closing document closed (CloneSources::forget_doc,
next to the other per-feature forget_doc calls). A closed slot refuses the stroke and its live
preview with the closed-source error, shows "Source: document closed" in the panel, reports no
`document` in cloneSource.list/set, and draws no overlay or crosshair, whatever document has the
ID now. Setting a new source clears it.
* Object Selection: New, Add, Subtract and Intersect mode buttons (#12853)
The options bar shows the four mode buttons before Object Finder. Clicks and
rectangle drags combine in the bar's mode, with Shift adding, Alt subtracting
and Shift+Alt intersecting (rectangle drags used to subtract with Shift+Alt).
In New mode a click inside the selection deselects and a drag inside moves
the outline, as the marquees do.
* Object Selection: Cmd stays the Move tool inside the selection; update tests for New-mode Deselect
* Object Selection mode tests: a Cmd-drag floats the pixels and lands as Move on the drop
* Object Selection: keep Object Finder off's Quick Selection dab and rectangle
A press inside the selection drags the outline only with Object Finder on;
with it off the click is still a Quick Selection dab and the drag a
rectangle. The dab subtracts with Alt again, including Shift+Alt.
* Object Selection: keep the Indexed/Bitmap alert for a press inside the selection
The New-mode outline gesture skipped finish_object_selection, so a click
deselected and a drag moved the outline without the tool's alert (#12867).
* Layers panel: off-screen group rows keep only their disclosure widget (#13117)
A group row scrolled out of view painted its whole row every frame. It now
registers only its disclosure triangle (hit area, click, tooltip, accessibility
label) and returns, like any other off-screen row (#125).
* Add layers_panel_bench: Layers panel frame time with many groups (#13117)
Idle frames of the app in a CPU harness on a document of N collapsed groups
(or plain layers, or expanded groups), with the panel scrolled to the top.
* Layers panel: keep a row whose eye-column menu is open (#13127 review)
The eye column's colour menu is the eye response's own popup, which
resp.context_menu_opened() doesn't see. A group row with it open now stays
whole when scrolled out of view, as egui closes a popup a frame doesn't show.
Also documents the bench example's helpers.
* Layers panel test: an off-screen group row adds no text shapes (#13127 review)
* Layers panel: skip off-screen Smart Filter rows (#13118)
Filter rows scrolled out of the list keep their place but no longer paint,
register widgets or take drops, as effect rows already do. A filter row takes
a reorder drop only where the list shows it, as masks do since #10485, so a
filter released over the panel header no longer lands on a row scrolled
under it. A selected filter stays selected while scrolled away.
* Re-run CI
* Move: frame and snap a dragged group (#13418)
A group dragged with the Move tool showed only the arrow: the moving box
came from snap::layer_rect, which skips groups, so a selected group got no
Move gesture, no box, no snapping and no Smart Guides.
Measure what the move carries instead: the move targets, a group as the
layers in it (whole, even with a selection), an artboard as its board.
Keep the targets and everything inside them out of the snap targets, so a
group doesn't snap to its own layers' starting edges.
* Rename the group drag test helper so it doesn't clash with drag()
* Move: keep linked layers as snap targets while selected pixels move
Camera Raw's window is an immediate viewport: it takes input and redraws
only while the main window draws a frame. Opened over a full-screen main
window, the new window covers it (Hyprland hands it the full screen, or
the maximized state), the compositor sends the hidden main window no more
frame callbacks, and the dialog freezes: sliders don't move and the app
looks hung until the main window is visible again.
While the main window is full screen, or maximized on Wayland, the dialog
is now drawn over the main window, as the web build already does.
Elsewhere a maximized window keeps Camera Raw in its own window.
Tested on Hyprland 0.56 (Omarchy): Super+F and Super+Alt+F both froze
the dialog before (a control-channel slider change timed out after 60 s
or took 48 s) and respond normally now.
* Use craft-psd from craft-libs; remove crates/psd
* Regenerate the scorecard (28 crate roots)
* Take craft-psd at craft-libs 55d120f2
* Regenerate lockfile for current shared format crates
* Pin shared format crates to current craft-libs main
* Match brush import cap to shared decoder budget
* xtask: update layer fixtures after PSD extraction
* Use craft-raster-codecs, craft-dpx, craft-tga, craft-gif and craft-exr for the format code in crates/codecs
* Regenerate lockfile for current shared format crates
* Pin shared format crates to current craft-libs main
* Fix codec module imports for clippy
The shape tools' options bar gets Path operations, Path alignment and
Path arrangement after the shape options. The operation is the shape
tools' own setting (New Layer by default, Exclude in Path mode); any
other operation adds the next drag to the active shape layer with
shape.create addTo and op. Reset Tool restores New Layer. Alignment
and arrangement entries stay greyed until the engine can move path
components.