Art dragged off the canvas and released over the Symbols panel's list
becomes a new symbol (symbol.new with the dragged ids), and a symbol
dragged out of the panel (PanelDrag::Symbol) is placed on the canvas
centred where it is dropped (symbol.place), showing its thumbnail at
the pointer on the way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-ups from the review of #205.
- Names that are user, file or system data are shown as they are: menu
items for recent files, saved views, recent fonts and Type > Font,
user swatch and graphic style libraries, saved perspective presets,
custom workspaces (and Reset <name>) and plug-ins no longer go through
the catalog (menus::shows_a_name). widgets::dropdown_names,
menu_item_name and dim_name are the non-translating forms of the shared
widgets, for the panels and dialogs that list such names.
- The command palette builds its entries (shown label, search text) once
per language and plug-in set instead of translating ~700 labels every
frame while a query is typed.
- The locale probe (reg.exe / defaults) runs on a background thread at
startup instead of on the first frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strings in code stay English and are the lookup keys; `crates/ui-egui/src/i18n`
maps them to display text at render time from one `*.tsv` catalog per language
(`LANGUAGES` registry, `tl!`/`t`/`tr`/`tr_ctx`/`tr_id`/`tn`/`fmt`). Command ids,
menu paths, `ui.menu.list`, the control channel, the CLI, MCP, status messages
and undo labels never see translated text, so agents, scripts and the existing
tests are unaffected (under `cargo test` the UI is always English).
Wired: in-window menus and the macOS native menu (rebuilt on a language change),
dynamic labels (Undo X, Reset X, Last Effect: X as templates), the command
palette (matches English and translated text), dock tabs, panel tooltips, the
toolbar (category names now cut by characters, not bytes), the dialog frame, the
shared widgets, the Preferences dialog, the shortcut editor, the hint and status
bars, the Home screen, and the literals of every panel and dialog.
Preference `interfaceLanguage` (User Interface › Language): `auto` or a code.
`auto` follows `VECTORCRAFT_LOCALE`, `LC_ALL`/`LC_MESSAGES`/`LANG`/`LANGUAGE`,
the macOS preferred languages and, on Windows (which sets no `LANG`), the user
locale from `HKCU\Control Panel\International`. `zh-TW`/`zh-HK`/`zh-MO`/
`zh-Hant-*` resolve to `zh-hant`; Simplified locales stay English until a
`zh-hans` catalog exists.
`zh-hant.tsv` (~2,850 rows) is clean-room, in the vocabulary used in Taiwan;
terms shared with PhotoCraft reuse its wording, vector terms use everyday
vector-drawing vocabulary. Tests: catalog well-formedness (placeholders,
ellipses, command ids, no Simplified characters), locale resolution, and two
coverage tests that fail when a menu string, panel/tool/preference label,
dialog button, hint-bar fragment or `tl!` literal has no row in a language
marked `complete_menus`. `VECTORCRAFT_I18N_DUMP=file cargo test -p
vectorcraft-ui-egui dump_source_strings` lists the strings for translators.
Also: two clippy findings under rustc 1.97 fixed in passing.
Integrated with main (squashed onto it, 42 commits on): the Czech interface
(#213) moves from its Rust table to `i18n/cs.tsv` on the registry (all 556
rows, Czech plural rule, its menu-coverage and glyph tests kept); Find Font's
resolution notes and the canvas context menu's labels are translated and
covered by the catalog tests (`menus::CONTEXT_LABELS`); the glyph test checks
CJK text only when the craft-fonts build input is embedded (#220 moved the
Japanese fonts out of this repository); a language saved by an older version
carries over to `interfaceLanguage`; Windows' locale lookup runs reg.exe by its
full path.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS keeps faces for its own interface under family names that start with
"." (".SF NS", ".LastResort", ".Aqua Kana"…); Mac apps don't list them. Since
the font menus list every installed family (#36), they showed 74 of them on a
stock macOS, at the top of every list.
- FontDb::menu_family_list: family_list without names that start with ".",
cached like it. Only a leading dot hides a family.
- Type › Font, the family dropdowns (Character, Properties), text.fontList and
Refresh Font List's count use it.
- Lookups by name still see every family: documents and fallbacks that name
a hidden family still find it (face, has_family, find_family, families,
text.fontList {family}, Fonts in Document's missing check).
- Test: tests_sysfonts::font_lists_leave_out_hidden_system_families_which_still_resolve
(a temporary font folder); the engine and UI font-list tests compare with
the listed families.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Type > Font, the Character, Properties and Glyphs panels and Find Font list
every installed font by default, as the reference app does, instead of the
four bundled families. The family dropdowns get a search field that filters
the list as you type (Enter picks the first match) and open on the current
font. Show System Fonts, which no longer means anything, becomes Refresh Font
List (text.rescanFonts): it picks up fonts installed since the app started and
redraws type set in them without editing the document. text.fontList lets MCP
agents list the families and a family's styles.
Root cause: the font lists came from FontDb::families(), which only held the
bundled fonts until something scanned the system fonts (fixed with #130). The
Type > Font menu, rebuilt every frame, now builds its items once per font
change, and renderers lay type out again when the fonts change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a window too short for them, the toolbar (tools, Fill/Stroke, colour
chips, drawing modes, Screen Mode, Edit Toolbar) and the dock's panel icon
column scroll with the mouse wheel, or with the scroll bar that shows while
the pointer is over them, instead of being cut off at the hint bar.
Root cause: both were drawn straight into their side panels with no scroll
area, and the toolbar's only fallback (two columns) runs out below about
760 pt. They now draw inside widgets::strip_scroll, a vertical ScrollArea
next to the menus' widgets::menu_scroll.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `*` or `/` operand had to be a bare number, so `1080/2 px` (the number
replaced by an expression, the field's suffix still there) read as nothing.
The unit after a factor or divisor now measures the whole expression, in
every unit (`Unit::parse`, shared by every unit field). Numeric fields
(num_field, opt_field, plain/mixed fields) select their whole text, unit
included, when they gain focus or are double-clicked, so typing replaces it;
unitless fields gain the same arithmetic (`parse_number`, `45*2°`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Menu bar menus, their submenus, panel menus, the effects menu groups and library
submenus are as tall as their items down to the window's bottom edge, and scroll
only past that (widgets::menu_scroll), instead of running off screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Artboards: All, a range or Full Document (one file per format: a PDF of
every artboard, other formats the bounds of all art), Include Bleed
(artboards grown by the document's bleed). Rows size files as a factor,
100w, 100h or 72ppi (suffixes @2x, @100w, @100h), and choose PNG, PNG 8,
JPG 100/80/50, WebP, GIF, SVG or PDF. Create Sub-folders (by size, by
format, or a row's own folder) and the presets Mobile 1x/2x/3x and
Density buckets (ldpi…xxxhdpi sub-folders). The gear opens Format
Settings: per-format options for every row of that format (the raster
and SVG options dialogs' rows, the PDF preset). Open Location after
Export shows the first file. The dialog has Artboards and Assets tabs.
The params of the last export are saved in the document
(Document.export_settings, not an undo step), read with
document.exportSettings, and the dialog reopens on them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening or placing a PDF or .ai with several pages or a password shows the
Import PDF / Place PDF dialog: a page preview with page navigation, Pages
(All or a range) when opening, Crop To (Bounding Box, Art, Crop, Trim, Bleed,
Media) and a password prompt for encrypted files.
- vectorcraft-pdf: ImportOptions.pages / crop / password, CropTo, info()
(pages, sizes, rotation, boxes); NeedsPassword / WrongPassword / BadPage
errors; images keyed by content so placed pages merge safely.
- document.open takes pages, page, cropTo (or crop) and password;
document.pdfInfo (query) lists pages and boxes, reports needsPassword and
renders a page thumbnail.
- file.place and file.place.info read only the page placed and take every
PDF box as crop (art, trim, bleed, media besides crop and bounding) and a
password; the UI's File > Place asks with the dialog when no page is given,
keeping Link, Template and the other place params.
- testkit::pdf writes hand-made PDFs with page boxes and RC4 encryption.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File > New opens a New Document dialog in the reference app's layout:
category tabs (Recent, Saved, Mobile, Web, Print, Film & Video, Art &
Illustration, Branding, Social) over cards of presets generated in code
(page-proportioned thumbnail, neutral name, size with units), and a
Preset Details column: name with Save Preset, width with the units
dropdown, height, portrait/landscape toggles, artboards, linked bleed,
Background Contents with its chip, collapsible Advanced Options (colour
mode, raster effects, preview mode), More Settings, Close and Create.
More Settings is the classic form of the same fields (profile and size
lists, artboard layout buttons, spacing and columns, Templates...).
Choosing a preset fills the fields; editing one makes them custom.
file.new takes every setting (preset, name, orientation, artboards with
artboardLayout {layout, columns, spacing, rightToLeft}, bleed,
backgroundContents, colorMode, rasterEffectsPpi, previewMode; sizes may
carry units). file.newPresets lists the categories; file.newPresets.save
and .delete keep user presets in the preferences; Recent remembers the
last sizes. Print presets and plain sizes start in Preferences > Units >
General, screen presets in pixels. Rearrange All Artboards shares the
grid layout code. The Home screen shows the same preset cards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File > Document Setup is a tabbed dialog over one engine command,
document.setup (no params: a query; any keys: one undo step;
document.setUnits stays as the units alias).
- General: units, bleed (four sides with a link toggle), Edit Artboards,
Show Images in Outline Mode (greyscale pixels under the frame),
Highlight Substituted Fonts/Glyphs (pink boxes on screen only).
- Transparency: grid size and colour presets or custom colours, Simulate
Colored Paper, the flattener preset (built-in or saved) and Discard
White Overprint (Overprint Preview keeps white overprints visible).
- Type: language with its quotes, Use Typographer's Quotes for the Type
tool and Smart Punctuation, superscript/subscript size and position and
small caps size (Character panel position/smallCaps; changing the
setup restyles the text in the same undo step), text export (SVG text
as text or outlines by default).
- The canvas draws a red bleed outline and the grid in the document's
colours and cell size as one textured quad, also in rotated views; a
white Background Contents exports raster formats opaque.
Model: Document.setup (DocSetup, defaulted, skipped when default) and
CharStyle position/small_caps; old files load unchanged.
tests_transparencygrid's helper now finds the grid by its texture (the
grid is one textured mesh instead of per-cell rectangles).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The General unit is the active document's units: Preferences > Units >
General sets them (one undo step) and is the units new documents start
in (and the unit shown with no document open); Document Setup changes
only the document. The Transform panel, Properties, the Control bar, the
Info panel, rulers, the status bar, Align spacing, Tabs, paragraph
indents, Pattern Options, dialog distances (Move, shape sizes, path
dialogs, Artboard Options, effect distances, Transform Each, parameter
dialogs), New Document and the canvas measurement labels (smart-guide
sizes, moves, the Measure tool, the Live Corners radius) all follow it,
live. Units > Type drives font size, leading and baseline shift; Units >
Stroke also reaches the Appearance panel and the Magic Wand weight
tolerance. Typed units ("5 mm", "12 pt") work in any field; all ten
units are choices; length preferences show in their unit and accept a
unit; offset/simplify/grid/type-size params read typed units. Typed text
starting with a multi-byte character no longer panics the parser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PdfSettings (serde, camelCase) is the Save PDF model: Standard, Compatibility
PDF 1.4-2.0, General (Preserve editing, Thumbnails, Fast web view, View after
saving, Create layers), Compression, Marks and Bleeds, Output, Advanced and
Security. PdfOptions = settings + pages, title and date.
document.exportPdf {path?, preset?, artboard?|artboards?|range?, standard?,
compatibility?, ...sections} -> {path|dataBase64, bytes, warnings} applies the
options over the preset (null keeps the preset's value); PDF/X, passwords,
PDF/A-2b at 2.0 and out-of-range values are refused; options accepted but not
applied yet come back as warnings. document.export {format: pdf} and
document.formats take and list the same options.
The Save PDF dialog (file.export.pdf, ui.savePdfDialog) has Preset,
Standard and Compatibility on top and a section list; it runs the export
before asking for a path, so bad options never open a save dialog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Window → Color Themes: the Create tab makes a five-colour theme on a
harmony wheel (a harmony rule from the selected colour, colours moving
together; or Custom, each on its own), with a brightness slider, hex
field, current-colour picker and name; the My Themes tab lists saved
themes to edit, add to Swatches as a colour group, or delete. Themes are
kept in the preferences (Prefs::color_themes; no online service).
Commands: colorTheme.list, colorTheme.save {colors | color + rule, name?,
replace?}, colorTheme.delete, colorTheme.addToSwatches. Harmony::theme
and move_on_wheel live in the colour crate; the wheel is now a shared
widget (widgets::harmony_wheel) used by Recolor Artwork's Edit tab too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Window > Attributes (Cmd+F11) shows Overprint Fill/Stroke, Show/Don't Show
Center, Reverse Path Direction Off/On, the non-zero / even-odd fill rule,
Image Map, a URL field with recent URLs and a Browser button, and a note.
New commands attributes.set and path.setFillRule; path.reverse takes ids and
reversed; attributes.info reports every value. Objects keep the attributes in
Node.attrs. SVG export wraps linked objects in <a>, and import reads links
back. The canvas draws the centre point of selected objects that show it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrowhead gains 30 original shapes generated in code, appended after the first
ten so saved names stay stable: Barbed, Half Arrow (left/right), Concave,
Double Arrow, Chevron, Feather, Swallowtail, Reverse Triangle, Triangle to Bar,
Kite, Leaf, Drop, Half Circle, Oval, Target, Tag (+ open), Diamond (open),
Hexagon (+ open), Star, Cross, Plus, Double Bar, Dot on Bar, Slash, Double
Slash, Bracket and Fork. Each is drawn in local units of the head's weight
inside the usual 4 x 4 box, with its own stop for the stroke under it.
Arrowhead::label() names them in the menus. The Stroke panel's arrowhead
dropdowns render every head as the canvas draws it (cached textures, a shared
widgets::doc_preview that the Brushes panel now uses too) in a scrolling list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Control bar and Properties Fill/Stroke chips show the proxies' paints
("?" for mixed selections) and open a popover with the Swatches panel body
(Shift-click or the toggle: the Color panel's mixer); a click brings the
chip's proxy forward through paint.toggleActive {fill}. The Properties
panel offers Recolor for multi-colour selections. Window panel shortcuts
(Color F6, Color Guide Shift+F3, Appearance Shift+F6, Graphic Styles
Shift+F5, Stroke Cmd+F10, Gradient Cmd+F9, Transparency Shift+Cmd+F10)
run window.panel {panel}, show in the Window menu and are editable.
paint.invert / paint.complement and the "?" state honour the Appearance
panel's active item; objects without bounds still show their paints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thumbnail, Small List and Large List views; thumbnails rendered on a
square or on type and cached by a hash of each style's look. Merge
Graphic Styles (named in Graphic Style Options), drag to reorder, delete
asks first (Alt-click does not), the right button held on a style shows
it large on the selected object. Styles dragged onto art apply to the
object hit; art dragged off the canvas or the Appearance thumbnail dropped
on the panel becomes a new style. Override Character Color (on by
default) clears type's character fill and stroke when a style with fills
or strokes is applied. Control bar Style picker.
Commands: graphicStyle.merge, graphicStyle.move, graphicStyle.setOptions,
ui.mergeGraphicStyles. Prefs: overrideCharColor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
paint.freeform.addPoint/setPoint/deletePoint/addLine/splitLine/selectPoint
and the paint.freeform.get query edit the freeform gradient behind the active
proxy (one undo step each; errors on other paints). The session keeps the
selected point. The Gradient tool on a freeform gradient selects and drags
points, adds points on the art (joined in Lines mode) and on lines, and
deletes the selected point. The Gradient panel shows the Draw toggle and the
selected point's colour, opacity, spread and Delete Point; the Color panel
recolours the selected point.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Weights and dash lengths show in Units > Stroke in the Stroke panel,
the Control bar and the Properties panel (Session::stroke_unit).
- Values the selected objects don't share show blank (weight, miter
limit, dashes) or light no button (cap, join, align), from a
per-revision cached DocState::stroke_mixed.
- Align Stroke Inside/Outside are disabled for open paths and type.
- The Control bar weight is a preset spinner with a width profile
dropdown, and its Stroke link (and the Properties panel's) opens the
Stroke panel as an anchored popover instead of docking it.
- The panels show a group's first painted object's stroke, as the
Stroke proxy does (Session::shown_stroke).
- stroke.set on a group skips images and symbol instances.
- document.inspect reports each object's strokeOptions (cap, join,
miterLimit, align, dash, dashOffset, alignDashes, arrows, arrowAlign,
profile) in stroke.set terms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Knockout Group is now Knockout {Neutral, Off, On} (old files' true loads as
On, false as Neutral). In a knockout group each child erases what the
children below it drew where it paints, then adds itself, so it composites
against the group's backdrop; neutral plain groups pass their children
through to the enclosing knockout group. transparency.set / object.setProps
take knockout "on"|"off"|"neutral"|bool; transparency.info reports the
state. PDF and SVG write knockout groups as nested soft masks with the same
look (PDF warns); the Transparency panel has a tri-state checkbox.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Gradient panel gains the Fill/Stroke proxy, a thumbnail menu of the document's gradient swatches with Save to
Swatches, Angle and Aspect Ratio fields with preset menus (the shared spin field), Show/Hide Options, and a stop
colour row whose eyedropper samples the next colour clicked on art into the selected stop (Eyedropper tool option
`stop`, `paint.sampleColor {stop}`). On the slider a selected midpoint diamond shows and sets its midpoint in
Location, double-clicking a stop opens the shared stop popover, Alt-drag copies a stop and Alt-dropping it on
another swaps their colours (`swap_stop_colors`), and a dropped colour swatch adds or recolours a stop. Clicking a
stop no longer falls through to the add-stop strip. Swatch tiles and the gradient thumbnail drag onto art, which
paints the object under the pointer through `paint.setFill`/`paint.setStroke {ids}`. The panel, the popover and
the Color panel share one stop-writing helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking a colour group's folder selects the whole group. Show Find Field
filters the panel by name. Tiles and list rows drag to reorder swatches,
move them into or out of colour groups or reorder groups (new command
swatch.move, one undo step); swatches and Fill/Stroke proxies dropped on
art paint the object under the pointer (paint.setFill/setStroke with its
id), and a proxy dropped on the panel becomes a swatch. List rows show
kind and colour-mode icons and take double-clicks; spot colours get a dot
in the grid. The search field widget is shared with the Layers panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
swatch.new takes mode and group; swatch.newGroup takes fromArtwork, toGlobal and includeTints and reports the group's swatches and the paints it linked. Adds a shared radio widget and the ui.newSwatch / ui.newColorGroup openers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking an Opacity row in the Appearance panel (a fill's or stroke's, or
the object's) opens a popup with the same blend-mode dropdown, opacity
field and slider as the Transparency panel (widgets::opacity_blend), and
edits that item through transparency.set. The blend-mode dropdown draws a
separator between the BlendMode::group() groups, also for the blend-mode
parameters of effect dialogs and inline effect editors. Fills and strokes
of type objects composite with their own opacity and blend mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a mask is edited, the Transparency panel and its commands act on the
masked object (Release leaves editing first). Mask commands and
transparency.set take ids and work without a selection; Make Opacity Mask
with one object adds an empty mask and enters editing. Opacity is 0..100 in
every command. transparency.info reports the panel's values with null for
mixed ones, shown blank. Saves and exports never include the editing layer
(Document::without_edit_modes; mask_edit is not serialized).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>