- Layers panel rows lay out their right-hand indicators first (lock, fx badge
with an effects disclosure triangle, link, optional blend-mode label) and
give the name the remaining width, cut with an ellipsis. The Studio kind
sub-label and the Effects sub-rows truncate the same way; a narrow panel
squeezes group indentation before the name. Rows leave room for the
overlay scrollbar. Drawn rects are recorded for tests (layer_row_ui).
- New view-state command layer.setEffectsExpanded (DocState::fx_collapsed,
no history step), driven by the fx triangle (Alt-click: all layers).
- Layers panel menu: Collapse All Groups (layer.setExpanded all=false).
Group open/closed state is document data (PSD lsct, .pcraft).
- ui.set {dockWidth} sets the right dock width (250..520) for agents and
snapshots.
- engine example designer_psd: ~140-layer designer PSD (4-deep groups, long,
CJK/Greek/accented names, fx, clipping, masks, vector masks, locks, links,
type/shape/fill/adjustment layers, hidden layers) for visual QA.
- Tests: name never intersects an indicator at 1x/1.5x/2x, 250/290/520 pt and
in every theme; fx triangle hides/shows effect rows without selecting;
Collapse All Groups from the panel menu, state survives PSD and .pcraft.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Layers panel: group disclosure triangles expand/collapse groups (#126)
Group rows get a disclosure triangle; click toggles the group, Alt-click sets
every group. Rows now list a group directly above its contents (the panel
used to reverse a pre-order walk, putting headers below their children) and
closed groups hide their contents (a kind filter still shows them).
The state is Group::expanded (PSD section divider open/closed folder,
.pcraft), toggled by the new layer.setExpanded command as a view change:
no history step, a clean document stays clean. document.inspect reports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Shortcuts: every displayed shortcut fires, from wherever focus is (#127)
- The menus show the catalogue's Photoshop shortcuts, but only command and
shell shortcuts were bound: Image Size (Cmd+Alt+I), Canvas Size
(Cmd+Alt+C), Feather (Shift+F6), Layer > Quick Export / Export As,
Window > Info (F8) and Actions (Alt+F9) showed a key that did nothing.
The binding table (new shortcut_dispatch module) now merges the
catalogue's shortcuts for live items, one owner per key.
- Any focused widget swallowed every shortcut (egui_wants_keyboard_input):
after clicking into a value field (Layers Opacity/Fill, options bar
Opacity/Flow/Smoothing), the Curves graph, or Tab, nothing fired until
the canvas was clicked. Now a focused text field keeps typing and its
editing keys (select all, clipboard, undo, navigation) while Cmd
shortcuts and F-keys still fire, as in Photoshop; a focused non-text
widget keeps only its navigation keys.
- A shortcut whose command is disabled says why on the status bar.
- Layers panel rows carry their layer name for accessibility (and tests).
Tests press every displayed menu / shell shortcut and every tool letter in
a whole-app kittest harness with a layered document (group, type layer,
selection) from four focus places, asserting each dispatches its item or
the item is greyed with a reason; plus real runs of the common ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI tests: scroll Layers rows into view under the fixed-height dock
The dock (#139) gives the Layers panel a fixed height that scrolls, so a
group's triangle or a row can sit below the fold. Tests now scroll the
node into view (accesskit ScrollIntoView) and click its real rect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Panel group heights no longer follow their content (#88): switching between a
pixel and an adjustment layer used to grow Properties and squeeze Layers.
- New `dock` module: `DockLayout` (order, heights, collapsed) in `UiState::dock`
(serde default, settable with `ui.set {dock}`). The last expanded group fills
the column; content taller than its group scrolls inside it.
- Drag the gap between groups to resize (min heights, clamped to the window);
double-click a tab or use the panel menu (hamburger, now live) to collapse,
move a group up/down, or close it; drag a tab strip to reorder (blocked by
Lock Workspace).
- Layout saved with New Workspace, reset by Reset Workspace (default heights,
order and tabs), and remembered across launches in `prefs.panelLayout` while
Remember Workspace Changes is on.
- #129: the icon rail toggled panel visibility, so a stray click made a panel
vanish. It now shows / expands / collapses a docked group and never hides it.
Window > <panel> and Window > Color/Gradients/... expand a collapsed group
instead of hiding it, so the menu always brings a panel back.
Tests: kittests for stable rects across layer kinds (Pro and Studio), splitter
drag + UI-state round trip + prefs persistence, reset/new workspace, double-click
collapse and strip-drag reorder (and lock), tiny windows, rail/Window menu
recovery, and a click-around test over layers, canvas, toolbar and options bar.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Dragging inside an existing crop frame now moves it (Photoshop Classic Mode) instead of
starting a new one; edges and corners resize it (Shift keeps the aspect ratio, Alt resizes
about the centre, an options-bar ratio preset always holds); Space while drawing a new frame
moves it like the marquee tools in Photoshop. Cursors follow the hit (resize arrows, move,
crosshair), the overlay gains edge handles, and a moved frame snaps its edges.
New module crop_ui.rs; small hooks in canvas.rs, snap_ui.rs, shortcuts.rs, control.rs
(ui.pointer `space` flag), panels.rs (hint) and lib.rs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
User and imported .abr brush presets now survive a restart. The new
photocraft_engine::preset_store keeps one JSON file per preset group plus
content-addressed, deflated tip bitmaps (8-bit when the tip came from 8-bit
data) in <config dir>/Presets, and an index with group order and deleted
built-ins. Built-ins are never written; writes are atomic and bounded;
corrupt or oversized files are skipped with a warning and left on disk.
The desktop app loads the store on a background thread and attaches it when
it arrives; sessions without a store (CLI, MCP, web, tests) stay hermetic.
Brush preset mutations bump ToolState::presets_rev and the store syncs after
the command, rewriting only groups whose content changed.
Fixes#78
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rebased onto main (#58/#61/#63/#66/#70/#74) by the maintainers: automation
open/save return and surface import/export warnings (app.open/app.save
reply with `warnings`), automation opens route .abr/.grd to the preset
libraries with script events suppressed, interactive opens keep open_file's
path/recent/notice handling.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* psd: .abr brush (v1/v2/v6+) and .grd gradient (v5) parsers and writers
Clean-room from public format descriptions: legacy computed/sampled brushes,
8BIM-sectioned samp/patt/desc files, 8/16-bit raw and PackBits tips. Bounded
(MAX_EDGE, MAX_TOTAL_BYTES), damaged brushes are skipped with warnings, hostile
input returns Err. Adds an abr fuzz target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* paint: 36 grouped built-in brush presets and new procedural tips
General / Dry Media / Wet Media / Special Effects groups (pressure size/opacity
variants, airbrush, pencils, charcoal, pastel, crayon, rake, watercolour, ink,
oil, foliage, grass, stipple, sparkle, halftone…). Leaf, grass, charcoal,
sponge, star and rake tips are generated in code. BrushPreset gains a group.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* io: map .abr brushes onto BrushSettings
Tip shape, shape dynamics, scattering, texture (embedded patt patterns),
dual brush, colour dynamics, transfer, pose, noise, wet edges, build-up and
captured tool options; unmapped settings are reported as warnings. Includes
bench_abr (100-brush import, 5000-dab stroke).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* engine: brush.presets.importAbr and gradient.presets.importGrd
Path or base64 data, group per file (re-import replaces), unique names next to
the built-ins; graceful-failure tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui: real stylus pressure/tilt, grouped Brushes panel, Import Brushes
Pen force from egui Touch events (Windows WM_POINTER via winit), Pointer Events
pressure/tilt/twist on the web, tilt via ui.pointer; mouse stays 1.0. .abr/.grd
open from File > Open, drag-and-drop, Brushes panel and Preset Manager. Preset
previews keyed on a cheap signature instead of the full brush JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* File open/save: set the path everywhere, Open Recent, show errors and import/export warnings
File › Open, drag-and-drop onto the window and launch paths now share
PhotocraftApp::open_file: the document is named after the file (not the full
path), keeps its path so File › Save writes back, and goes to Open Recent.
Failures are shown as errors (status bar + a non-blocking notice).
Import/export warnings from photocraft-io are no longer thrown away: they show
in the status bar and in a dismissable notice (native and web), and the
control protocol returns them from app.open/app.save/file.open/file.save/
file.saveAs/file.saveACopy. .pcraft is in the Open and Save filters, and
File › Save writes .pcraft documents in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* macOS: open files from Finder, Open With and the Dock (Apple events)
Finder double-click, Open With, drops on the Dock icon and open -a deliver
files as kAEOpenDocuments Apple events, not argv, and winit 0.30 ignores them.
Register an NSAppleEventManager handler through the small fmv-macos-events
crate (MIT OR Apache-2.0, 168 lines, audited, pinned =0.1.0; it keeps the
Objective-C unsafe out of our forbid(unsafe_code) workspace) before the event
loop starts, so both the launching event and later ones reach the UI as
OsEvent::Open; the window is focused. The quit Apple event goes through the
normal close path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
crates/automation/src/rpc.rs: a headless session behind the control
protocol's envelope (engine.execute, doc.*, session.list, batch), on
stdio or loopback TCP. MCP gains command_batch (headless and bridge).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>