Commit Graph
1798 Commits
Author SHA1 Message Date
dotCooCoo 77b2391f7a i18n: Reset Tracking and Kerning check matches each term once
A Tracking term that is part of the Kerning term no longer counts as
present when only the Kerning term appears in the row.
2026-10-29 10:10:58 +07:00
dotCooCoo 946ed6f12d i18n: Character panel Kerning and Tracking terms agree in each catalog
In Brazilian Portuguese the Character panel's Kerning field was named
Entrelinhamento, the word the catalog uses for Leading, so the two
fields had the same name. In Danish, Turkish, Indonesian and Dutch the
Reset Tracking and Kerning command named tracking with a different word
from the Tracking field, and in Dutch, Bulgarian and Greek the Info
panel's type line did the same.

The pt-br Kerning field is now Kerning (0 = Automático). The reset row
in da, tr, id and nl and the Info panel row in nl, bg and el now use
the word of the catalog's Tracking field. A new test checks that the
three fields have different names in every catalog and that the reset
row and the Info panel row use the field words.
2026-10-29 10:10:58 +07:00
dotCooCoo 71a67601a5 Layers: Alt-click selects only on the drawn name, and in an isolated layer
An Alt-click right of a short layer name, inside the wider rename
target, no longer replaces the selection. The isolated layer's own row
now selects the art inside it (layer.selectAll), where it cleared the
selection before. The doc line says the click selects visible, unlocked
art and clears the selection on a hidden or locked layer.
2026-10-29 10:03:20 +07:00
dotCooCoo ec988442b5 Layers: Alt-click on a layer name selects the layer's art
The reference app selects all objects on a layer with an Alt-click
(Option-click) on the layer's name in the Layers panel. In VectorCraft
an Alt-click on a name did what a plain click does: it highlighted the
row and made the layer current, and the art stayed unselected.

An Alt-click on the name of a layer or sublayer now also selects its
visible, unlocked art, as the row's selection square does
(layer.selectAll), and the layer still becomes current
(layer.setCurrent). On a locked or hidden layer the click clears the
selection, as that row's selection square does. On a group or object
row, beside the name, and with Shift or Ctrl/Cmd held as well, the
click does what it did before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 10:03:20 +07:00
dotCooCoo 5c1ab5042f Gradient: the Cmd/Ctrl-click reset paints only the selected characters and gives an unpainted stroke its weight
The reset ran paint.editGradient, which changes every run of a type
object and leaves a stroke without paint at weight 0. It now runs
paint.setFill or paint.setStroke with the linear White, Black gradient:
characters selected with the Type tool take it and the rest keep their
paint, and a stroke without paint gets the 1 pt weight, as a plain click
on the Gradient button already does.
2026-10-29 10:03:12 +07:00
dotCooCoo 85d7913f10 Gradient: Cmd/Ctrl-click on the Gradient Fill box or the Gradient button resets to White, Black
The reference app resets a gradient to black and white when the
gradient button in the Tools panel is Cmd/Ctrl-clicked, and to the
default black and white linear gradient when the Gradient Fill box in
the Gradient panel is Cmd/Ctrl-clicked. In VectorCraft a Cmd/Ctrl-click
on either did what a plain click does, and the reset was only in the
Gradient panel menu.

A Cmd/Ctrl-click on the Gradient panel's thumbnail or on the Tools
panel's Gradient button now makes the gradient of the active Fill or
Stroke a linear gradient with the White, Black stops, through
paint.editGradient, as one undo step. A solid or no paint becomes that
gradient, and with nothing selected the default paint changes. A plain
click keeps its behavior, and the panel menu's Reset to White, Black
is unchanged.

Docs: docs/target-app-parity.md.
2026-10-29 10:03:12 +07:00
dotCooCoo 62b231aaa5 Color panel: Cmd/Ctrl-click and Cmd/Ctrl+Shift-click on the spectrum apply Complement and Invert
The reference app selects the complement of the current fill or stroke
on a Cmd/Ctrl-click of the Color panel's color bar and the inverse on a
Cmd/Ctrl+Shift-click, and with Alt (Option) also held it changes the
nonactive one. In VectorCraft these clicks picked the spectrum's color,
and a Cmd/Ctrl+Shift-click cycled the color modes.

A Cmd/Ctrl-click on the spectrum now applies Complement and a
Cmd/Ctrl+Shift-click applies Invert to the active Fill or Stroke, as
the panel menu's items do: a selected gradient stop or freeform point
is recolored in place, and a solid color goes through paint.complement
or paint.invert. With Alt also held, the click runs the command on the
inactive proxy, which stays behind. A Cmd/Ctrl drag still picks colors,
and plain, Alt and Shift clicks keep their behavior. The panel menu's
Invert and Complement use the same code.

Docs: docs/target-app-parity.md.
2026-10-29 10:03:02 +07:00
dotCooCoo fa75b6f53d Part of #4326: a lost mouse-up after the pointer moved does not click at the press
The released buttons go after the frame's last pointer movement, at its
position. Before, they went first, at the press position, so a press that
moved past the click distance in the same frame ended as a click on the
widget under the press.
2026-10-29 10:02:50 +07:00
dotCooCoo f9fc20b3b9 Part of #4326: a drag whose mouse-up was lost ends once macOS reports no button down
egui ends a drag only on a button release or Escape. When the window
never receives the mouse-up, a drag such as a dock column's resize keeps
following the pointer with no button held.

On macOS, each frame's input now gets a release for every button egui
still holds when NSEvent.pressedMouseButtons reports no button down, at
the pointer's last position. Frames that carry a button event, and
control-channel input that holds a button between steps, are left as
they are.
2026-10-29 10:02:50 +07:00
dotCooCoo 7bc7ea58b5 Document: Cmd+Alt+Shift+U switches to the next unit of measurement
The reference app switches the units of measurement with
Ctrl+Alt+Shift+U on Windows and Option+Shift+Command+U on macOS.
VectorCraft could set the document's units with document.setUnits or
a right-click on a ruler, but no command stepped through them and no
key did it.

document.switchUnits (Switch Units, default Cmd+Alt+Shift+U) sets the
document's units to the next one in the order a right-click on a
ruler lists them: Points, Picas, Inches, Millimeters, Centimeters,
Pixels, Feet & Inches, Meters, Yards, Feet, and from Feet back to
Points. Each press is one undo step, and the reply names the new
units. Keyboard Shortcuts lists it under Document.

The precise cursors shortcut-editor test used Cmd+Alt+Shift+U as a
free key; it now uses Cmd+Alt+Shift+Q.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-10-29 10:02:27 +07:00
dotCooCoo a4ba07dd1b Width tool: Escape deselects the selected width points
With the Width tool, Escape did not deselect the selected width points:
they stayed selected, Delete still removed them, and in isolation mode
the same Escape left isolation mode. The reference app's shortcut list
gives Esc for deselecting a width point.

Escape now deselects the Width tool's selected width points. The path,
its widths and the undo history do not change, and no command is sent.
While width points are selected the tool takes Escape, so that Escape
does not also leave isolation mode. With no width point selected, and
during a drag, Escape does what it did before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 10:02:18 +07:00
dotCooCoo 7a2683d6d0 Stroke Weight: a spinner click from a weight shown as a preset goes past it
A weight of 2.004 pt shows as 0.167 p in picas, the same text as the
2 pt preset. One click down on the spinner went to 2 pt and the field
did not change. The spinner now treats a preset shown with the same text
as the weight as reached, as the field's arrow keys do, so the click
goes on to 1 pt. A plain step also passes a whole-point step shown with
the same text as the weight: 2.0004 pt goes down to 1 pt.
2026-10-29 10:00:43 +07:00
dotCooCoo c252d22465 Stroke Weight: a step from a weight between the presets goes to the next preset
A weight between the presets stepped to the next whole point, so 0.52 pt
went down to 0 pt. In the Stroke panel, the
Control bar and the Properties panel, a plain step of the spinner, the
arrow keys or the wheel now goes to the nearer of the next whole point
and the next preset in its direction: 0.52 pt goes down to 0.5 pt and
up to 0.75 pt, 0.4 pt goes down to 0.25 pt, and 1 pt goes down to
0.75 pt. From 1 pt up a step is still a whole point (14 pt to 15 pt),
and Shift and Ctrl/Cmd steps are unchanged.

In the field, a preset keeps its decimal places (0.0156 in), and a preset
the field shows with the same text as the value (2 pt as 0.167 p) counts
as reached, so the next press goes on to the next preset.
2026-10-29 10:00:43 +07:00
ham9zah 4661615af4 Properties: test that typed transforms turn about the reference point (#4285) 2026-10-29 10:00:18 +07:00
dotCooCoo 8e93155ed9 Tests: the idle cursor test renders every frame on the test thread
the_canvas_hands_the_tool_cursor_to_the_os_and_idles_while_the_pointer_rests
asserts that the window asks for no repaint once the pointer rests. On a
loaded machine a canvas frame takes longer than 8 ms, so later frames go
to the render worker, and a worker frame that finishes during the idle
frames asks for a repaint (repaint delay 0, cause render_worker.rs:63).
The test now sets canvas.worker_started, as the canvas tests do, so every
frame renders on the test thread.
2026-10-29 09:57:34 +07:00
dotCooCoo c3a6fbf250 Appearance: Add New Fill and Add New Stroke with an empty ids list run nothing without a selection
Called with `"ids": []` and nothing selected or targeted, the two commands
ran and put "select objects or give ids" in the status bar. An empty ids
list now counts as no ids, so the commands run nothing, as they do when
ids is left out. A non-empty ids list still adds to those objects.
2026-10-29 09:57:34 +07:00
ham9zah 411f96b7c8 Appearance: the object chip of a selection whose fills differ shows "?" (#4174)
The object row drew the None swatch when the selected objects' fills
differ, which reads as "no fill". It now draws the "?" chip the Control
bar and Properties panel use for differing paints.
2026-10-29 09:57:34 +07:00
el-j c96f0c30f7 perf(ui-egui): eliminate zero-filling allocation on canvas texture upload in color_image (#4504)
- Replace vec![Color32::TRANSPARENT; N] zero-initialization with direct single-copy bytemuck::cast_slice(&img.pixels).to_vec() in color_image.
- Eliminates redundant zero-filling of ~20.7 MB buffer on every canvas frame render on Retina displays, saving memory bandwidth and allocation overhead.
- Fallback preserves transparent image for undersized or mismatched pixel buffers.

Fixes #4504.
2026-10-29 09:57:34 +07:00
NetterSilverandClaude Haiku 5.5 26f9b7f80c Reject non-hex characters in Image Trace ignoreColor
Found while testing crates/ui-egui/src/panels/image_trace.rs:28-34 (ignore_color)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 08:44:45 +07:00
Dylan M. Taylor 55dddf6adf Extract UI event and rendering helpers 2026-10-28 16:44:17 +00:00
aiongg 13d837363d Dropdowns: menus grow back after a search with no matches (#4337)
* chore(deps): pin craft-libs to 62bb0e1d (shared combo box)

* fix(ui-egui): dropdown menus grow back after their contents shrink

Every combo box is craft_ui::ComboBox, whose menu follows its contents'
height while it is open (egui#5225); clippy.toml bans egui::ComboBox.

The font family menu is a craft_ui::combo::Menu under its field, with the
search field and filters in its header and the family rows scrolling under
them, still laid out only in view. The menu keeps to the room on its side
of the field, so the list's minimum height and the popup's room are gone.

* fix(ui-egui): the shortcut import's target picker grows back after a search

Its search field, "No equivalent" and the row's choices are the header of
a craft_ui::combo::Menu, and Keyboard Shortcuts' entries scroll under
them. That was the last menu on the popup path that capped itself at the
room left, so that path and combo's `searchable` flag are gone.

* chore(deps): craft-libs 60d60d48 (dropdown fixes)

* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)

* chore(deps): craft-libs 92d392cf (#387 merged)

* chore(xtask): pdf-deps expects craft-libs 92d392cf
2026-10-27 10:36:58 -04:00
ham9zah 0365cf3ae3 Appearance: shared rows map one by one, and effect dialogs edit each copy (#4174)
Review follow-ups:

- items_per_target maps each requested row on its own. Rows deleted or
  duplicated together, one shared and one not, fell back to the literal
  indices in every object, so the other objects could lose the wrong
  items. The first object takes its own rows; each other object takes
  its copies of the shared ones only.
- effect.dialog takes `aligned` (the panel passes it on a mixed
  selection's shared effect row), and its OK and preview run
  effect.setParams with it, so the dialog edits each object's own copy
  of the effect instead of the first object's index in every object.

Tests: removing a shared stroke with an unshared fill leaves the other
object's fills; an aligned dialog's command carries `aligned`.
2026-10-27 14:36:13 +07:00
ham9zah 8305ccf998 Appearance: a mixed selection lists the attributes its objects share (#4174)
With several objects selected whose appearances differ in anything (two
circles whose fills differ but whose stroke and effect are the same),
the Appearance panel showed only "Mixed Appearances": no stroke, effect
or opacity row, so attributes the objects share couldn't be edited
together. It now lists what they share, as the issue describes:

- `vectorcraft_doc::appearance::SharedStack` (with `align`, a stacking-
  order longest-common-subsequence match) gives the fills, strokes and
  object-level effects every object has an equal copy of, and where each
  is in every object's own stack.
- The panel's object row names the objects' common kind ("Path",
  "Ellipse"; "Mixed Objects" only when kinds differ, as the Control bar
  does), with an italic "Mixed Appearances" row under it when they
  differ, then only the shared rows with their usual controls. The
  Opacity row reads "Mixed" when opacity or blend mode differ (an edit
  sets them all). The object row shows no fill chip when the fills
  differ. Rows aren't dragged in a mixed selection: where a row would go
  in each of several different stacks isn't one place.
- The engine maps a shared row to each object's own item: the active
  item (a row of the first selected object's stack) always does, so the
  Color, Stroke and Transparency panels edit every object's matching
  item, and the item and effect commands do with `aligned: true`
  (appearance.setItem / removeItem / duplicateItem, effect.remove /
  duplicate / setParams / lightingPreset, transparency.set), each as
  one undo step. moveItem and effect.move refuse aligned rows of
  differing stacks. Without `aligned`, explicit indices keep their
  meaning for single objects and scripts.
- "Mixed" is added to every catalog.

Tests: the shared stack of the issue's case, of a stroke at another
index, of nothing alike and of equal stacks, and that matches never
cross; editing the shared stroke edits both strokes (at index 1 and 2)
in one undo step that restores both, also through stroke.set on the
active row; removing a shared effect and adding a fill reach both, and
reordering differing stacks is refused; the panel lists kind, note,
Stroke, Feather and Opacity, not Fill, the full stack for alike objects
and "Mixed Objects" for a path and type. The existing object-row test
now expects the kind for two rectangles and the shared rows when their
opacity differs.
2026-10-27 14:36:13 +07:00
nrq6993-a11y dfec712c2d G2: Scribble effect Settings presets
Squashed from #4274 onto main.
2026-10-27 14:36:12 +07:00
dotCooCoo ca60c8e8a7 Brushes: New Brush makes the brush from the art its options opened with
OK in a new scatter, art or pattern brush's Brush Options ran brush.new
without the art the dialog had captured for its preview, so brush.new
read the selection again. A selection changed while the dialog was open
gave the brush other art than the preview showed, and a cleared
selection made OK fail. OK now passes the captured art (the side tile
for a pattern brush) to brush.new.
2026-10-27 14:36:12 +07:00
nrq6993-a11y 678cff6707 G4: Optical margin menu uses shared text flag
Squashed from #4309 onto main.
2026-10-27 14:36:12 +07:00
aiongg e724228e25 docs: update source paths for the moved files 2026-10-26 22:01:25 +00:00
aiongg f21dc9e0a3 test(ui-egui): read file_dialog_open from app/files.rs in the picks scan 2026-10-26 22:01:25 +00:00
aiongg c50026fc86 refactor(ui-egui): move lib.rs items to {app,app/clipboard,app/commands,app/control,app/files,app/fonts,app/frame,app/ime,app/input,app/links,app/prefs,app/screenshot,app/view,caches,clock,services,state}.rs 2026-10-26 21:45:33 +00:00
aiongg 055d017603 refactor(ui-egui): move widgets.rs items to widgets/{blend,buttons,chips,color,drag,dropdowns,labels,numeric,panel_chrome,text,toggles}.rs 2026-10-26 21:45:33 +00:00
aiongg b3789b280c refactor(ui-egui): move shortcuts.rs items to shortcuts/{paste,presentation,typing}.rs 2026-10-26 21:45:33 +00:00
aiongg 556825b363 refactor(ui-egui): move panels/swatches.rs items to panels/swatches/{actions,body,dnd,libraries,tiles}.rs 2026-10-26 21:45:33 +00:00
aiongg d8a6f8f3d8 refactor(ui-egui): move panels/layers.rs items to panels/layers/{controls,model,rows,thumbs}.rs 2026-10-26 21:45:33 +00:00
aiongg 16cdbd1b33 refactor(ui-egui): move menus.rs items to menus/{bar,context,entries,invoke,labels,run,slots,state,submenus,tree,ui_commands}.rs 2026-10-26 21:45:33 +00:00
aiongg 5dfd01e1f1 refactor(ui-egui): move io.rs items to io/{export,open,save}.rs 2026-10-26 21:45:33 +00:00
aiongg bd1559b8fe refactor(ui-egui): move chrome.rs items to chrome/{control_bar,hints,status_bar,tabs}.rs 2026-10-26 21:45:33 +00:00
aiongg dbce5020f2 refactor(ui-egui): move canvas.rs items to canvas/{artboards,context,cursor,drag,grid,home,input,outline,overlays,raster,task_bar,tool_overlays,view}.rs 2026-10-26 21:45:33 +00:00
aiongg 303d9a0a4d test(ui-egui): read tl! literals past test-only imports 2026-10-26 21:17:08 +00:00
aiongg f1d427762b style: cargo fmt on files moved without formatting 2026-10-26 19:32:10 +00:00
aiongg 2b70dc2d9c refactor(ui-egui): move dialogs/revolve.rs tests to dialogs/revolve/tests.rs 2026-10-26 18:33:16 +00:00
aiongg 6909734d9e refactor(ui-egui): move panel_docking.rs test files to panel_docking/ 2026-10-26 18:33:16 +00:00
aiongg 1fa3699634 refactor(ui-egui): move layout_migration.rs tests to layout_migration/tests.rs 2026-10-26 18:33:16 +00:00
aiongg e5451f1579 refactor(ui-egui): move tests_*.rs to tests/<topic>.rs 2026-10-26 18:33:15 +00:00
aiongg ebccd3a9ed refactor(ui-egui): move screen_color.rs tests to screen_color/tests.rs 2026-10-26 18:18:18 +00:00
aiongg ed276d89d4 refactor(ui-egui): move panels/libraries.rs tests to panels/libraries/tests.rs 2026-10-26 18:18:18 +00:00
aiongg a899981893 refactor(ui-egui): move opening.rs tests to opening/tests.rs 2026-10-26 18:18:18 +00:00
aiongg 17e36b62b1 refactor(ui-egui): move dialogs/save_pdf.rs tests to dialogs/save_pdf/tests.rs 2026-10-26 18:18:18 +00:00
aiongg b76a161783 refactor(ui-egui): move font_menu.rs tests to font_menu/tests.rs 2026-10-26 18:18:18 +00:00
aiongg 5052b3e5ac refactor(ui-egui): move dialogs/find_links.rs tests to dialogs/find_links/tests.rs 2026-10-26 18:18:18 +00:00
aiongg 29e311afdd refactor(ui-egui): move dialogs/save_options.rs tests to dialogs/save_options/tests.rs 2026-10-26 18:18:18 +00:00