Commit Graph
572 Commits
Author SHA1 Message Date
bflatastic 687f4c84b8 Control bar: Replace menu for selected symbol instances (#4121) 2026-10-30 11:04:24 +07:00
bflatastic 5d4b4b9b35 Type: Character panel › Show Snap to Glyph Options (#4232) 2026-10-30 02:41:25 +07:00
bflatastic 513b02233f Align: Align to Glyph Bounds for point and area type (#4231) 2026-10-30 02:01:58 +07:00
bflatastic 5165b6eb1d Pathfinder: live compound shapes (Make, Shape Mode, Release, Expand Compound Shape)
Squashed from #4621 onto main.
2026-10-30 00:15:58 +07:00
dotCooCoo c3cd065ef3 Character and Paragraph Styles: Alt-click on New asks for the new style's name
Squashed from #4402 onto main.
2026-10-30 00:15:55 +07:00
dotCooCoo 0ee5fc0a9e Type: Cmd/Ctrl+Shift+U underlines, and Cmd/Ctrl+Alt+Shift+C and H toggle the line composer and hyphenation
Squashed from #4383 onto main.
2026-10-30 00:15:55 +07:00
el-j 49a05508ab fix(canvas): allow multi-selecting artboards via Shift-click on canvas label (#4484) 2026-10-30 00:15:54 +07:00
Ridzzz0Alam c403ad762f Properties: Properties doesn't offer Offset Path and Simplify for type alone (#2238)
The Quick Actions listed Object › Path's Offset Path and Simplify for every
selection, but both take paths: with type selected, the dialog opened and
could only fail ("Offset Path: select paths"). They are now left out when
every selected object is type, as Lock and Isolate Group are when they can't
run; live type offsets through the Appearance panel's Effect › Path ›
Offset Path, which works on type.
2026-10-29 23:39:21 +07:00
Taha-Mahmoodi 46cc03b50a Fix #4459: retain text properties when copying text across documents 2026-10-29 23:14:47 +07:00
VoiceLessQ 1411a821c1 Fix #3098: Image Trace traces off the UI thread, with progress and Cancel
Tracing a photo or a large image ran inside imageTrace.make on the UI
thread, so the window froze for as long as the trace took, with nothing
to say it was working.

A trace the user asks for (Object > Image Trace > Make and Make and
Expand, the task bar's Image Trace, the Image Trace panel's Trace and
presets) now runs on a thread of its own, as heavy plug-in filters do.
The status bar says so, and after 0.4 s a progress window shows how
many of the image's colors are traced, with Cancel and Continue in
Background (imageTrace.cancelRuns, imageTrace.hideRuns).

The result goes back to the command it was prepared for
(cmd::TraceRun, Traced::hand_in), which takes it only for the same
object, image and settings, so the edit is one undo step as before. A
newer trace of the same object replaces one still running, and a trace
whose image or settings changed meanwhile changes nothing. The control
channel, MCP, scripts and the web trace at once, as before.

The tracer reports its stages and color layers to a callback and stops
when it says so (trace_watched).
2026-10-29 22:39:34 +07:00
dotCooCoo 41dda1029f Character: Cmd/Ctrl+Alt+K highlights the kerning and Cmd/Ctrl+Alt+Shift+F highlights the font name
Squashed from #4390 onto main.
2026-10-29 20:48:21 +07:00
el-j 752bbb050d Fix #4567: vertically center numbers and text in input fields
In single-line text and numeric inputs across panels and dialogs, egui
0.37 defaulted TextEdit alignment to Align2::LEFT_TOP, placing the text
galley at the very top of the allocated input box (e.g. 1pt top margin,
11pt bottom margin in 26pt rows).

Align single-line TextEdit instances to Align2::LEFT_CENTER across
recessed_text, opt_field_named (dash/gap cells), hex fields, search
fields, and dialog inputs, matching DesignCraft's vertical centering.
Add regression assertions in look_tests verifying balanced vertical
spacing.
2026-10-29 19:21:33 +07:00
dotCooCoo cdf4453051 Libraries: gradients and patterns can be added to a library and applied from it
Squashed from #4442 onto main.
2026-10-29 19:21:33 +07:00
dotCooCoo c9853094d4 Image Trace: Manage Presets saves, renames and deletes custom tracing presets
Squashed from #4071 onto main.
2026-10-29 19:21:32 +07:00
dotCooCoo 3cfdb58b90 Transparency: pattern editing mode and opacity mask editing can't be entered from each other
Squashed from #4458 onto main.
2026-10-29 18:54:45 +07:00
dotCooCoo c8a7420b93 Place: an image whose pixels can't be read is refused instead of placed blank
Squashed from #4257 onto main.
2026-10-29 18:54:38 +07:00
nevaremandClaude Opus 5.5 a7db76bdfb Properties: selected ruler guides show their position in a Transform section
With ruler guides selected on their own (a click on one), the
Properties panel showed the Document sections, so a guide's position
could be typed only in the Transform panel.

The panel now has a Guides heading and a Transform section with the
Transform panel's guide fields (#1597): X moves the vertical guides and
Y the horizontal ones, together. With art selected too, it shows the
art's sections as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EjfPR4a1jPek7r3nyFFNYv
2026-10-29 18:46:47 +07:00
dotCooCoo e31e04e545 Part of #3137: Color Guide takes its base from colors chosen in Color, Swatches and the Color Picker
The Color Guide kept the base color it took when the panel first showed,
and only the base chip changed it. In a new document the current fill is
white, so every harmony and variation was white or gray until the base
chip was clicked with another current color.

A color chosen in the Color panel (spectrum, sliders, fields, chips and
Recent Colors), in the Swatches panel or a swatch library panel, or with
OK in the Color Picker for a fill or stroke now becomes the guide's base
color, and the guide shows its harmonies. A color applied from the guide
itself leaves the base as it is, and so does selecting artwork. The base
chip (Set base color to the current color) still makes the current color
the base. A new base clears the cells selected for Save Colors as
Swatches.

The base moves from egui's temporary memory to UiState::color_guide_base,
which is not saved, so the Color panel, the Swatches panels and the Color
Picker can set it.
2026-10-29 14:36:35 +07:00
bflatastic 1924ddd1dd Properties: one filter leaves out Lock and Isolate Group while they're off (keeps show() within the cognitive complexity lint #4299 added) 2026-10-29 11:22:51 +07:00
dotCooCoo dfe201dec3 Transparency: isolation mode and opacity mask editing can't be entered from each other
Enter Isolation Mode worked while an opacity mask was edited, and Edit
Opacity Mask and View Opacity Mask worked in isolation mode, so each
mode replaced the other's isolated art. In the reference app, isolation
mode can't be entered while a mask is edited, and mask editing can't be
entered in isolation mode.

object.isolate and perspective.editText, which shows type in isolation
mode, are now off while an opacity mask is edited, and a call returns
an error that ends "stop editing the opacity mask first". In isolation
mode transparency.editOpacityMask and transparency.viewOpacityMask are
off, and transparency.makeOpacityMask refuses one target, an object or
a layer, which it would take into mask editing; a call returns an error
that ends "exit isolation mode first". Make Opacity Mask with two
objects still masks one with the other. Pattern editing mode does not
count as isolation mode for these commands.

Object > Perspective > Edit Text, the canvas context menu and the
Transparency panel's menu and Make Mask button follow the commands'
enablement. A click or Alt-click on the mask thumbnail in isolation
mode shows the error and does not edit the mask. While a mask is
edited, the Contextual Task Bar leaves out Isolate, and the Properties
panel's Quick Actions leave out Isolate Group.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:20:08 +07:00
dotCooCoo a6a850868c Object: Lock, Hide, Unlock All and Show All are off in isolation mode
The reference app disables its Lock, Hide and Show All menus in
isolation mode. In VectorCraft every item of Object › Lock and
Object › Hide, Unlock All and Show All worked there.

In isolation mode, object.lock, object.lock.above,
object.lock.otherLayers, object.hide, object.hide.above,
object.hide.otherLayers, object.unlockAll and object.showAll are now
off. Their menu items are greyed out, and a call returns an error
that ends "exit isolation mode first". The Contextual Task Bar's
Lock button is greyed out too, and the Properties panel's Quick
Actions leave out Lock. Pattern editing mode and opacity mask editing do not count as
isolation mode for these commands. The check is outside_isolation in
cmd.rs, with has_selection_outside_isolation beside it.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 10:12:16 +07:00
dotCooCoo 5ed2fec4fd Text styles: the panel menu's Duplicate highlights the copy and Delete clears the highlight
Squashed from #4419 onto main.
2026-10-29 10:12:16 +07:00
dotCooCoo e28c08a8b8 Tabs: the position field's label is translated
The Tabs panel drew its X: and Y: label untranslated, so French showed
"X:" in place of its catalog's "X :" with a no-break space, and Chinese
showed an ASCII colon. The label now goes through the catalogs, and the
Polish and Vietnamese catalogs get their X: and Y: rows.
2026-10-29 10:12:16 +07:00
dotCooCoo b6f916d211 Character and Paragraph Styles: a style dropped on New is copied, and one dropped on Delete is deleted
The Character Styles and Paragraph Styles panels took only clicks on
their rows. The reference app copies a style dragged onto Create New
Style and deletes a style dragged onto Delete.

A style row can now be dragged. Dropped on Create New Style, the style
is copied with charStyle.duplicate or paraStyle.duplicate, as the panel
menu's Duplicate does, and the panel highlights the copy. Dropped on
Delete, the style is deleted with charStyle.delete or paraStyle.delete,
as the Delete button deletes the highlighted style, and Delete accepts
a dragged style even when the highlighted one is Normal or none. Normal
is not deleted. Each button is outlined while a row it accepts is held
over it, and each drop is one undo step.

Docs: docs/target-app-parity.md.
2026-10-29 10:11:41 +07:00
dotCooCoo 16d679fde7 Layers: Cmd/Ctrl+Alt-click places a new layer below the current one and a new sublayer at the bottom
The reference app places a new layer below the selected layer with
Ctrl+Alt-click (Command+Option-click) on the New Layer button, and a
new sublayer at the bottom of the layer list with the same click on
the New Sublayer button. In VectorCraft that click opened Layer
Options, and New Layer and New Sublayer always added the layer above
the current layer and the sublayer on top of the layer's contents.

layer.new takes below, which adds the layer directly below the current
layer at its level, and layer.newSublayer takes bottom, which adds the
sublayer under the parent's contents. In a clipping layer the new
sublayer goes above the clipping path. Each is one undo step, and
below is refused together with top. A Cmd/Ctrl+Alt-click on the
Layers panel's New Layer and New Sublayer buttons runs them. A plain
click, an Alt-click and a Cmd/Ctrl-click work as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 10:10:59 +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 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