67 Commits
Author SHA1 Message Date
Raghav Krishna aa12046847 Crop: Straighten gets its label and a spirit-level glyph (#10888) 2026-11-10 17:47:27 +05:30
TriwarrandFredApps 7c64653d5c Add toolbar side toggle (#11193)
* Add a toolbar side toggle and dock-edge resizing

- Let the toolbox sit on the canvas' left or right, persisted in the panel
  state and switchable from the toolbar header and preferences.
- In the compact arrangement, stack dock, rail and toolbox on the dock's
  physical edge so the toolbox cannot cover the rail.
- Make the dock panel's inner edge the single resize handle: it drives the
  panel width and the dock total (split size and column widths) in the same
  frame, so the rail and dock content stay bound with no reserved room and
  no second handle. The docking layout's own column-edge handle is covered;
  the bar is the theme separator at rest and the accent on hover/drag.
- Persist the layout, keep the split size in sync, handle RTL, and test the
  toolbar side state.

* Toolbar side: dock-edge only with a lone canvas; test every theme

- Use the between-dock-and-canvas layout only when nothing is docked beside the canvas: that view drops the canvas from craft-ui's split, so with a panel there the standard layout keeps it visible and dockable.
- Add canvas_alone_layout/canvas_alone and a unit test for a lone canvas versus a split canvas side.
- Add a kittest that toggles the toolbar Left/Right across every ThemeKind::ALL entry, asserting the panel lands on the requested physical side.
- No dependency changes: Cargo.toml and Cargo.lock are identical to main, so the repository's exact pins are untouched.

* Toolbar side: keep the canvas a drop target during dock drags

While a panel is being dragged, draw the standard layout even with the toolbar at the dock edge. It keeps the canvas in craft-ui's split, so a panel can still be dropped beside it; after the drop the canvas is no longer alone and the standard layout stays. The between-dock-and-canvas layout resumes for a lone canvas once the drag ends.

* Toolbar side: stable drag layout, unwind-safe dock flag, clippy

- Restore the dock-only thread-local through a drop guard, so an unwinding show does not leave a later standard layout canvas-less.
- Do not switch the dock layout mid-drag: craft-ui records its areas on the press, and changing the shape cancels the drag. The layout stays stable; a panel beside the canvas still selects the standard layout before the drag begins.
- Fix the CI clippy errors: explicit auto-deref in end_dock_node/resize_end_dock and field reassignment in the toolbar_side test.

* Toolbar side: anchor the Edit Toolbar flyout; borrow the canvas layout

- more_slot_flyout takes the resolved ToolbarSide instead of the text direction, so a right-hand toolbar in LTR (and a left-hand toolbar in RTL) anchors the flyout away from the canvas like the tool flyouts.
- canvas_alone borrows the saved layout and treats the native-dock legacy layout as canvas-only, avoiding a per-frame clone and traversal.

* Toolbar side: only a horizontal canvas split uses the dock-edge layout

A saved workspace can put the canvas above another panel in a vertical root split; canvas_alone_layout now requires SplitAxis::Horizontal so that shape keeps the standard layout. The guard test covers the vertical case.

* Toolbar side: document the functions this PR adds

Covers ToolbarSide::resolve/other, canvas_alone_layout, the DockOnlyGuard drop, and the two new tests. Docstring coverage of the PR's own additions is now complete; no behaviour change.

* docs: refresh the scorecard after merging main

The merge left docs/scorecard.md stale against the combined settings table; regenerated with cargo xtask scorecard, which is what the gate's scorecard step checks.

* Toolbar side: honor the workspace lock, empty docks and the end-dock shape

- The dock-edge resize handle is inert while the workspace is locked (hover sense, no highlight) and set_end_dock_total refuses to write then; the drag test covers a locked workspace.
- An empty dock takes only the rail's width instead of a clamped blank column; dock_is_empty reports it and a test empties the dock.
- canvas_alone_layout now requires the second child to be the Dock itself, and end_dock_node/resize_end_dock follow horizontal splits only, so a layout the handle cannot resize keeps the standard layout instead of snapping back.
- Tests: DockOnlyGuard restores the flag on drop and unwinding; the theme test clicks the toolbar's move button instead of setting state; a dock-edge harness drags the edge, checks it persists and that a locked workspace refuses it; axis tests and a two-column ui.set {dockWidth} test.
- The flyout indicator follows the resolved toolbar side, the rail keeps its chrome fill and 4/8 inset with the separator inside it, ATTRIBUTION counts the new icon (116), and docs/control-protocol.md describes dockWidth as the whole end dock.

* Toolbar side: floating windows on empty docks, iconic widths, dockWidth columns

- An empty dock still runs the docking pass on the full remaining area, so Studio's floating Properties window keeps rendering with or without the rail; the dock column itself takes no width. A Studio regression builds an empty dock with a floating Properties group and asserts the pass runs and the window is drawn.
- Dock width now uses each column's displayed width (iconic_width while collapsed to icons), and resizing/scaling keeps iconic widths in step. A collapse-to-icons regression checks a 36-point column not reserving 290.
- ui.set {dockWidth} scales every column to the requested total directly instead of resizing only the first column and then finding the sum already equal. The two-column test also covers a target smaller than the other column's expanded width (250 from [300, 180]) and asserts per-column widths.

* Toolbar side: iconic widths, edge release and modal handling, side persistence

- An iconic-only dock keeps its narrow displayed width instead of being raised to the expanded
  minimum, and the dock-edge panel records its laid-out width for a geometry regression.
- The release frame applies its final pointer position before dropping the drag, so the dock keeps
  the width the user released at.
- The dock-edge handle is inert (and any stored drag is dropped) while a modal dialog is open.
- set_display_width changes iconic_width while iconic and leaves the columns expanded width for
  expanding again; the regression resizes an iconic column and checks the restore.
- Clicking the toolbar side persists the panels snapshot immediately through persist_panels, even
  when the dock is not drawn, while Remember Workspace Changes is on.
- The locked-drag test compares against the pre-drag width instead of only settled equality.

* Toolbar side: defer persistence while moving, translate the side labels

- persist_panels defers while the dock persistence pass sees a native window move/resize (the
  same native_moved signal), so a click during an OS resize cannot save intermediate geometry;
  it requests the usual 100 ms repaint and leaves the dock pass to save when the geometry is
  stable.
- The toolbar-side tooltip/accessible labels now pass both literals directly to tl!, and all 34
  registered catalogs carry translations for Move Toolbar to the Right/Left.

* Toolbar side: keep in-progress dock geometry out of toolbar saves

- native_moved caches its answer for the frame, so the dock pass and persist_panels see the same
  movement result even though only the first call observes the change.
- persist_panels defers while a dock-edge drag is stored, a pointer is down or a native window
  moved; it stores a pending flag and requests the usual 100 ms repaint.
- persist_pending runs in the frame tick (before preferences save) and retries the deferred save
  once geometry is stable, so the toolbar side is not lost even when the dock was not drawn.
- The dock persistence pass also skips while the dock-edge drag start is stored, covering a
  synthetic/accessibility click with no pointer button down.

* Toolbar side: keep a same-width dockWidth request a no-op

The dockWidth rewrite scaled and committed the legacy layout even when the requested width
already matched, so a later `ui.set {dockTabs}` took the customized-workspace path and expanded
the Properties group. Restore the early return for an unchanged displayed width and cover it:
setting the current dock width keeps `app.ui.docking` None.

Fixes the `a_new_adjustment_layer_brings_properties_to_the_front` failure on CI.

* Toolbar side: recheck native bounds, expire stale dock-edge drags

- native_moved keeps movement already seen in the frame but compares the bounds again on every
  call, so a later dock save is not fooled by the early persist_pending check caching false
  before the docking UI updates a floating window.
- dock_panel records the frame it rendered; persist_pending drops a stored dock-edge drag start
  once the panel stopped rendering (Shift+Tab hides the dock mid-drag), so deferred saves resume.
- The same-width dockWidth test also selects a tab in a collapsed group and asserts the tab,
  the collapsed state and that no workspace was customized.

* Toolbar side: draw the release width, clear drags when the dock empties

- The release frame draws the panel at the final pointer width just stored, instead of the width
  read before the drag ended, so the panel and toolbar match the saved dock width.
- The empty-dock path clears a stored dock-edge drag start, so a dock that empties mid-drag cannot
  leave deferred saves blocked.
- Tests: nested DockOnlyGuard (flag found set stays set), and a queued `ui.set {dockWidth}`
  through the rendered dock pass checking a two-column dock totals 420 points.

* Toolbar side: cancel the dock-edge drag when resizing is unavailable

- A disabled parent UI (screen picking) never delivers drag_stopped, so dock_panel now clears a
  stored dock-edge drag whenever resizing is unavailable: locked, modal, empty dock or disabled
  UI. The same condition makes the handle hover-only, so it cannot start a new drag while
  disabled.
- Regression: start a resize, disable the UI, release, re-enable and move the pointer; the drag
  is cleared, the dock does not jump and persistence can resume.

* Toolbar side: bound the dock-edge layout and enforce column minima

- The dock-edge panel leaves the toolbar and the 200-point canvas minimum room, so a narrow
  window shrinks the dock instead of the canvas.
- The empty dock runs the docking pass in a child ui that does not consume the parent layout, so
  the toolbar and canvas keep their space.
- Column scaling keeps the 220-point panel minimum: minima are allocated first, the rest is shared
  proportionally, and requests below the sum of minima stop there.
- The dock metrics (empty, iconic-only, width) come from one layout snapshot per frame.
- Removed dead empty-dock branches, narrowed the content-edge interceptor to one point either
  side, corrected the toolbar target comment, used the drag press origin, and documented the
  Side::End assumption.
- Tests: narrow-window canvas minimum, empty dock composed with toolbar and canvas, minimum-aware
  two-column widths, and the empty compose regression.

* Toolbar side: bound the dock-edge layout, enforce panel minima, match iconic widths

- The dock-edge panel leaves the toolbar and the 200-point canvas minimum room, so a narrow
  window shrinks the dock instead of the canvas.
- The empty dock runs the docking pass in a child ui that does not consume the parent layout, so
  the toolbar and canvas keep their space.
- Column scaling allocates each column's 220-point panel minimum first and shares the rest
  proportionally; requests below the sum of minima stop there.
- Iconic columns use craft-ui's renderer rule: iconic_width.unwrap_or(36).clamp(36, 240), including
  the real collapse case that leaves iconic_width unset.
- Dock metrics come from one layout snapshot per frame; dead empty-dock branches are gone; the
  content-edge interceptor is one point either side; the toolbar target comment is corrected and
  the Side::End assumption documented.
- Tests: narrow-window canvas minimum, empty dock composed with toolbar/canvas, minimum-aware
  two-column widths, collapse via Action::SetIconic leaving iconic_width unset, and the 36..240
  clamp. The press-origin change was reverted: Sense::drag starts immediately.

* Toolbar side: distribute refused width, draw actual widths, invalidate stale drags

- scale_columns moves the total toward the request in passes, freezing columns that hit a bound
  (iconic 36..240, expanded minimum 220), so width refused by the iconic cap reaches columns that
  can still grow.
- set_end_dock_total returns the width actually stored; the dock panel uses that width during the
  drag, on release and after, instead of forcing the requested or minimum width.
- The custom drag stores docking_generation; a workspace replacement clears the stale gesture
  before any width mutation.
- Clamp bounds are normalized against the available maximum (including iconic columns) and
  resizing is disabled when the window cannot fit the column minima, so narrow layouts cannot
  panic.
- Tests: iconic-cap surplus redistribution, actual-vs-requested panel geometry during and after a
  drag, workspace replacement during a drag, and a 300pt window resize without panic.

* Fix dock-edge resize bounds and cancellation

* Preserve explicit dock width across legacy migration

* Document dock code, dedupe widths, harden toolbar_side load

- Add doc comments to the functions and tests this PR touches (Docstring Coverage).
- Share the toolbar width (toolbar_width/toolbar_columns), the rail widths and
  CANVAS_MIN_WIDTH instead of repeating the values; no values change.
- Document dockWidth as the requested model width, which a narrow window may draw
  narrower in either layout, and test model 520 / drawn <= 364 / canvas >= 200.
- Document that a dockWidth change is shared equally among columns within their
  minimums and iconic caps; tie ICONIC_WIDTH/ICONIC_MAX_WIDTH to craft-ui's
  DockStyle defaults (36/240) in a test.
- Replace the DOCK_ONLY thread-local and its guard with show_layout(dock_only).
- Move the test-only probe writes behind panel_docking::record, with no-op stubs
  outside tests.
- Read an unknown or malformed saved toolbar_side as Start instead of dropping
  the whole panel state; ui.set still refuses it.

* Test visible dock chrome in narrow windows; name panel body minimum

- The narrow-window dockWidth test now checks the docking controls craft-ui drew:
  hit_rects are full widget rects (before clipping), so the visible part of each,
  intersected with the dock, must stay inside the dock and off the canvas and
  the toolbox, with at least one control visible.
- Name the 200-point guard for drawing a panel's body PANEL_BODY_MIN_WIDTH,
  separate from CANVAS_MIN_WIDTH; no value change.

* test(ui-egui): assert dock controls are clipped at the panel edge

The narrow-window test intersected each hit rect with the dock and then asserted the
intersection lies inside the dock, which always holds. Check each dock control's clipped
interact_rect against the dock instead, and add a squeezed-column test where a control's
full rect runs past the panel edge, so the clip is actually exercised.

* ci: retrigger pull_request workflows after resolving the merge conflict

No changes. GitHub did not queue Actions for 3652ede, the push that
resolved the conflict with main.

* test(ui-egui): assert saves resume after a disabled UI cancels a dock-edge drag

The test's doc says saves must resume once a disabled UI drops the held drag, but nothing
checked it. Assert that the held drag is not saved, that its width is saved as soon as the
drag is cancelled (while the UI is still disabled), and that a width change made after
re-enabling reaches the saved layout too.

* test(ui-egui): cancel a dock-edge drag on a window shrink and check the dockWidth column split

constrained_width_does_not_panic_while_resizing now starts the drag in a wide window and
shrinks the window below the column minima while it is held: the drag must be cancelled and
the dock must stay within the window. Both ui_set_dock_width tests now check how the change is
split between the columns, not only the total.

* test(ui-egui): tighten dock tests per CodeRabbit review 5824274070

- disabling_the_ui_cancels_a_dock_edge_drag: assert the drag is cancelled on the first disabled
  frame, before the pointer is released.
- all_iconic_totals_stop_at_both_bounds_without_losing_expanded_widths: check the stored column
  widths sum to the expected total after each request.
- explicit_width_request_preserves_queue_and_native_state_until_invalid_workspace_recovery: add
  a changed-width (520) case next to the no-op one.
- the_toolbar_side_toggle_works_in_every_interface_theme: with the toolbar on the right, a grouped
  tool's flyout opens to the left of its button.

* test(ui-egui): assert stored widths and native rect per CodeRabbit review 5824824938

- constrained_width_does_not_panic_while_resizing: the stored column widths captured before the
  window shrinks are unchanged after the cancel and after the release.
- explicit_width_request_preserves_queue_and_native_state_until_invalid_workspace_recovery: the
  native state keeps the exact rectangle the test saved until recovery, not just some state.

* Keep the toolbar side when a workspace saved none

A workspace saved before the toolbar could move has no panels.toolbar_side,
so applying it reset a right-hand toolbar to Start. Keep the current side
when the saved value is missing or unreadable; a valid saved side still wins.

* Cover a saved start side and the other panel flags on workspace apply

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-09 15:51:29 +01:00
rmsff 08b1149175 fix(ui): vertical text gets Top / Center / Bottom alignment buttons (#12059)
The Type options bar always showed Left / Center / Right. As in the
reference application, for vertical text it now shows "Top align text",
"Center text" and "Bottom align text" with vertical glyphs: when the
edited or selected text layer is vertical, or with no text targeted
when the Vertical Type tool is active. The buttons still set left /
center / right and the lit one follows the targeted paragraph. The Type
Mask tools keep today's buttons (not measured).

The orientation is read from the target's own document, as the
paragraph style is, so a text edit left in a background document is
not judged by a same-id layer of the active one.

The three glyphs (vertical-align-{top,middle,bottom}.svg) are original
work drawn on Lucide's grid and stroke, with their ATTRIBUTION.md row;
they are kept out of the Lucide row's wildcard.
2026-11-08 15:55:59 +03:00
David MannandClaude Opus 5.5 c7b5373ba8 Crop: Contextual Task Bar under the crop frame, as a reusable task-bar widget (#10890)
The Crop tool gets the reference application's contextual task bar: a small floating
bar centred 11 pt under the crop frame with a grip, the preset dropdown (the options
bar's own, so both stay in sync), Straighten, a ⋯ menu (Hide bar, Reset bar position,
Pin bar position) and, while a crop is pending, Cancel and Done. It follows the frame,
stays inside the canvas, and stays put once dragged or pinned. Window › Contextual Task
Bar (after Tools, as in the reference) shows and hides it.

The bar itself is a reusable widget (widgets/task_bar.rs): position rule, grip, ⋯ menu
and the hide/pin/place state in Panels (kept with the panel layout across restarts,
read by ui.inspect › taskBar, set by ui.set {panels}). A context supplies only its
controls and where ⋯ goes; the Crop contents are tools/crop_task_bar.rs. Generative
Expand stays out until #41.

Also: Lucide pin and rotate-ccw icons, six new strings in all 27 catalogues, parity
661/661 (FLOOR raised), control-protocol docs. Three tests about the options bar's
Ratio dropdown now hide the task bar, which shows the same dropdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:50:20 +01:00
davidvhkandFredApps f1137013a2 feat(paint): add History Brush opacity/flow controls, source selection, and custom icon (#4446)
* feat(paint): add History Brush opacity/flow controls, custom icon, and options bar source label

- Options bar and keyboard shortcuts (digits 0-9 and Shift+digits) for History Brush opacity and flow %
- Options bar displays translated source state or snapshot name with missing-source hint when deleted
- Dedicated clean-room history-brush icon combining Lucide brush with counter-clockwise rewind arc
- Preserved JetBrainsMono attribution in ATTRIBUTION.md and added history-brush icon attribution
- Add contributor credit in people.toml

* History Brush options bar: rustfmt the source label

* Opacity keys: 00 gives Clone Stamp and History Brush their 1% minimum

Their Opacity fields and the retouch command start at 1%, so 00 set a 0%
the stroke then painted at 1% (CodeRabbit). The Brush still goes to 0%.

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 05:29:57 +01:00
Raghav Krishna 1d86b32bab Crop: give each overlay its own glyph in the menus and on the options button
Six original icons in the house style, one per crop overlay, drawn in the
crop-mark frame around the guide they name. They sit between the check mark
and the label in the options-bar overlay menu and in the canvas Crop menu, and
the options-bar button paints the overlay in use.

Tests: crates/ui-egui/tests/it/crop_overlay_icons.rs (#10889).
2026-11-03 15:52:40 +05:30
aiongg 6d737c219e docs: tidy agent, contributor and release docs 2026-10-31 04:17:45 +00:00
David MannandClaude Opus 5.5 d9cb0edd8d Liquify: no Twirl Counterclockwise tool, Alt with Twirl Clockwise does it
The reference application's Liquify has Twirl Clockwise only; holding
Alt (Option on macOS) while using it twirls counterclockwise. PhotoCraft
had an extra Twirl Counterclockwise tool on the strip, and #9598 had just
given it a mirrored icon.

Twirl Counterclockwise leaves the tool strip (LiquifyTool::ALL, 11 to
10 tools) and the rotate-ccw icon #9598 added goes with it (asset,
icon_data entry, ATTRIBUTION count back to 113). Alt already reversed
Twirl Clockwise at stroke-down; that stays. The TwirlCcw variant stays as
a stroke's direction: Alt-strokes record `twirlCcw`, and filter.liquify
strokes and older saved strokes naming it still replay as Twirl
Clockwise reversed (the command doc says so). Selecting it as the tool
(control channel `tool: "twirlCcw"`, or a preference saved by an older
version) is refused with an error that says to choose twirlCw and hold
Alt; the rest of the settings still apply and the dialog opens.

Tests: the_tool_strip_has_no_twirl_counterclockwise (no such tool, label
or missing icon on the strip, C is Twirl Clockwise, `twirlCcw` as the
tool is an Err and leaves the tool, an old saved preference naming it
opens); alt_with_twirl_clockwise_twirls_counterclockwise (the field
displacement a dab leaves right of the centre points up without Alt and
down, mirrored, with Alt). twirl_counterclockwise_has_its_own_mirrored_icon
and the twirlCcw row of alt_reverses_twirl_and_swaps_pucker_and_bloat
are removed. The i18n labels for Twirl Counterclockwise stay (the
variant's label).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:34:03 +01:00
FredApps 7f1f867b1d Merge approved PhotoCraft PR #9598: fix(ui): Liquify's Twirl Counterclockwise gets a mirrored twirl icon
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Preserve current attribution exclusions and credit the mirrored Lucide Twirl Counterclockwise icon; keep both Liquify regressions.
2026-10-29 04:21:48 +01:00
FredApps 86fcbf26ee Merge approved PhotoCraft PR #7648: Pen: Path Operations menu, as in Photoshop (#1397) 2026-10-29 03:29:31 +01:00
FredApps e7e6ff1bf2 Merge approved PhotoCraft PR #4547: fix(icons): PhotoCraft icon corner radius matches other Craft apps (#4478) 2026-10-29 03:28:34 +01:00
Khawar Zamman Wani 8b835a103c Merge origin/main into pen-path-operations 2026-10-28 09:22:55 +05:30
Hector VillarrealandClaude Opus 5.5 47aea89e92 fix(ui): Liquify's Twirl Counterclockwise gets a mirrored twirl icon (#9547)
Twirl Counterclockwise used Lucide's undo-2 arrow, which reads as Undo.
It now uses rotate-ccw, Twirl Clockwise's rotate-cw mirrored (path data
mirrored about x = 12), registered in icon_data and listed in
ATTRIBUTION.md with the other Lucide icons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:39:43 -06:00
D-monizedandClaude Opus 5.5 0056b44a6f Merge origin/main into feature/font-recent-favorites
main now has its own Recent Fonts (#3559), so this branch drops its separate
recent list (prefs `recentFontList`, `type.font.noteRecent`, `type.font.clearRecent`,
the five-font cap) and its font menu uses main's: the list is
`PhotocraftApp::note_recent_font`'s, the rows and arrow keys main's `font_menu_rows`,
and every applied family is recorded, as main does. Favourites, the class and
similar/variable filters, the style expander, format badges and the live preview
stay. Faux Bold keeps the one-pass thickened outline on main's fallible coverage
buffers (#6001), budgeted as a plain pass. Both sides' strings and icon credits are kept;
the scorecard is regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:28:05 -06:00
Khawar Zamman Wani ac3d172acd Merge remote-tracking branch 'origin/main' into pen-path-operations
# Conflicts:
#	ATTRIBUTION.md
#	docs/ui-parity.md
2026-10-22 22:36:15 +05:30
aiongg 1f0177f23c Integration: track t04
# Conflicts:
#	ATTRIBUTION.md
#	contributors/people.toml
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/gaps.md
2026-10-22 15:38:17 +00:00
D-monizedandClaude Opus 5.5 1b2e5b5536 Merge origin/main into feature/font-recent-favorites
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:43:10 -06:00
aiongg 809ecfcfa3 Merge PR #5951: Type: Create warped text button in the options bar, as in the reference app (#5911)
ATTRIBUTION.md: main's icon-exceptions row with the PR's text-warp.svg added to its list.
2026-10-22 02:23:35 +00:00
aiongg 567a0de150 Merge PR #3532: Move tool: show all six Align icons and an icon menu for Distribute (#3490)
ATTRIBUTION.md: the Lucide row is ours plus `align-edge-*.svg` and `distribute-*.svg` from the PR.
2026-10-22 02:23:23 +00:00
Khawar Zamman WaniandClaude Opus 5.5 85a1c26950 Merge remote-tracking branch 'origin/main' into pen-path-operations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:28:28 +05:30
Korg1ch 5634b5155a fix(ui-egui): Slice Select gets its own icon, the Slice knife with a pointer (#7859) 2026-10-20 15:50:02 +03:00
D-monized ca1d9da607 Merge remote-tracking branch 'origin/main' into pr6526-merge
# Conflicts:
#	ATTRIBUTION.md
#	crates/text/src/tests.rs
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/src/type_font_picker_tests.rs
#	crates/ui-egui/src/type_tool.rs
#	docs/scorecard.md
2026-10-19 05:09:06 -06:00
Khawar Zamman WaniandClaude Opus 5.5 750c67e20e Pen: Path Operations menu, as in Photoshop (#1397)
The Pen, Freeform Pen and Curvature Pen options bar gets Photoshop's Path
Operations button: New Layer (Shape mode), Combine Shapes, Subtract Front
Shape, Intersect Shape Areas, Exclude Overlapping Shapes and Merge Shape
Components. In Shape mode a new path is added to the selected shape layer
with the chosen operation, so a hole can be cut in a shape (#7533); in Path
mode the operation applies to the subpath added to a targeted vector mask,
the selected path or the Work Path.

- engine: path.mergeShapeComponents for work, saved and layer paths, sharing
  bake_path with the shape-layer command
- control/MCP: ui.set penMode and penPathOp
- original path-op icons, tooltip translated in every catalog

Closes #1397. Closes #7533.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:30:53 +05:30
vangel76andClaude Sonnet 5.5 f3a727cc94 Merge upstream/main into fix/3490-move-align-bar
Resolve ATTRIBUTION.md: keep main's ellipse-dashed.svg entries and this PR's align-edge/distribute icon entries.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 02:28:09 +02:00
Michael HigginsandClaude Opus 5.5 a914c58a63 fix(ui): wide ellipse for Elliptical Marquee, dashed C + brush for Quick Selection (#6875) (#6917)
The Elliptical Marquee used a dashed circle and Quick Selection a brush
inside a dashed circular arc, so the two read alike. Photoshop draws the
marquee as a horizontal ellipse and Quick Selection as a brush in an
upright dashed rounded C. New original ellipse-dashed.svg; quick-selection.svg
redrawn; tests check the ellipse is wider than tall and the C runs down
the left edge.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:34:42 -04:00
D-monizedandClaude Opus 5.5 e11e296944 Font menu: favourites, recent fonts, class and similar-font filters, live preview
The font menu gains Photoshop's local font library and filters (everything but
Adobe Fonts):

- Favourites: a star on every row (never picks the font) and a favourites-only
  filter. Kept in the preferences (type.favoriteFonts).
- Recent fonts: up to five, most recent first, above a divider. Wires the dead
  Preferences > Type > Recent Fonts (now 0..5, default 5; older saved values
  are capped on load).
- Class list: All Classes, Serif, Slab Serif, Sans Serif, Script, Monospace,
  Handwritten, Blackletter, Decorative, read from each font's OS/2
  sFamilyClass, PANOSE and post tables (photocraft_text::font_class), else its
  name. Fonts that state nothing stay unclassified.
- Show Variable Fonts, Show Similar Fonts (PANOSE/class/weight distance to the
  current font) and Clear All; the class list collapses with «.
- Each row: style expander (">") that picks a family in a given style, and an
  O / TT / VAR format badge.
- A preview at the top sets the type layer's own text (or the selection being
  typed) in the font under the pointer.
- The search field and filters stay fixed while only the list scrolls, and the
  search field lines up with the font list.

Commands: type.font.favorite, type.font.noteRecent, type.font.clearRecent,
type.font.library. Two original icons (font-similar, font-variable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:18:41 -06:00
Mara Johnson a08f7a2295 Merge remote-tracking branch 'origin/main' into fix/4478-icon-corner-radius 2026-10-15 14:56:43 -04:00
FazminandClaude a4b1e2a607 Type: Create warped text button in the options bar, as in Photoshop (#5911)
The Type tool's options bar now has Photoshop's Create warped text button
after the text colour swatch. It opens the same Warp Text dialog as
Type > Warp Text... for the selected type layers (or the layer being
edited), and is greyed when there is no type layer to warp.

- assets/icons/text-warp.svg: original glyph (a T above a bowed arc) on
  Lucide's 24 px grid and stroke; listed in ATTRIBUTION.md.
- The tooltip is translated in all 20 catalogs, reusing each language's
  existing "Create Warped Text" wording (new for Russian).

Test: options_bar_warp_button_warps_the_selected_type_layers.

Co-Authored-By: Claude
2026-10-14 23:09:05 -04:00
vangel76andClaude Opus 5.5 9303c10bb6 Merge upstream/main into fix/3490-move-align-bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:44:23 +02:00
mroxsoandhighperfocused a076f617e0 fix(ui): give the Quick Selection tool its own icon (#5116) (#5184)
Quick Selection reused the Ellipse Marquee's dashed circle. It now has a
distinct glyph: Lucide's brush with a dashed selection arc around the
bristles (Photoshop's Quick Selection mark). A rasterized test asserts
the two icons read as distinct and that the arc actually renders.

Co-authored-by: highperfocused <highperfocused@pm.me>
2026-10-14 13:07:48 -04:00
vangel76 2b3df66ff0 Merge remote-tracking branch 'upstream/main' into m/x
# Conflicts:
#	ATTRIBUTION.md
2026-10-13 21:55:40 +02:00
Vikram BhaskaranandBrandon Thomas 24930b3188 Draw adjustment thumbnails as a half-filled circle. (#4628)
The contrast glyph put a smaller semicircle inside the ring, which read as concentric rings on a layer thumbnail. It is now one circle with a filled half. (#4561)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 00:13:39 -04:00
Korg1ch 4122bc6918 fix(ui): use a linear gradient ramp glyph for the Gradient tool icon (#4172) (#4335)
* fix(ui): use a linear gradient ramp glyph for the Gradient tool icon (#4172)

* test(ui): verify Gradient tool icon rasterizes a left-to-right ramp (#4172)
2026-10-12 20:56:09 -04:00
Mara Johnson d84ad4c22f fix(packaging): Windows and Linux icons keep the rounded-square corners (#4478) 2026-10-12 19:02:42 -04:00
vangel76andClaude Opus 5.5 958af5f518 Merge upstream/main into fix/3490-move-align-bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:54:58 +02:00
5025fd2895 Color Sampler tool (#1046) (#2753)
* Color Sampler tool (#1046)

Add the Color Sampler to the Eyedropper (I) flyout: click to pin numbered
sample points to canvas pixels, drag to move, Alt-click (or drag off-canvas)
to remove, Clear All in the options bar, at most 10 per document. Each point
reads one composite pixel (RGB 0-255 and naive device CMYK, same as the
pointer readout) and the Info panel lists them under their number with the
position in the ruler unit; the readouts refresh when the document changes.

Points are per-document engine view state (DocState.color_samplers): they
belong to their document, are discarded on close, and are not saved to
.pcraft or PSD. They are driven by commands shared by the UI, CLI, control
channel and MCP (view.colorSamplers.add/.move/.delete/.clear/.list); the
mutations change no pixels and add no History step.

Points follow the canvas geometry (crop, Canvas Size, Image Size, Image
rotation/flip, rotated crop) through a pending Session.sampler_map that
Session::edit applies before culling the points that land outside the new
canvas; the count removed is reported in the status bar.

- engine: new sampler_cmds.rs (commands + shared readout helpers) and the
  sampler_map wiring in lib.rs, image_cmds.rs, mode_cmds.rs, commands.rs,
  analysis_cmds.rs (compound rolls points back on error).
- ui-egui: Tool::ColorSampler + original sampler.svg icon, toolbar slot,
  Info panel list, options bar, pointer handling, overlay markers, cursor.
- docs/scorecard.md regenerated (TOOL-1649-1 no longer says the tool is
  missing). No menu item, so parity is unchanged (627/627).

* Color Sampler follow-up: tool count, README, i18n, roadmap

The full ui-egui suite (not just the filtered tests) surfaced these gaps:

- state.rs: Tool::ALL.len() assertion 52 -> 53.
- README.md: "52 tools" -> "53 tools" and add Color Sampler to the list,
  so readme_tool_list_matches_tool_all passes.
- i18n: the options-bar hint, "{n} of {max} sample points" and
  "No document open" need entries in every catalog that claims complete
  menus (every_tl_literal_is_translated); added to all 16 (coverage
  1584/1584). The translations were drafted alongside the tool.
- docs/roadmap.md: drop Color Sampler from the missing-tools list and note
  it landed.

* Color Sampler: fit main's new tools and Image Size resample type

Reset Tool covers ColorSampler, the test ViewXform sets aspect, and
Image Size checks Method::None instead of an Option.

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-11 11:43:09 -04:00
vangel76andClaude Opus 5.5 8607654fa7 Merge upstream/main into fix/3490-move-align-bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:33:05 +02:00
d8d8d9f3d2 Tools: Convert Point tool in the Pen flyout (#3204) (#3211)
Convert Point is a tool of its own, built on the existing path.convertPoint
edit that Pen + Alt already drives. A click makes a smooth anchor a corner, a
drag from an anchor pulls out smooth handles, a drag on a handle moves it
alone. The tool selects the anchor it converts so its handles show; a press on
empty canvas or a segment does nothing, and Cmd/Ctrl is Direct Selection, as
with the Pen. It shares the Pen's key (P).

Original glyph with an ATTRIBUTION.md row; tool name in all catalogs; README
tool list and the G6 / ui-parity / roadmap counts updated (tools 56 of 68, the
earlier 53 was stale).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:09:38 -04:00
vangel76andClaude Opus 5.5 0a75c37c7a Move tool: show all six Align icons and an icon menu for Distribute (#3490)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:44:26 +02:00
ade5799165 Mirror the shell for right-to-left UI languages (DIST-220-4) (#3114)
* Mirror the shell for right-to-left UI languages (DIST-220-4)

Arabic, Hebrew, Persian and Urdu read right to left, but the window's
chrome was hard-wired left to right, so an RTL catalog (#2505) would
put Arabic labels into a left-to-right layout.

The reading direction now follows the language code
(i18n::dir_for_code: ar, ckb, dv, fa, he, ps, sd, ug, ur, yi), so a
catalog needs no extra setting and the LANGUAGES rows are unchanged.
In a right-to-left language the shell mirrors:

- the menu bar starts at the right with File; menus open under their
  title's right edge, and the arrow keys step through them in screen
  order
- the toolbox docks on the right, the panel dock and its rail on the
  left; tool flyouts open towards the canvas and their corner marks
  and the header chevrons follow
- the title, options and status bars read from the right, with the
  workspace controls and the Fit/progress group at the left end; the
  title and window-drag gap use the free span between them

New module direction.rs holds the helpers (start_panel/end_panel,
row/trailing layouts, horizontal_centered, the popup alignment). Left
to right, every change lays out as before.

No RTL catalog ships yet, so PHOTOCRAFT_UI_DIRECTION=rtl forces the
mirrored layout for review (read once, native only). Panel interiors,
dialogs, menu rows, submenus and document tabs stay left to right, and
egui still draws UI labels without Arabic shaping or bidi reordering;
docs/localization.md and the parity docs say so.

Adds Lucide's chevron-left (the status bar's info menu arrow points
left in RTL; Timeline's Previous frame button asked for it too and drew
the fallback square).

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

* Localization docs: egui shapes Arabic words; what's missing is a font and bidi word order

egui 0.36 shapes right-to-left runs with HarfRust, so letters join within
a word. The gaps are an Arabic fallback font in the UI and paragraph bidi
(words are placed left to right; emilk/egui#8576, #8577).

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

---------

Co-authored-by: MathMattTech <334629259+MathMattTech@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:06:18 -04:00
Valentin KleinandClaude Opus 5.5 42e4af6416 Tools: Frame tool (#2914) (#2915)
The Frame tool (K, its own toolbar slot after the Crop group) draws a
rectangular or elliptical frame; the options bar picks the shape, Shift
constrains to a square or circle and Alt draws from the centre. A frame
drawn over the selected pixel layer's content takes that layer in;
otherwise it is an empty frame.

Frames are built as Frame from Layers builds them: a group clipped by a
layer mask (rectangular or anti-aliased elliptical). The new engine
command layer.new.frame does it in one Session::edit, so one undo step.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:20:15 -04:00
Valentin KleinandClaude Opus 5.5 017d1aea1c Tools: Perspective Crop (#1043) (#2864)
New Perspective Crop tool in the Crop flyout (C): drag a box or click four
corners, drag corners independently onto a skewed object, perspective grid
(Show Grid), Enter commits, Esc cancels. W/H/Resolution come from the Crop
tool's fields (Front Image, Clear), or the quad's mean side lengths.

Engine: image.perspectiveCrop {corners, width?, height?, resolution?,
interpolation?} maps the quad onto an upright canvas through the
quad-to-rectangle homography (Heckbert's square-to-quad map, inverted):
every raster layer, mask, alpha channel and the Quick Mask, one undo step.
The Background stays opaque over the background colour; type, shape, fill
and smart object layers are rasterized first; vector geometry follows the
nearest affine map. Non-convex, self-crossing, counter-clockwise or
degenerate quads are refused with an error.

algo: warp_surface_clipped bounds the per-tile warp by the new canvas even
when a layer reaches past the map's horizon; convex_quad, quad_size,
quad_to_rect.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:28:11 -04:00
2e0c426692 Tools: Artboard tool (#2858) (#2862)
Photoshop's Artboard tool in the Move flyout (V, ⇧V cycles Move ↔ Artboard):
dragging on an empty spot draws a new board (`layer.new.artboard`), clicking a
board selects it, dragging it moves it with its contents, and the selected
board's edge and corner handles resize it (`layer.artboard.set`,
moveContents: false). Each gesture is one undo step. Options bar: Size preset,
W/H, Make Portrait, Make Landscape, Add New Artboard. Original toolbar glyph
(assets/icons/artboard.svg, ATTRIBUTION.md row).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 07:12:53 -04:00
5866cd2cf6 Tools: Horizontal and Vertical Type Mask (#1055) (#2861)
The Type flyout (T, Shift+T cycle) gains Vertical Type Mask and Horizontal
Type Mask, in Photoshop's order. They type like the Type tools, with the
canvas under Quick Mask's colour and the glyphs clear while editing. On
commit the glyph coverage replaces the selection; no layer is left behind
and the whole session is one "Type Mask" history step. Escape always
cancels and leaves the selection unchanged.

The commit runs a new engine command, type.createSelection: a type layer's
glyphs (optionally removing that layer, the tools' hidden working text) or
type.create's keys directly, with new/add/subtract/intersect and an
anti-aliasing override. type.create's layer building is shared through
type_cmds::new_text_layer.

Original dashed-T icons (ATTRIBUTION.md), tool label translations in every
catalog, README tool list and the gaps/parity docs updated.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 07:06:10 -04:00
c436aa7294 Tools: Freeform Pen and Curvature Pen (#1054) (#2856)
The Pen flyout (P / Shift+P) now holds Pen, Freeform Pen and Curvature Pen.

Freeform Pen: a drag draws freehand; on release the trace is fitted with
cubic Beziers within the options bar's Curve Fit (default 2 px) by
photocraft_vector::trace::fit_trace (Schneider, "An Algorithm for
Automatically Fitting Digitized Curves", Graphics Gems 1990, the fitting
Make Work Path already uses), keeping turns sharper than 60 degrees as
corners. Releasing on the start point closes the path.

Curvature Pen: each click adds a smooth point and the path runs through
every point (Catmull-Rom style handles from
photocraft_vector::edit::curvature_knots); dragging a point moves it,
double-clicking toggles corner/smooth, clicking the first point closes,
Enter/Esc finish and Backspace removes the last point, as with the Pen.

Both commit through the Pen's path: a shape layer in Shape mode, else the
targeted vector mask, else the work path, as one History step.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 06:56:44 -04:00
d96bb50161 Add Anchor Point and Delete Anchor Point tools (#1044) (#2756)
Paths could be reshaped after drawing since #865, but the number of
anchors could not change. Two new engine commands do that, and two tools
in the Pen flyout drive them on the canvas.

- photocraft_vector::edit::add_anchor splits the segment at t with de
  Casteljau, so a curve keeps its shape and gets a smooth anchor; a
  straight segment gets a corner on the line. delete_anchor joins the
  neighbours keeping their handles, drops an emptied subpath and refuses
  to remove a path's only anchor.
- path.addAnchor / path.deleteAnchor take the same targets as the other
  point edits (work path, saved path by name, shape layer path, vector
  mask) and record one history step each.
- Tool::AddAnchorPoint / DeleteAnchorPoint join the P group after the Pen.
  They act on Direct Selection's paths, highlight the segment or anchor
  under the pointer, show a +/- cursor badge, swap with Alt and turn into
  Direct Selection with Cmd/Ctrl. An edit clears a Direct Selection on the
  same path so it never points at a shifted anchor.
- Icons derived from Lucide pen-tool (ATTRIBUTION.md), hints and tool
  names in all 16 catalogs, README tool list, roadmap.

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 05:48:07 -04:00
073e1cc1d2 Add the Color Replacement tool to the Brush toolbar group (#1045) (#2719)
The engine already had paint.colorReplacement, but the desktop UI had no
tool for it: no Tool entry, no toolbar slot, no options bar and no stroke
routing, so only agents and the CLI could use it.

Tool::ColorReplacement joins the Brush (B) flyout after the Mixer Brush,
so B / Shift+B cycle to it and ui.set / ui.inspect name it. Its options bar
has Mode, Sampling (Continuous / Once / Background Swatch), Limits,
Tolerance and Anti-alias, all mapped to the command's params, with the
foreground as the replacement colour. Strokes are drawn live through
LiveDab, which can now preview the stroke's finishing dab, so the preview
at the last move matches the commit. The command refuses a mask, channel
or Quick Mask target instead of silently recolouring the layer. New icon
derived from Lucide's brush; label translated in all 16 catalogs.

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 05:20:00 -04:00
LuniiyaandBrandon Thomas e44d96e1d4 Favorite themes from the appearance button's right-click menu (#2445)
* feat(ui): favorite themes from the appearance button's context menu

Hovering a theme in Preferences > Interface shows a star; clicking it saves
the theme to interface.favoriteThemes (filled accent star once favorited).
Right-clicking the title-bar appearance button opens a menu with Appearance
Settings, then the favorite light themes and the favorite dark themes.

* Regenerate the scorecard for the favorite themes pref

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 03:09:47 -04:00
325c5b3194 Tools: Single Row Marquee and Single Column Marquee (#2837) (#2848)
Both tools join the Marquee flyout (no key, outside the M / Shift+M cycle,
as in Photoshop). A click selects the one-pixel row (column) under the
pointer across the whole canvas; a press-drag moves the line until the
release. The selection goes through select.rect, so Shift adds, Alt
subtracts, Shift+Alt intersects and the options-bar modes apply; Feather
applies, Anti-alias and Style/Width/Height are greyed. Selection-mode
cursor badges, the selection right-click menu and snapping cover them.
Original row/column glyphs with an ATTRIBUTION.md row; tool names in all
catalogs; README tool list and the G6 / ui-parity rows updated.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 19:10:34 -04:00
JohnnYoruandBrandon Thomas 897f9f37ad Add the Remove tool with non-local patch completion (#2193)
* Add the Remove tool with non-local patch completion

Tool::Remove (J flyout) paints a hole that paint.remove fills from its
surroundings as a background job, in one undo step. A stroke around an
object also removes what it encloses; the selection limits the hole.

The fill (photocraft_algo::nonlocal) follows Newson, Almansa, Gousseau and
Pérez (IPOL 2017): texture features in the patch distance, onion-peel
initialisation, match fields upsampled between levels and a best-patch
final copy, plus gradient-domain hiding of edge and interior seams.
Sparse holes are filled in tiles. inpaint::complete (Content-Aware Fill,
Spot Healing, Content-Aware Move) is unchanged.

On 20 holes in public-domain photos (remove_quality) the fill keeps 0.89
of the texture it replaces, against 0.54 with the Wexler/PatchMatch
completion. 24 MP (remove_bench): 100 px ring 120 ms, 1000 px ring 2.5 s.

* Translate the Remove tool's label and tighten comments

Adds "Remove Tool" to every UI catalog, following each one's pattern for
tool names and its existing word for Remove. Corrects comments that no
longer matched the code: the benchmark's description, the tile and margin
minimums, and the reasons given for two parameters.

* Put the Remove tool first in the J group

The J flyout's slot now shows Remove until another J tool is picked, and
J from another group selects it; repeated J goes on to Spot Healing Brush,
Healing Brush, Patch, Content-Aware Move and Red Eye.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 05:19:11 -04:00