Commit Graph
547 Commits
Author SHA1 Message Date
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 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 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
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 678cff6707 G4: Optical margin menu uses shared text flag
Squashed from #4309 onto main.
2026-10-27 14:36:12 +07: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 f1d427762b style: cargo fmt on files moved without formatting 2026-10-26 19:32:10 +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 aafbd39745 refactor(ui-egui): move panels/gradient.rs tests to panels/gradient/tests.rs 2026-10-26 18:18:17 +00:00
aiongg d129469e90 refactor(ui-egui): move panels/transform.rs tests to panels/transform/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 24a2b891fc refactor(ui-egui): move panels/text_styles.rs tests to panels/text_styles/tests.rs 2026-10-26 18:18:17 +00:00
aiongg dd22bf4b1d refactor(ui-egui): move panels/appearance.rs tests to panels/appearance/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 6ceee3e8e7 refactor(ui-egui): move panels/paragraph.rs tests to panels/paragraph/tests.rs 2026-10-26 18:18:17 +00:00
aiongg bfcd245bb6 refactor(ui-egui): move panels/color.rs tests to panels/color/tests.rs 2026-10-26 18:18:17 +00:00
aiongg fcb227a87c refactor(ui-egui): move panels/tabs.rs tests to panels/tabs/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 4d174fd4a6 refactor(ui-egui): move panels/character.rs tests to panels/character/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 36b6c513f7 refactor(ui-egui): move panels/brushes.rs tests to panels/brushes/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 66bddeaac7 refactor(ui-egui): move panels/library_panel.rs tests to panels/library_panel/tests.rs 2026-10-26 18:18:17 +00:00
aiongg ad496e9a98 refactor(ui-egui): move panels/align.rs tests to panels/align/tests.rs 2026-10-26 18:18:17 +00:00
aiongg dff3e525c2 refactor(ui-egui): move panels/swatches.rs tests to panels/swatches/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 11926af98d refactor(ui-egui): move panels/image_trace.rs tests to panels/image_trace/tests.rs 2026-10-26 18:18:17 +00:00
aiongg 32034021a4 refactor(ui-egui): move panels/artboards.rs tests to panels/artboards/tests.rs 2026-10-26 18:18:16 +00:00
aiongg 42e11117e2 refactor(ui-egui): move panels/properties.rs tests to panels/properties/tests.rs 2026-10-26 18:18:16 +00:00
aiongg 2377df07ee refactor(ui-egui): move panels/layers.rs tests to panels/layers/tests.rs 2026-10-26 18:18:16 +00:00
aiongg a042e3ff25 refactor(ui-egui): move dialogs/mod.rs i18n/mod.rs panels/mod.rs to foo.rs files 2026-10-26 18:18:16 +00:00
dotCooCoo 6ed4608d82 Images: an image past the 2 GiB decode allowance draws at reduced size instead of a crossed box
A PNG, JPEG, GIF or WebP image that takes more than the decode
allowance as RGBA (2 GiB, 512 MiB in the web app), such as a 24000 ×
24000 JPEG, was placed with the warning "too large to draw" and then
drew as a crossed gray box on the canvas and was left out of raster
exports. A 16-bit PNG whose decoded samples were past the allowance
was shown the same way, with the same warning, although its RGBA fits.

The renderer now decodes such an image at the largest size, keeping its
proportions, that fits the allowance as RGBA. Each pixel of the result
is the mean of the image's pixels it covers. A PNG that isn't
interlaced is read a row at a time; other images are decoded whole when
their decoded samples fit the allowance. A 16-bit PNG that isn't
interlaced and whose RGBA fits is read a row at a time at its own size.
An image of more pixels than the allowance has bytes still draws as the
crossed box, and so does one whose decoded samples are past the
allowance, other than a PNG that isn't interlaced. The renderer
decodes an image at reduced size only while the result and the memory
reading it takes (the image decoded whole, or the PNG reader's row
buffers) fit beside the images being painted; otherwise the image is
not decoded on that render (on the canvas it draws as the crossed box)
and is tried again on the next render. The document keeps the whole
file for saving and export. A placed document's saved preview past the
allowance is decoded the same way, at the size it is drawn at or at the
reduced size when that is smaller.

Open, Place and the Links panel say "an image of W × H pixels is shown
at reduced size" for an image drawn smaller. They say nothing for an
image drawn at its own size, and they say "too large to draw" for one
that is not drawn at reduced size.

Docs: docs/mcp.md, docs/target-app-parity.md (the crossed box's line
now names an image over the allowance that can't be drawn at reduced
size).
2026-10-26 21:41:42 +07:00
nrq6993-a11y b467f37d1d G4: mixed Small Caps and super/subscript in Character panel
Squashed from #4260 onto main.
2026-10-26 20:12:52 +07:00
dotCooCoo 7a815b0acc Type: text.convertListToText moves the Type tool's caret past the markers
The Type tool's anchor and caret were moved past the inserted markers
only by the menu item in the desktop app. When text.convertListToText
came over MCP or the control channel while the Type tool was editing,
they kept their old byte offsets, and the inserted characters (a bullet
is 3 bytes) could leave an offset inside a character.

The command now ends the Type tool's typing, then moves its anchor and
caret past the characters inserted at or before them. In threaded type,
when the caret's character moved to another frame, the tool edits that
frame, which becomes the selection when it was not selected. The menu
item runs the command without adjusting the selection itself.

Docs: docs/mcp.md.
2026-10-26 19:38:12 +07:00
dotCooCoo 2f445e117f Type: Convert to Text makes a list's bullets and numbers characters of its text
The reference app's Type › Bullets and Numbering › Convert to Text turns
the bullets and numbers of the selected list paragraphs into editable
text. VectorCraft drew list markers but could not convert them.

Type › Bullets and Numbering › Convert to Text (type.convertListToText,
text.convertListToText) puts each list paragraph's marker at the start
of the paragraph as characters: the bullet, or the number and its
period as the paragraph showed it, then a tab, in the marker's font,
size and color. The paragraph leaves its list. Where the marker's tab
went to the left indent, the paragraph gets a plain left tab stop
there, so the text after the tab starts where it did. Where the
marker's tab went to a stop with a leader, or to a right, center or
decimal stop, a plain stop where the tab ended keeps the converted tab
there without the leader; right, center and decimal stops between the
marker and that place are removed. Past the left indent this applies
only to a paragraph with no tab of its own. In threaded type the
numbers are those the story counted, and a paragraph that goes on into
the next frame is converted once, where it starts. A right-to-left
paragraph with no direction set gets the Right to Left direction when a
letter or roman numeral in front of it would make it run left to right.
The change is one undo step. The Type tool's caret stays next to the
character it was next to, and in threaded type it goes with that
character to the frame it moved to, which becomes the selection when it
was not selected.

With the Every-line Composer a paragraph that holds a tab is broken
line by line, so a converted paragraph can break its lines at other
places than the list paragraph did.

Docs: docs/mcp.md, docs/target-app-parity.md.
i18n: Convert to Text in the 24 catalogs.
2026-10-26 19:38:12 +07:00
Taha-Mahmoodi cfd085f34a Fix #3426: the Pathfinder panel's Pathfinder Options… sets Precision, Remove Redundant Points and Divide and Outline Will Remove Unpainted Artwork
Squashed from #4141 onto main.
2026-10-26 15:28:51 +07:00
Taha-Mahmoodi 0a241a7fdd Brushes: Window › Brush Libraries opens brush libraries whose brushes click or drag into the document's Brushes
Squashed from #4020 onto main.
2026-10-26 15:17:30 +07:00
Taha-Mahmoodi a34055beec Artboards: the Artboards panel's Delete Empty Artboards deletes every artboard with no artwork in one undo step
Squashed from #4142 onto main.
2026-10-26 14:10:36 +07:00
skylar taylor-barrick 581c79dcb1 M14: distinguish qualification from publication (#4173) 2026-10-25 23:47:58 -04:00
Taha-Mahmoodi 502b204d74 Transform panel: the menu's Transform Object Only, Pattern Only, Both and Use Registration Point for Symbol act
Squashed from #4146 onto main.
2026-10-26 10:04:25 +07:00
dotCooCoo df716c3118 Image Trace: Document Library traces with a color group from the Swatches panel
Squashed from #3975 onto main.
2026-10-26 09:25:42 +07:00
vangel76 b54a5e6765 Artboards: canvas menu to lock and unlock artboards (#2531)
Squashed from #2532 onto main.
2026-10-26 09:25:35 +07:00
dotCooCoo fd4c187ab5 Image Trace: settings that wait stay with their object when a plain image is selected
With Preview off, selecting a plain image after an Image Trace object
kept the panel on that object without saving its waiting settings. A
trace of the image then made the image's trace the panel's object, and
selecting the first object again showed its stored settings and lost the
change. The panel now keeps the waiting settings for the object it
leaves whatever is selected next.
2026-10-26 09:09:45 +07:00
dotCooCoo 3eda1b128c Image Trace: changes that wait with Preview off stay with their object
With Preview off, changes made in the Image Trace panel for one Image
Trace object were lost when another object was selected: selecting the
first object again showed its stored settings. The panel now keeps the
waiting changes for each object and shows them again when that object
is selected, unless the object's own settings changed in the meantime.
When Preview was turned on while another object was selected, the
object is traced with its changes when it is selected again.

Preview is now the UI command imageTrace.preview ({on?}: on, off or
toggle), and both the panel's checkbox and ui.set's imageTracePreview
run it. The command label has a row in every catalog.
2026-10-26 09:09:45 +07:00