With the pointer resting over art while the view moves (trackpad or wheel
scrolling, pinch zoom, a Hand/space pan), the document point under the
pointer changes every frame, and the canvas hit-tested the art there on
each one, up to three times: Object Highlighting (canvas.rs
hover_highlight -> selection_hit_at) and the Selection tool's cursor
(select.rs cursor: hit_test, and with Snap to Point the nearest-anchor
search). Over a detailed engraving in a clip group (the reporter's
PhotoCraft icon: one path of tens of thousands of segments) each of those
costs roughly 3-6 ms, so every frame of the move took 12-15 ms of UI time;
off the art they return at once.
- canvas.rs: the canvas notes when its view (rect, zoom, centre,
rotation) last changed. Until it has been still for HOVER_STILL_SECS
(0.15 s) it draws no hover highlight and keeps the cursor the tool last
gave for the same tool and modifier keys instead of asking again; it
asks for a repaint then, so both come back without the pointer moving.
Pointer moves still reach the tool as before.
- test: a_moving_view_leaves_the_art_under_the_pointer_unhighlighted_until_it_stops
(wheel frames draw no hover outline and keep the cursor; once still,
the outline returns and the cursor follows the art). It fails on main
at the first wheel frame.
Measured on an Apple M4 (release build, 1440x900 window, the PhotoCraft
app icon SVG fitted at 138%, Selection tool, Smart Guides and Snap to
Point on), median UI frame time over 60 wheel-pan frames with the pointer
resting:
before: over the art 12.3-15.0 ms, off the art 1.0 ms
after: over the art 0.4-1.0 ms, off the art 0.6 ms
The render readout is the art's own raster time on the render worker,
which this change doesn't touch.
The panels' scroll areas used egui's default floating scroll bar, which
is drawn over the right edge of the content: in a window too short for
the Properties panel it covered the Image Trace button's dropdown arrow
and the W/H constrain link, and in a dock column too tall for the window
it covered the panels' menu buttons and right-hand controls. Menus
(#2086, list_scroll_style) and the toolbar and dock icon column (#808,
strip_scroll) had already been given bars that keep clear of their
items; the panel bodies hadn't.
- widgets: panel_scroll shows a panel's scroll area with the menus' slim
solid bar (list_scroll_style), in a column of its own at the right
edge; scroll areas inside the body keep their own style.
- dock.rs: the docked Properties panel and Properties in a flyout,
floating group or shared dock group use it.
- floating.rs: a dock column too tall for the window uses it.
- panel_docking.rs: a panel's body in the shared dock uses it, laid out
as wide as the scroll area leaves beside the bar; the shared dock's
icon column scrolls with strip_scroll, as the dock's icon column does.
- test: the_properties_scroll_bar_covers_no_control (a 1440x600 frame of
the dock with an image selected: no control's rect meets the
Properties scroll bar). It fails on main with "Tracing Presets" (the
Image Trace arrow) and "Constrain Width and Height Proportions" under
the bar.
Verified live at 1440x600 with an image selected: before, the hovered
bar sat over the Image Trace arrow and the right ends of the Quick
Action buttons, and a dock column's bar over the Swatches panel menu;
after, both bars sit in their own 8 pt column and nothing is covered.
With the Artboard tool the arrow keys did nothing to the artboards: the
tool claimed only Escape, Delete and Backspace (tools/src/xform/artboard.rs
claims_key), so the arrows fell through to object.nudge in
ui-egui/src/shortcuts.rs, which moved the selected art on its own (or
nothing, with no art selected) and left the artboard where it was.
- artboard.rs: the tool claims the arrow keys and turns each press into
one artboard.move of the selected artboards that aren't locked, by the
keyboard increment (Preferences > General > Keyboard Increment, read
from the tool context as Touch Type does), Shift ten times as far as
object.nudge does, with moveArt from the tool's Move/Copy Artwork with
Artboard option, as a drag sends it. One undo step per press; nothing
during a drag or with only locked artboards selected.
- tests: arrow_keys_nudge_the_selected_artboards (tools: claims, step,
Shift, moveArt, several boards, locked, mid-drag) and
artboard_tool_arrow_keys_move_the_selected_artboards_with_their_art
(engine: the artboards and the art on them move, one step per press).
Verified live over --control: a 400x300 artboard with a selected 40 pt
square, Artboard tool, ArrowRight: before, the artboard stayed at x 0 and
the square moved to x 51 ("Move"); after, the artboard goes to x 1 and the
square with it ("Move Artboard"); Shift+Down moves both 10 pt; with
Move Artwork off only the artboard moves; increment 5 moves 5 pt.
The drag case in the report: a drag already moves the art, but only the
art that lies wholly inside the artboard and is unlocked and visible
(doc/src/lib.rs art_on_artboard; locked and hidden art only with
Move Locked and Hidden Artwork with Artboard). Art overlapping the
artboard's edge stays behind by design, which may be what the reporter
saw when dragging.
## Summary
When the Paragraph and Properties panels act on multiple paragraphs with different hyphenation settings, the Hyphenate checkbox is off, matching Illustrator mixed-value behaviour alongside mixed indents, alignment and composer. Toggling still applies through `text.setFormat` to every paragraph in scope.
## Tests
- `paragraph_mixed_hyphenate_checks_off` in `vectorcraft-ui-egui` paragraph panel tests.
Part of gap G4
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Adobe help documents Area Type and Type on a Path as starting from a click on the path or shape border, not inside a filled interior. Restore outline-only hit testing for those tools (no fill-interior hits that would steal point-type clicks). Strokeless filled paths still accept clicks on the outline.
## Tests
- area_and_path_tools_hit_path_outline_not_fill_interior
Part of storytold/vectorcraft#3505 (review feedback on PR #3534)
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Area Type and Type on a Path locate the path under the click with the same hit rules as the other tools, so a filled shape with no stroke can be clicked in its interior. Illustrator accepts those clicks for area type and for type on a path (the start follows the click on the path). VectorCraft had forced outline-only hit testing here, so interior clicks missed and the tools fell back to ordinary point or area text with placeholder copy.
## Tests
- type_on_path_and_area_type_hit_filled_paths_without_a_stroke in vectorcraft-tools
Closesstorytold/vectorcraft#3505
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
The fill/stroke chip popover no longer closes while the pointer is down, a panel paint drag is active, or the opening click is still on the chip anchor, so dragging fill onto stroke (or the reverse) inside the popover can finish. Color Picker proxies are not added; the reference app edits only the colour the picker was opened for.
## Tests
- panels::tests::dropping_a_proxy_paint_on_the_other_proxy_applies_it (unchanged)
Closesstorytold/vectorcraft#3440
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Illustrator lets you drag the fill colour onto the stroke (or the other way) in the Color Picker and in the fill/stroke chip popover. VectorCraft now shows the Fill/Stroke proxy pair in the Color Picker (except for artboard background edits) and applies paint.setFill or paint.setStroke when a proxy paint is dropped on either square; clicking a square switches which side the picker edits. The fill/stroke chip popover stays open while the pointer is down, while a panel paint drag is active, or when the opening click is on the chip anchor, so drags between the proxies can finish.
## Tests
- color_picker_proxy_drag_applies_fill_to_stroke (headless egui)
- existing dropping_a_proxy_paint_on_the_other_proxy_applies_it unchanged
Closesstorytold/vectorcraft#3440
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Replace length/angle with Length X/Y, reference point, Type, Base Along, Slope and Fill Arc; extend geom and shape.arc params.
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
A click on the artboard with the Arc tool (no drag) opens an Arc options
dialog at the pointer, matching Illustrator. Length, angle, and Closed
follow the same form as Line Segment; OK runs shape.arc and remembers
Closed via tool.setOption.
## Tests
- dialogs::tests::arc_click_opens_options_and_ok_draws
- Existing draw2::family::tests::click_opens_dialog_and_arc_shift (tools)
Part of gap G11
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Exporting a CMYK document to PDF/X-1a or PDF/X-3 flattens transparency
first. Where the flattener has no single colour (an image with see-through
pixels over a flat fill, gradients, masks, raster effects) it renders the
area into an image, and that image was always an RGB PNG. The PDF writer
then separated it back to CMYK, so CMYK art behind the image came back with
other inks: a C100 M6 Y41 K3 panel printed C75 M14 Y35 K5 under a
transparent logo, with a hard edge against the rest of the panel, and K
moved into C, M and Y.
For files without transparency the flattener's images are opaque and
clipped to their regions anyway, so in a CMYK document it now draws them
as ink amounts over the paper (Renderer::render_region_inks) and stores a
CMYK TIFF, which the PDF writer keeps as DeviceCMYK samples. An area too
large for a CMYK image falls back to the PNG it made before. In-app
Flatten Transparency and other flattening, whose images keep alpha, are
unchanged.
Test: tests_pdfx::pdfx_1a_flattening_keeps_the_inks_behind_an_image_in_a_cmyk_document
exports a CMYK panel under a half see-through PNG to PDF/X-1a, opens the
file again and checks the inks behind the see-through half.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
When the Paragraph panel menu acts on multiple paragraphs with different composers (single-line vs every-line), neither composer item is checked, matching Illustrator mixed-value behaviour and the panel's other mixed fields (indents, alignment).
## Tests
- `paragraph_mixed_composer_checks_neither_menu_item` in `vectorcraft-ui-egui` paragraph panel tests.
Part of gap G4
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
ui.libraryPanel.locate decided whether a panel's library was missing
through missing_file, which answers only for a persistent panel. A
command that turns Persistent off leaves the panel open on its missing
library until the next drawn frame closes it, and while the window is
hidden the app draws no frame between control requests. Locate
Library… on that panel answered "isn't missing its library" and read
no file.
The check now asks whether the panel's library can be found, the
lookup the tab label and the per-frame tidy use. A panel whose library
is there is still refused, persistent or not.
window.swatchLibrary and window.graphicStyleLibrary had the same
check: given the library id of such a panel, they answered "no
library". They now bring the open panel forward and answer its file's
name with count 0; the panel still closes at the next tidy.
Docs: docs/mcp.md (ui.libraryPanel.locate, window.swatchLibrary).
## Summary
Package status notes are parenthetical fragments joined with spaces; templates without {note} are used when there are no missing links or skipped fonts, so folder paths are not followed by stray spaces or double gaps.
## Tests
- package_status_without_notes_has_no_extra_spaces
- package_status_message_templates_parse extended
Closesstorytold/vectorcraft#3494
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Package completion status and the show-folder prompt use i18n templates with
literal text between {folder} and {note}, so @msg rows parse and translate in
every locale (Illustrator-style localized status feedback).
## Tests
package_status_message_templates_parse; dialogs::tests_package; complete_languages_translate_every_message.
Closesstorytold/vectorcraft#3494
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Swatch library panels title imported and User Defined .acb color books from the file name (without the extension), matching Illustrator. Host temporary import paths still fall back to the book title when copying into the user library folder (#2996).
## Tests
- `color_book_tab_titles_use_the_file_name` covers user-folder scan, load by path, and copyToUser keeping the source file name.
- Existing `copying_a_color_book_from_a_temporary_path_uses_its_title` unchanged.
Closesstorytold/vectorcraft#3395
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
document.open with a path that is already open in another tab now activates
that document instead of reading the file again and opening a second window,
matching Illustrator Home › Recent Files when the file is already open.
The result includes activated: true; unsaved edits and the active tab are kept.
## Tests
open_same_path_activates_existing_document in cmd::fileio::tests.
Closesstorytold/vectorcraft#3455
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
The panels and dialogs draw in their own egui families (`ui`,
`ui-semibold`, `mono`), and egui panics drawing text in a family no
font is bound to. Only `theme::install_fonts` binds them, and only
`VectorcraftApp::logic` calls it, on its first frame. An embedder that
draws the panels or dialogs itself (`dialogs::show`, a panel's `ui`)
on its own context, without `logic`, panics on the first semibold
title it draws, an effect dialog's for one. Calling `install_fonts`
avoids that, but it embeds the interface's five faces (about 2 MB)
whatever the embedder wants: today such an embedder keeps them out of
its binary only by never calling it, so that the linker drops them.
vectorcraft-ui-egui gets an `interface-fonts` feature, on by default,
that embeds those faces. Without it `install_fonts` binds the same
three families to egui's built-in fonts, so an embedder can always
call it. Those lack some symbols the panels show (→ ▾ ● ✓), which the
docs say. With the feature, `install_fonts` installs exactly what it
did before: the same font data and the same family stacks.
`tests/install_fonts.rs` draws an effect dialog on a context only
`install_fonts` prepared, and checks the face at the head of each
family in both configurations. `cargo xtask ci`'s `no-bundled-fonts`
step runs it without default features and lints the crate that way.
Without the `install_fonts` call, or without the semibold binding,
both tests fail with egui's "FontFamily::Name(\"ui-semibold\") is not
bound to any fonts". The two lib tests about the interface faces are
adjusted: the graphics-loss test checks a family install_fonts binds,
and the catalogs' glyph coverage test needs `interface-fonts`.
docs/development.md says when an embedder calls `install_fonts`, and
that it replaces the context's font definitions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review feedback on #3498: dimmed neighbours share one opacity composite
before the centre tile and one after, preserving overlap paint order
without per-copy compositing.
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
In pattern editing mode, preview copies were drawn in row-major order and the
centre tile was always composited on top, so Pattern Options Overlap had no
visible effect when negative spacing made tiles overlap (as in Illustrator).
Preview copies now paint in the same order as tiled pattern fills.
## Tests
- vectorcraft-doc: preview_paint_order_respects_overlap
Closesstorytold/vectorcraft#3480
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>