Commit Graph

1131 Commits

Author SHA1 Message Date
Brandon Thomas 23ca3b2360 Merge origin/main 2026-10-10 03:59:59 -04:00
Alex 265f10b91d
Fix SVG selection and import detection (#2224)
* Fix SVG import detection and configurable group depth

* Fix malformed SVG error routing and formatting

* Refresh scorecard for SVG depth preference

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:58:08 -04:00
Sir Starch 1b6150e3fc
Layer › Rasterize acts on every selected layer; the Layers menu offers Rasterize Layers (#2243) (#2253)
Rasterize › Layer / Type / Shape / Fill Content / Smart Object took only the active
layer, so right-clicking inside a multi-layer selection converted one layer. Without a
`layer` param they now rasterize every selected layer they apply to, in one history step.
An explicit `layer` (the "Rasterize?" paint prompt, scripts) still targets exactly one.

Layers panel: a multi-layer selection gets one "Rasterize Layers" item (greyed only when
nothing selected can be rasterized, judged by the whole selection, not the clicked layer).
Edit Contents and Convert to Layers act on one layer, so they are not offered there.
Adobe's documentation: select "the layer or layers you'd like to rasterize".

Tests: engine integration (mixed type/shape/fill/smart selection, one undo step, per-kind
commands, explicit layer, nothing to rasterize, bad targets) and ui-egui (menu entries,
enablement, right-click → Rasterize Layers end to end). The new behaviour tests fail on
the old code.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:55:43 -04:00
Brandon Thomas b63a0a2fdf Merge origin/main 2026-10-10 03:51:21 -04:00
Brandon Thomas 0fc26926b6 Regenerate docs/scorecard.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:51:08 -04:00
Tropical 071fe4590b
fix(ui): mode buttons respect two-column toolbar (#2241)
* fix: mode buttons respect two-column toolbar

* fix: draw menu indicator on window mode button

* fix: syntax mistake in `paint_toolbar_menu_indicator`

* fix: pass tokens to paint_toolbar_menu_indicator

* refactor: rename paint_toolbar_menu_indicator to paint_toolbar_flyout_indicator

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:49:22 -04:00
Brandon Thomas fc42f81f42
Web: leave the JPEG XL DNG decoder out of the wasm (25.1 MB -> 24.3 MB) (#2233)
* Web: leave the JPEG XL DNG decoder out of the wasm (0.88 MB, 23.1 MiB now)

The 0.6.0 web build went over packaging/web/package.sh's 24 MiB gate (26,980,503 bytes on
782bcb67). Today's landings added 1.34 MB; the biggest single item was jxl-oxide (#2047, JPEG
XL-compressed DNG): ~0.6 MB of jxl_* code plus brotli-decompressor and tables, 0.88 MB after
wasm-opt. 0c72d954 brought the wasm back to 25,144,413 bytes (21 KB under the gate) by deflating
catalogs and fonts; this takes it to 24,262,088 bytes (23.14 MiB, 904 KB of headroom).

jxl-oxide is now a native-only dependency of photocraft-raw. On wasm32 a JPEG XL DNG segment
returns RawError::Unsupported ("can't be opened in the browser version of PhotoCraft; open it
in the desktop app"), like the other cfg-gated web features; every other DNG compression and
raw format still opens in the browser. Native builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* xtask i18n-coverage: read the deflated catalog! registry entries

0c72d954 changed the LANGUAGES registry from include_str!("xx.tsv") to catalog!("xx"), so
`cargo xtask i18n-coverage` failed with "language `ja` has no include_str! catalog". Parse the
new form (the macro argument is the catalog's file stem).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:40:35 -04:00
Nigel Ihza Firdausy 91a2b58725
fix(ui): remove the gap beneath Spectrum document tabs (#2265)
* fix(ui): remove Spectrum document tab-to-canvas spacing (#2188)

* scrollbars test: compare the centred view within float error

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:40:16 -04:00
texjer 6a39da98c8
Move tool on a locked layer: Photoshop's message (#2249)
* Move tool on a locked layer: Photoshop's message

Dragging with the Move tool (or Cmd with another tool) on a layer it can't
move showed nothing: the Background or a position-locked layer with no
selection, or a locked layer whose selected pixels can't float. Photoshop
puts up "Could not use the move tool because the layer is locked." when
the drag starts (a plain click says nothing); move_lock.rs does the same
with the Error dialog, translated in every catalogue. The float lock rule
moves to float_cmds::locked_for_float so the UI can ask before the press
goes anywhere.

Also in Dutch and Ukrainian. The Cmd-with-a-painting-tool case lands with
the Cmd-as-Move change, whichever merges second.

* Fix merge: close the lock-message test

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:39:40 -04:00
Brandon Thomas cbc50329e2 Merge origin/main 2026-10-10 03:16:07 -04:00
Sir Starch 1f84f19caf
Hand tool: an open hand and a fist as the pointer on Windows, and the fist while panning everywhere (#2196) (#2221)
* Hand tool: an open hand and a fist as the pointer on Windows, and the fist while panning everywhere (#2196)

Windows: winit maps CursorIcon::Grab and Grabbing to the four-arrow move cursor, so the Hand
tool never showed a hand. tool_cursor::hand now hands the OS an outlined bitmap hand (open) and
fist (dragging), drawn from signed distance fields like the brush tips, through the same
set_cursor_image path. macOS and the Linux themes keep their own grab cursors.

All platforms: the tool-cursor block of canvas_view is inside `else if primary`, which the Hand's
own drag branch skips, so Grabbing was dead code and the pointer was a plain arrow while panning
(Hand tool, Space and the middle button). The drag branch now sets the fist.

Also used by the Hand pointer under a dialog and the Camera Raw preview's pan.

* tool_cursor tests: the Hand's pointer on Windows is now a bitmap (#2196)

brush_hover_uses_an_os_bitmap_that_survives_motion_but_not_tool_or_panel_changes pinned the old
behaviour (no bitmap, CursorIcon::Grab once the Hand is selected), which is the four-arrow move
cursor on Windows. It now expects the open hand's bitmap.
2026-10-10 03:15:00 -04:00
texjer b37bff987c
Cmd held is the Move tool with the painting tools (Cmd+Alt duplicates) (#2248)
Photoshop switches to the Move tool while Cmd (Ctrl off the Mac) is held
with every tool but the Hand, Rotate View, Zoom, Crop, Slice, Path Selection,
shape, Pen and Type tools. #896 did this for the selection tools per press; this
does it for the rest (Brush, Eraser, Gradient, Clone Stamp, Eyedropper, ...).

hold_keys gains Temporary::Move (a modifier, not a rebindable key); the
canvas sets PhotocraftApp::tool_override for the frame and the tool state
machine, the Move tool's selection check (move_ui::moves_selected_pixels)
and the tool modules that gate on the tool read active_tool(). The held-tool
latch now lasts through the release frame, otherwise the Move's Up went to
the selected tool. So Cmd-drag with the Brush moves the layer, Cmd+Alt-drag
duplicates it first (move_mods), Cmd-drag with a selection floats the
selected pixels, and Cmd-click picks the layer under the pointer.

Off the Mac, Cmd+Alt is Ctrl+Alt, which the brush tools keep for the
on-canvas resize (brush_resize, also Alt+right-drag there): with a brush
tool Ctrl+Alt-drag resizes, with the other tools (Gradient, Eyedropper...) it
duplicates. Tests pin both layouts on any host. docs/control-protocol.md
describes `command` on ui.pointer events.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:44:27 -04:00
Harrison O'Connor-Hoover 935c94b5d3
Add pixel mask density and feather controls to Properties (#2240) 2026-10-10 02:43:34 -04:00
Krzysztof Cieślik a0c18f5f75
Tools: double-clicking a layer mask launches Select and Mask with preference (#204) (#2161)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:35:51 -04:00
texjer 95006995f6
Selection cursors: Photoshop's badges for cutting, copying and dragging the selected pixels (#2250)
Over a selection the cursor now says what a press does, as in Photoshop:
the move cursor where a marquee press moves the outline (#1428, unchanged),
the arrow with scissors where Cmd (or the Move tool) cuts and moves the
selected pixels, the arrow with a second arrow where Alt lifts a copy, a
hollow arrowhead while they are dragged, and a plain arrow over the floating
piece. canvas::selection_cursor decides, drawn as icon badges beside the OS
arrow (icons::cursor_badge), white with a dark outline.
2026-10-10 02:24:22 -04:00
Harrison O'Connor-Hoover 97f37feaec
Default layered flat-image saves to PSD (#2234)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:23:57 -04:00
David Mann eb20f83654
Masks: target a new layer mask and keep separate colour pairs for mask and pixels (#2166) (#2200)
* Masks: target a new layer mask and keep separate colour pairs for mask and pixels (#2166)

Photoshop keeps one foreground/background pair for layer masks and one for
layer pixels and swaps them when the edit target changes; adding a mask
targets it, with white/black. PhotoCraft never touched the colours when a
mask was targeted, and adding a mask left the pixels targeted.

- engine: ToolState::target_mask swaps in the other target's pair
  (mask pair starts at white/black, colours picked on either side are kept).
- ui-egui: sync_mask_targets applies the effective target (mask targeted,
  active layer has a mask, composite channel, no Quick Mask) after every
  command, every frame and every control request; the Layer Mask add
  commands target the new mask.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix merge: close mask colour-pair test before the deletion tests

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:20:48 -04:00
Nigel Ihza Firdausy 1e7c38b98f
Expose Clone Stamp opacity and flow and apply them to strokes (#2256)
Co-authored-by: tiya nur alfiyanti <tiyanuralfiyanti24@gmail.com>
2026-10-10 02:20:27 -04:00
Krzysztof Cieślik b4044a5c0c
Workspace: wire open_documents_as_tabs preference (#204) (#2177)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:18:05 -04:00
Harrison O'Connor-Hoover 2db1b26d9d
Preserve quoted multiline variable CSV records (#2242) 2026-10-10 02:17:09 -04:00
David Mann 26d40dbd0f
TGA: save the document's alpha channel as 32-bit alpha (#2124) (#2157)
Flat exports ignored the document's alpha channels, so a Targa saved
from a document with an "Alpha 1" channel had no alpha. Photoshop 25.1
(checked via scripting) writes a 32-bit Targa's alpha from the
document's alpha channel when there is exactly one (spot channels
skipped), composites layer transparency over white, writes an opaque
alpha when there are several channels, and never adds alpha to
grayscale Targas. TGA export now does the same; 16-bit documents are
written as 8-bit Targa with the channel's values.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:11:44 -04:00
Tanish Manku f2836b0475
Soft brushes paint soft strokes and thin lines stay even: a Gaussian-like tip and area coverage for tiny tips (#2167)
Brush phase 1 (owner-approved option A of the brush audit):

- Soft tips fall off as (1 - t²)⁴ from the hard core to the edge, a Gaussian-like curve that
  tracks exp(-4.6 t²) but is exact on every platform and reaches 0 at the edge with a flat tangent.
  The old smoothstep kept so much of the tip near full strength that overlapping dabs (Flow builds
  up, as in Photoshop) turned a soft stroke's edge to half the tip's width. It now keeps about two
  thirds of it, at the default 10 % spacing, with no ripple along the stroke.
- Computed tips under 3 px radius are rasterised by covered area (a 4×4 grid per pixel, each
  sample anti-aliased over its quarter pixel) instead of at pixel centres: a 1 px dab's total
  coverage no longer swings 1.00–1.17 with its sub-pixel position, so thin lines don't bead.
- The default spacing stays 10 %. Photoshop's documented 25 % beads soft strokes with either
  curve; it waits for Photoshop-authored reference strokes.
2026-10-10 02:10:39 -04:00
kotulsky 26cc6ed6bc
Confirm layer deletion only from the Layers trash button (#2197)
* fix(ui): confirm deletion only from the Layers trash button

* Trash confirmation: Alt-click deletes without asking (Photoshop); drop docs page and screenshot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:10:24 -04:00
Duke242 2c15f13f02
scorecard: Marquee modifiers and brush resize drag are done (TOOL-214-2, TOOL-213-10) (#2261)
* scorecard: Marquee Shift, Alt and size readout are done (TOOL-214-2)

The row still said canvas.rs passes false for Shift (#208), but #208 is
fixed: marquee_corners applies Shift (square or circle) and Alt (from the
centre), the W x H readout follows the drag, and marquee_tests.rs covers
all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* scorecard: on-canvas brush resize drag is done (TOOL-213-10)

The row said only [ ] resize the brush, but brush_resize.rs (#297, #370)
implements Photoshop's drag: Alt+right-drag, or Control+Option-drag on
macOS, sets diameter and hardness with a circle and readout, and is
covered by nine tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:04:18 -04:00
Luniiya 9c79871c6b
Preferences: the dialog grows with the window (#2155)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:03:37 -04:00
kotulsky 8b1bb0fd05
fix(engine): select neighbouring layer after deletion (#2189)
Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:01:55 -04:00
David Mann 8a80430c40
Layers panel: press-drag-release picks a fill or adjustment layer (#2071) (#2172)
Photoshop's "Create new fill or adjustment layer" footer button opens its
menu on the mouse press, so one gesture can press it, drag onto an item and
release to choose it. PhotoCraft used egui's Popup::menu, which only opens on
the release (click), so choosing needed two clicks.

A small reusable press_menu helper (the menu bar's press-drag gesture from
#669/#775, for a single popup button) now drives the adjustment button and the
identical fx (layer style) footer button: the press opens the menu, the release
that ends the opening click keeps it open, releasing on an item chooses it, a
release on nothing chooses nothing, and a secondary click still opens it. The
gesture belongs to the popup whose button started it, so several press menus in
one frame don't end each other's gestures. The items dispatch the same
commands as before.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:57:43 -04:00
Tropical 4aa511bb05
fix(ui): avoid drawing panel borders twice (#2226)
* fix: don't draw panel borders twice

* fix: avoid drawing caption buttons over titlebar border

* fix: remove manually painted separator on statusbar

* titlebar test: caption buttons stop above the panel's separator line

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:57:01 -04:00
David Mann 44636cb6f2
Canvas: clip the Free Transform preview to the canvas (#2027) (#2201)
The transform overlay drew the moving pixels as a textured mesh with the
canvas painter, whose clip is the whole viewport, so a box moved or
scaled past the canvas edge showed its pixels over the pasteboard.
Photoshop never shows pixels outside the canvas; only the box and its
handles reach over the pasteboard.

Build the preview mesh (Free Transform/perspective grid and Warp) in
document space and clip each triangle to [0, 0, width, height] before
mapping it to the screen. Clipping in document space stays exact under a
rotated or flipped view; a mesh fully on the canvas is drawn unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:51:47 -04:00
Harrison O'Connor-Hoover 34382a65ca
Ignore hidden RGB when Magic Wand selects transparency (#2273) 2026-10-10 01:51:28 -04:00
David Mann bf1036a273
Layers panel: Opacity and Fill share one right-aligned column (#2085) (#2173)
In the pro theme the Fill row zeroed the horizontal item spacing for the
lock icons on the whole row, so the "Fill:" label touched its field while
"Opacity:" kept the normal gap and sat 8 pt further left. Scope the zero
spacing to the lock icons only, so both labels end the same gap before
fields that share one right edge, as in Photoshop. The narrow-panel check
that drops the "Lock:" text now counts the restored gap.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:51:26 -04:00
kmnkv1 effee8cdc3
i18n(ru): translate layer style effects (#2210)
Stroke was Мазок; effect names were untranslated. Names follow Photoshop's Russian UI.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:43:46 -04:00
kmnkv1 f10c83425c
fix(layer style): wrap Make/Reset Default buttons (#2209)
Translated labels (ru) are wider than the page column and overlapped the effect list.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:43:42 -04:00
Vikram Bhaskaran 39c97c9be0
Keep the selection when smart selection finds nothing (#2235)
* Keep the selection when smart selection finds nothing

Select Subject and Object Selection run heuristics that often miss on busy
photos. In Replace mode an empty result cleared the selection, so the Select
Subject button looked dead and even destroyed existing work. apply() now
preserves the selection and reports changed:false in every mode.

The UI also answers visibly instead of staying silent: empty smart-selection
results set an info status suggesting what to try next, and a click with
Object Selection falls back to a Quick Selection dab at the brush size
instead of doing nothing.

* Translate the smart-selection status messages; drop needless borrows (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:42:28 -04:00
David Mann 5e481cde02
Layers: mask thumbnails update after Invert and other whole-mask edits (#2117) (#2164)
Thumbnail and bounds caches key on surface_fingerprint, which hashed only
tile coordinates and Arc pointers. A fresh Reveal All mask has no tiles;
Image > Adjustments > Invert (and other whole-mask edits) only flips its
default pixel, so the fingerprint stayed the same and the Layers panel kept
the stale white thumbnail. The fingerprint now includes the default pixel.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:27:41 -04:00
Michał Kosior 183122f676
Fix hyperlink in credits panel to use crate::links::open instead of the unreliable ui.hyperlink_to (#2146) 2026-10-10 01:26:48 -04:00
optimalbytee a9267e199d
Fix layer mask toggle action labels (#2156)
Co-authored-by: optimalbytee <optimalbytee@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:13:06 -04:00
David Mann 9482e0c710
Smudge: use the Brush Settings spacing for retouch strokes (#2140) (#2171)
parse_brush, shared by Smudge, Blur, Sharpen, Dodge, Burn, Sponge, the
clone/healing tools and the History Brush, defaulted `spacing` to a fixed
25% instead of the session brush's spacing (Brush Settings > Brush Tip
Shape > Spacing). The UI never sends `spacing` for these tools, so the
Brush Settings value was ignored. Default to the session brush spacing,
as Photoshop does; an explicit `spacing` param still wins.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 01:10:12 -04:00
Benziza 634cfbf3c5
Preserve unchanged XMP translations in File Info (#2152)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 01:08:52 -04:00
Coy Geek f4bc463ee8
docs(adjust): correct Levels and Curves composition order (#2212)
Describe per-channel records before the master to match the compositor fixed in #1929. Clarify the CMYK black channel and the ignored Lab master.

Closes #975.
2026-10-10 00:59:39 -04:00
Sawizzle 6973fe4496
docs: register the MCP server from an installed release (#2185)
* docs: register the MCP server from an installed release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop the third-party link; the Windows path is the default install folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:59:34 -04:00
Brandon Thomas 0c72d95425 Web: store translation catalogs and bundled fonts deflated, unpacked on first use (wasm back under the 24 MiB gate)
The 0.6.0 web build was 26.9 MB, over packaging/web/package.sh's 24 MiB gate (Cloudflare's per-file
cap is 25 MiB, #198). The i18n .tsv catalogs (1.85 MB -> 0.64 MB) and the Inter/JetBrains Mono fonts
(1.52 MB -> 0.73 MB) are now deflated by build.rs and inflated on first use, so wasm is 25,141,080
bytes. No visible change. A catalog that fails to inflate falls back to English, a font that fails
falls back to egui's default font; neither panics. Headroom is only ~25 KB: the next size cut is
needed soon (fetching catalogs on demand would save another ~0.64 MB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:11:13 -04:00
Shayan Horbank e398cc80b8
Use shared Color Picker for Layer Style and text styles (#2015) (#2049)
* Use shared Color Picker for Layer Style colors (#2015)

* Use shared Color Picker for text style colors (#2015)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 22:03:51 -04:00
texjer a81eaa4187
Keyboard Shortcuts: import Photoshop .kys sets; first launch takes Photoshop's live set (#1163) (#1987)
* Edit › Keyboard Shortcuts: Import Photoshop Shortcuts… loads a .kys set (#1163)

Photoshop saves a shortcut set as .kys XML: <command name="Gaussian Blur...">
rows with one or more <shortcut>Control+Opt+Cmd+G</shortcut>, then the tool
keys. The dialog's new button (and File › Open or a drop of a .kys) parses it
with roxmltree, matches each row to our commands by menu label, and fills the
dialog's overrides, so OK applies the set the way hand-made changes are
(a key moved to a command is taken from its old owner) and Cancel discards it.

Rules, from Photoshop's own ambiguities: a label in several menus (Levels…
under Image › Adjustments and Layer › New Adjustment Layer; All under
Select, Edit › Purge and View › Show) goes to the command whose default is one
of the row's keys, else the one with a default, else the first in menu order.
A row's first key is kept unless one of its keys is already the command's
default (Zoom In is Cmd++ then Cmd+=). Scripts, Bridge, the frames commands
and the macOS application menu have no command here and are counted in the
message. Tool keys are not imported: the tool letters are fixed.

* First launch beside Photoshop imports its live shortcut set; the Open dialog lists .kys

Photoshop keeps the set in use as Keyboard Shortcuts.psp in its per-version
settings folder (macOS ~/Library/Preferences/Adobe Photoshop <v> Settings,
Windows %APPDATA%\Adobe\Adobe Photoshop <v>\Adobe Photoshop <v> Settings),
the same XML a saved .kys holds. The desktop app hands the newest version's
file to the UI (Services::photoshop_shortcuts); at the first launch that has
no shortcut overrides yet, kys_import::auto_import applies it through the
Keyboard Shortcuts dialog's own OK path and posts a notice naming the set and
the count. The preference dialogs.photoshopShortcuts records the source, so
the import happens once; someone who already changed a shortcut keeps theirs.
Reset All to Defaults undoes it.

The Open dialog's filter now lists .kys (it greyed them out), and dropping
Keyboard Shortcuts.psp imports it like a .kys.

* kys import: Ukrainian and Dutch strings; tests follow Cmd+K and Cmd+F as the defaults (#1900)

* kys import: user-facing strings name no other product (#1985)

The button is Import Shortcuts…, its tip names the .kys format, the notice
says "Keyboard shortcuts imported" and names the set, and a file that is not
a set is "not a keyboard shortcut set (.kys)". A set without a name is called
by its file name. Comments, identifiers and the XML root tag keep the format's
own name.

* rustfmt: format panels.rs and prefs_ui.rs

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:55:43 -04:00
ROSSA AI 5ed094a940
ui: Panels::rail and Panels::menu_bar let an embedding app hide the icon rail and the menu bar (#1960)
The right dock's icon rail and the title bar with the in-window menus were
always drawn. An app that embeds PhotoCraft's UI and offers a simpler view, or
draws its own bar, could hide every other panel through `ui.panels` but not
these two.

Two Panels flags, both on by default and with `#[serde(default = "yes")]`, so
saved settings and the current UI are unchanged. `ui.set` accepts them like
the other panel flags. The title bar's caption buttons, where PhotoCraft draws
its own, go with it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:47:12 -04:00
mroxso 16ad6e1079
Add font previews and keyboard cycling to font menus (#1746)
Co-authored-by: highperfocused <highperfocused@pm.me>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:43:59 -04:00
Max Murphy 995c895c33
Notices: auto-hide after a delay, with Interface preferences (#2022) (#2100)
* Notices: auto-hide after a delay, with Interface preferences (#2022)

Non-blocking notices no longer stay on screen until closed: each card
hides itself after Interface › Notification › Auto Hide Notifications'
delay (new, default 6 s), and the timer pauses while the pointer rests
on the stack until it moves away. Two new Interface preferences,
notificationAutoHide and notificationDurationSeconds, are read by
notices.rs; errors still land in the status bar.

The decision lives in the pure notices::expire helper, unit-tested
without an egui clock; Notice.shown_at round-trips through the control
channel with serde defaults. Labels translated across the complete
catalogues. Regenerated docs/scorecard.md.

* Notices: repaint on expiry, and a fresh delay when a long hover ends (#2022)

Addresses review of the auto-hide change:

- When the last notice expires, `show` had already painted it and, with
  nothing left, requested no further frame, so the card lingered until
  unrelated input. It now requests a repaint in the frame that drops
  anything, clearing the stack at once.
- A stationary hover over a settled UI refreshed `shown_at` only on
  frames, so hovering longer than the delay then leaving expired the
  notice instantly. `show` now tracks the pointer leaving the stack and
  restarts the timers on that frame, so hovering never counts as elapsed
  time.

Regression tests: expiring a notice requests a frame to clear it;
leaving a long hover gives a fresh delay.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:38:03 -04:00
Luniiya 8c3b6f8e5f
Appearance modes: sync with system, separate dark and light themes (#1981)
* Appearance modes: sync with system, separate dark and light themes

* Appearance button cycles Auto, Light, Dark

* Appearance: default stays Dark, follow the portal signal instead of polling, split out the Preferences resize

* Preferences: size both appearance cards for the five dark themes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:34:49 -04:00
Luniiya 782bcb67fa
Toolbar: Studio themes draw round colour chips with Switch Colors below (#2002)
* Toolbar: Studio themes draw large round colour chips with Switch Colors right below

* Toolbar: round chips keep Default Colors, follow Tokens::round_chips, lie side by side in two columns

* Theme: Solarized Dark and Adwaita use the Studio round colour chips

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:14:37 -04:00
Sivarajan cc584d9aff
Add Adwaita and Adwaita Dark themes (+ brush chip fix on light themes) (#1823)
* Brush preset chip: draw the tip preview in the icon colour on light themes

The options-bar chip always painted the round tip white, which nearly vanished on Studio
Light's chrome (246 gray) and disappears entirely on a white header bar. Dark themes keep
Photoshop's white tip; light themes use their icon token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add Adwaita and Adwaita Dark themes

Two opt-in themes that put the Studio layout in GNOME's libadwaita palette, so PhotoCraft
sits with the rest of a GNOME desktop: header bar, sidebar, card and view colours, the
GNOME blue accent (#3584e4), destructive and warning colours, 6 / 9 / 12 px radii, and a
neutral (not red) close button as in GNOME's title bars. The default theme is unchanged.

Wired through ThemeKind, the interface.theme preference (adwaita, adwaitaDark), Window ›
Theme and the control channel, with menu and preference labels in every complete-menus
language ("Adwaita" kept as the proper name). A test holds both themes to WCAG contrast:
7:1 for text and 4.5:1 for dim text on every surface that carries labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: translate the Adwaita theme names in Dutch and Ukrainian

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:08:12 -04:00