Commit Graph

1038 Commits

Author SHA1 Message Date
bflatastic d0a99faf1e macOS DMG: generate DS_Store and TIFF from a script, attribute the assets (review of #493)
- packaging/macos/dmg/generate.py writes background.tiff and DS_Store on any OS (resvg with
  the repo's fonts; ds_store and mac_alias, as dmgbuild uses). Both are byte-reproducible and
  carry nothing from the machine that made them.
- DS_Store: same window, icon size, positions and background alias as before, without the
  bookmark (pBBk/pBB0) and the alias's disk-image record, which held the author's boot volume
  UUID and name, its creation date and temporary paths.
- background.tiff: the same pixels as before (checked), now opaque RGB with Deflate (1.8 MB to
  1.0 MB) and LittleCMS's built-in sRGB profile instead of an HP profile with no open licence;
  no file names or software tags.
- background.svg: drop an unused pattern and the xlink namespace (renders the same).
- ASSETS.md rows for the three files; README, docs/releasing.md (volume name) and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:36:43 +07:00
Xus Badia 884df22408 macOS DMG: branded window background and icon layout
The DMG opened as a plain Finder window. It now shows the app icon's figure on
the VectorCraft colour, with VectorCraft.app and the Applications link side by side.

- packaging/macos/dmg/: background (SVG source, 1x + 2x HiDPI TIFF) and the
  Finder view settings (DS_Store: window, icon size 128, positions, background).
- package.sh copies them into the staging folder. The image is still built with
  hdiutil makehybrid: no mounted device and no Finder scripting on CI.
- The volume name drops the version: .DS_Store finds the background through an
  alias that includes the volume name. The DMG file name keeps the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:36:40 +07:00
BFlat d3625f0b70
Merge pull request #499 from storytold/feat/task-bar-drag
Contextual Task Bar: drag it by its handle, pin and reset its position
2026-10-08 20:35:17 +07:00
BFlat 5abffd7622
Merge pull request #488 from wstock/feat/issue-394-auto-collapse
Part of #394: Auto-Collapse Iconic Panels
2026-10-08 20:34:23 +07:00
BFlat b5ffeeab57
Merge pull request #486 from wstock/feat/issue-394-bare-numbers-points
Part of #394: Numbers Without Units Are Points
2026-10-08 20:33:53 +07:00
BFlat 94d888a010
Merge pull request #484 from wstock/feat/issue-394-home-screen
Part of #394: Show The Home Screen When No Documents Are Open
2026-10-08 20:33:25 +07:00
BFlat 6641ffd225
Merge pull request #497 from storytold/fix/issue-485
Fix #485: the mouse wheel steps the focused numeric field
2026-10-08 20:33:03 +07:00
BFlat 810085ae56
Merge pull request #492 from storytold/fix/issue-483
Fix #483: Shift-drag marquees toggle the selection; the Lasso's Alt keeps the rest
2026-10-08 20:32:40 +07:00
BFlat bd9c32d384
Merge pull request #490 from storytold/fix/issue-482
Fix #482: Smart Guides land resize handles on anchors and snap the transform tools' drags
2026-10-08 20:32:22 +07:00
bflatastic d11daabdc7 Contextual Task Bar: drag it by its handle, pin and reset its position
The grip at the bar's left end now drags the bar anywhere on the canvas
(grab cursor), which always holds it whole, also when the window shrinks.
Unpinned, the bar keeps that offset as it follows the selection (in the
document it was moved in); the "…" button opens a More Options menu with
Hide Bar, Pin Bar Position (checked while pinned: the bar stays where it
is across selection changes and the handle still moves it; unpinned it
follows the selection again from there), Reset Bar Position (unpinned,
back under the selection) and Show Properties Panel, as in Illustrator.
As there, the position and the pin last for the session only.

- UI commands window.taskBar.pin {pinned?} and window.taskBar.reset, so
  the control channel and MCP reach them; ui.inspect reports taskBar
  {pinned, rect}.
- One opt_bool helper reads the {key?: bool} params of the toggling window
  commands (toolbarColumns, floatTools, collapseDock, taskBar.pin).
- New strings in ja, es, zh-hans, zh-hant, pt-br and cs; the dropped
  "Hide Contextual Task Bar" tooltip and per-key es messages removed.
- Headless egui test: dragging the grip moves the bar, the offset follows
  the selection, pin holds it across selection changes and a shrinking
  window, unpin follows from there, reset restores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:23:15 +07:00
bflatastic d728c47e6f Auto-Collapse Iconic Panels: dialogs keep the panel, the dock doesn't (review of #488)
- A press inside a dialog the popped-out panel opened (Swatch Options from its
  menu, say), or on a modal dialog's backdrop, put the panel away under the
  dialog. The press now counts as away only when it lands on no foreground
  layer (the flyout itself, dialogs, palettes) and not on the modal backdrop
  (`dialogs::modal::backdrop`), as the popovers decide a click elsewhere.
- A press in the expanded dock's panel group left the flyout open: only the
  icon column is spared now (its icons swap or close the flyout themselves),
  so a click in another docked panel puts it away, as a click anywhere else in
  the workspace does in the reference app.
- The test covers a click in a dialog, on its backdrop and in the docked group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:22:15 +07:00
wstock 948f696f52 Part of #394: Auto-Collapse Iconic Panels
Preferences › User Interface › Auto-Collapse Iconic Panels was stored
and never read: a panel popped out of the icon column stayed until its
» or its icon. On, a press away from the flyout now puts it away. A
press on the icon column is left to the icons (they swap or close the
flyout on release, so acting on the press would reopen the one being
closed), and a press in a popup the flyout opened (a panel menu, a
dropdown) keeps it. Off by default, as in Illustrator.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:22:14 +07:00
bflatastic 077e4f34ec Numbers Without Units Are Points: picas fields, on by default (review of #486)
The option tells points from picas, where a bare `12` could be either: the
reference app dims it unless a unit is Picas, and it starts on. A number typed
with no unit into a field in picas is now read in points with it on (the
default again, as before #486), and in picas with it off; fields in any other
unit read it in their unit either way, as there. The Preferences dialog dims
the checkbox unless one of the Units is Picas.

The fields learn the preference when it changes, through
`prefs_dialog::apply_runtime` as Scrub Numeric Fields does, rather than from a
write into the egui context every frame. Tests for picas and other fields, the
wiring and the dimming; docs/mcp.md and ROADMAP say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:22:11 +07:00
wstock b1aac0cc1b Part of #394: Numbers Without Units Are Points
Preferences › Units › Numbers Without Units Are Points was stored and
never read. On, a number typed with no unit into any length field
(`num_field`, `opt_field`, and so the spin fields built on them) is read
in points whatever the field's unit; a typed unit (`12 mm`, `1in`)
still wins, and the field keeps showing its value in its own unit. The
app sets the frame's rule from the preference in `logic`, the fields
read it through the egui context (`widgets::typed_unit`), so none of
the 45 callers change.

The default flips from true to false, which is Illustrator's default
and the behaviour the fields had while the option was unread.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:22:10 +07:00
wstock f1935596ef Part of #394: Show The Home Screen When No Documents Are Open
Preferences › General › Show The Home Screen When No Documents Are Open
was stored and never read: the Home screen always replaced the canvas
while no document was open. Off, the window is now empty while no
document is open (as when Illustrator's option is off), and the Home
button (`app.home`) still shows the screen; closing the last document
returns to the empty window. `menus::home_showing` is the one place
that decides, for the canvas and the Home button's lit state.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:21:42 +07:00
bflatastic 517b166722 Fix #485: the mouse wheel steps the focused numeric field
The wheel did nothing over numeric fields. As in Illustrator, once a numeric
field has the focus, turning the wheel over it steps its value like Up/Down:
a notch a step, Shift ten, Ctrl/Cmd a tenth, applied at once with the new value
selected.

- widgets::step_value (was step_with_arrows) reads the arrows and the wheel
  through one path for num_field, plain/mixed fields and the spin fields built
  on them (panels, the Control bar, dialogs, the Font Size and Leading combo
  boxes), and now opt_field (Stroke dash/gap) too. Trackpad and fine-wheel
  points add up to notches; the rest carries to the next turn.
- While the pointer is over the focused field the panel under it doesn't
  scroll; an unfocused field, or the pointer anywhere else, leaves the wheel to
  the panel and the canvas as before.
- The dialogs' and preferences' egui drag values become the same recessed
  number fields (widgets::range_field: clamped to their range, rounded to their
  precision), so they type, step and scroll like every other numeric field.
- Headless tests for the wheel (units, modifiers, points, focus and pointer),
  and for the panel scrolling or not; docs/control-protocol.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:21:39 +07:00
bflatastic 377f236687 Fix #483: Shift-drag marquees toggle the selection; the Lasso's Alt keeps the rest
A Shift-drag marquee only ever added to the selection, so there was no way to
take some of the selected objects out with it. As in Illustrator, Shift-drag
now toggles what the marquee reaches: the selected ones are deselected, the
others selected, the rest of the selection stays.

- Selection tool: the Shift marquee runs select.toggle, which now also takes
  {ids} (each one toggles).
- Direct Selection and Group Selection: the Shift marquee toggles the anchors
  inside it (select.anchorsMany mode "toggle"); a path left with none leaves
  the selection, one with all of them is selected whole again.
- select.anchorsMany takes mode "set"|"add"|"toggle"|"subtract" (add: true is
  still mode add). Adding anchors of a path selected whole keeps it whole, and
  anchor references outside the path are ignored.
- Lasso: Shift adds and Alt subtracts through the same command, so an Alt lasso
  no longer drops selected type, groups and other non-path objects.
- Tests at the tool, engine (Session::pointer) and MCP (pointer_gesture) levels,
  command sweep cases for the new modes; docs/mcp.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:21:36 +07:00
bflatastic f7c88af1de Fix #482: Smart Guides land resize handles on anchors and snap the transform tools' drags
Bounding-box resizes with the Selection and Free Transform tools already lined
their edges up with the other objects (#355, after v0.4.0), but a handle never
landed on another object's anchor point, nothing was labelled, and the Rotate,
Scale, Shear and Reflect tools didn't snap their drags at all.

- Targets::snap_scale: the dragged corner (free, or along its diagonal with
  Shift) or side lands on the nearest anchor or centre within the snapping
  tolerance, labelled "anchor"/"center"; alignment snaps keep their line and
  now say "align", as an artboard's resize handle does. Alt (from the centre)
  and rotated boxes work the same way.
- The transform tools start a drag from the anchor or centre they grabbed (the
  point a click would set) and snap the pointer as drawing tools do (Smart
  Guides, the grid, pixels or Snap to Point), so a corner dragged onto another
  object's anchor scales exactly onto it.
- Factor the nearest anchor/centre lookup (Targets::nearest_point) and the
  transform tools' drag teardown (end_drag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:21:31 +07:00
BFlat 70b1bbf679
Merge pull request #496 from storytold/fix/issue-491
Part of #491: pen taps click where they tap; Linux note for tablet pens on Wayland
2026-10-08 20:20:11 +07:00
bflatastic 744edddaea Part of #491: pen taps click where they tap; Linux note for tablet pens on Wayland
Pens on KDE Plasma 6.3+ under Wayland don't reach the app: winit 0.30 binds no
Wayland tablet protocol (zwp_tablet_v2), and since Plasma 6.3 KWin no longer
emulates pointer input for such clients (kwin!6336), so the pen moves KWin's own
cursor while the window gets no motion or presses. The fix is upstream (winit
0.31's tablet input, winit#4318, once eframe moves to it). docs/development.md
and the README give the workarounds: run under XWayland (WAYLAND_DISPLAY=), or
set KWIN_WAYLAND_EMULATE_TABLET=1 for the session.

On our side, a pen or finger (winit Touch, emulated as a mouse by egui-winit)
lifts with an egui PointerGone in the same frame. A tap whose press and lift
arrived in one frame had no hover position, so the canvas ignored it, and a
drag ending without a pointer position ended at the canvas's centre. The press
and double-tap now fall back to the interact position, and the release to the
last position seen during the drag. Headless frame tests replay the events a
pen produces: a stroke with pressure reaching Use Pressure Pen, and a one-frame
tap that selects without moving (fails without the fix).

ROADMAP: Last updated, the Platforms row, the hardening gap and the graphics
processor/input bullet (pen pressure reaches the tools on Windows only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:14:18 +07:00
Brandon Thomas d9fc3b6505 Bump version to 0.5.0 (from 0.4.0) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:13:01 -04:00
BFlat c61683bfd6
Merge pull request #478 from wstock/fix/issue-394-pixel-grid
Part of #394: Show Pixel Grid (Above 600% Zoom)
2026-10-08 19:10:19 +07:00
BFlat 21c3581282
Merge pull request #477 from wstock/fix/issue-394-anti-aliased-artwork
Part of #394: Anti-aliased Artwork
2026-10-08 19:08:05 +07:00
BFlat c877607676
Merge pull request #475 from wstock/fix/issue-394-type-path-only
Part of #394: Type Object Selection by Path Only
2026-10-08 19:07:03 +07:00
bflatastic 7578787392 Canvas grid: turn with a rotated view, no per-frame vectors (review of #478)
The pixel grid draws through the document grid's `grid()`, which assumed an
unrotated view: with Rotate View its lines stayed upright on screen, in the
wrong places, or vanished. It now spans the box round the canvas's corners in
document space and draws each line through the view, so the pixel grid and
the document grid (lines and dots) turn with the art. The lines are iterated
rather than collected, capped, and computed by multiplication instead of
accumulating a step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:00:03 +07:00
wstock aa8e77d09a Part of #394: Show Pixel Grid (Above 600% Zoom)
Preferences › Guides & Grid › Show Pixel Grid (Above 600% Zoom) was stored and
never read. On (the default), in View › Pixel Preview at 600% zoom and above the
canvas draws a translucent line at every document pixel over the art, so the
pixels the art rasterizes to can be seen; nothing below 600%, out of Pixel
Preview, or with the option off. Drawn by the same `grid()` as the document
grid, at a 1 pt spacing.

A headless frame test counts the pixel grid's lines at 800%, 500% and 600%, in
and out of Pixel Preview and with the option off, and checks they are drawn
over the art.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 19:00:03 +07:00
wstock 349e1eac3c Part of #394: Anti-aliased Artwork
Preferences › General › Anti-aliased Artwork was stored and never read. Off, the
canvas renders the art with `AntiAlias::None` — a pixel is painted when the art
covers at least half of it, raster effects and pattern tiles staying smooth — and
on (the default) with `AntiAlias::Art`, as before. The preference is part of the
canvas's cache key, so changing it re-renders. Exports keep their own
Anti-aliasing option.

A headless frame test reads the art's raster back out of the frame's texture
upload: edge pixels partly covered with the option on, none off, and smooth
again once it is back on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 18:59:51 +07:00
bflatastic afb438367d Type Object Selection by Path Only: every baseline, Type tool and Eyedropper (review of #475)
- Type is picked on the baseline of any of its lines (point type and area
  type, besides area type's frame), as in Illustrator, not only the first
  one: the layout's baselines are cached with its bounds
  (`TextObject::cached_baselines`, from `TextLayout::baselines`), with the
  first one across the bounds as the fallback. Vertical type's runs down
  each column's centre line.
- Type in perspective is picked where it is drawn (through its projection).
- The bounds rule type out before its path is built, so hovering stays
  free of allocations away from type.
- The Type tools still edit type clicked among its characters (they made
  new type there with the preference on), and the Eyedropper still samples
  it there, as it does inside fills with Object Selection by Path Only.
- Tests for all of it; docs/mcp.md and ROADMAP say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:59:47 +07:00
wstock beea693ee1 Part of #394: Type Object Selection by Path Only
Preferences › Type › Type Object Selection by Path Only was stored and never read.
On, the Selection tools pick type on its type path only — point type's baseline
(the line the selection marks under it, across its layout's width), area type's
frame, type on a path's path — within the selection tolerance; a click among the
glyphs or inside the frame selects nothing. Off (the default), anywhere in the
type's bounds selects it, as before.

`HitOptions` gains `type_path_only`, set by the tools' context (`hit_options()`)
and the canvas's `hit_at` from the preference, so the Selection tools, Cmd-click
behind, object highlighting and the context menu agree. Tests at the hit-test
level (point, area and on-path type) and through the Selection tool with
`prefs.set`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 18:59:47 +07:00
BFlat 40d6975b02
Merge pull request #489 from storytold/fix/issue-487
Fix #487: Keyboard Shortcuts records chords with modifiers
2026-10-08 18:58:11 +07:00
bflatastic 6dc6b8600b Fix #487: Keyboard Shortcuts records chords with modifiers
egui reports a modifier's own key press (ShiftLeft, ControlLeft…) as a key event, and the
recorder took the first key press it saw, so pressing Shift+A saved `ShiftLeft` as the chord: a
shortcut that can never fire. The recorder now skips modifier key presses, so the chord completes
on the first other key with the modifiers held (Esc still cancels). A modifier is never a chord's
key in the shortcut parser either, so `shortcuts.set` refuses one, and overrides saved that way by
older versions are dropped on start, giving the default back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:55:02 +07:00
BFlat 4974d658e5
Merge pull request #481 from storytold/fix/eps-vector-import
EPS import: other apps' EPS open as vectors, a preview that stands in says why
2026-10-08 18:31:59 +07:00
bflatastic 4827780f03 Fix #474: Illustrator 8 EPS gradients open (executable strings, `load` by integer keys)
Stock EPS files saved in the Illustrator 8 format failed with "typecheck in `a
dictionary`": its gradient procset builds shading dictionaries with `bd` …
`ed`, where `ed` is `(>>) cvx` (or a Level 1 dictionary builder as a
string), and `cvx` left a string literal, so `shfill` got a string. Its
`discard` procedure also `load`s procedures by integer keys, which `load`
refused.

- Executable strings: `cvx` makes a string executable (`xcheck` true,
  `cvlit` back, still a `stringtype` for `length`/`get`/`forall`/
  `getinterval`); run by name or met in a procedure, it runs its text as a
  program, every time (an executable file shares the same runner).
- `load` takes any key (integers, booleans), as `where` already did.
- Test: an Illustrator 8-style gradient procset with both `ed` forms and
  `discard`; the import fuzz vocabulary has executable strings and integer
  keys. An Illustrator 2026 file saved in the Illustrator 8 format (made
  here, not committed) now opens as vectors with its radial gradient.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:24:05 +07:00
bflatastic 9f3b992ecc EPS import: other apps' EPS open as vectors, a preview that stands in says why
Discord feedback: "EPS files are always rasterized: they open as bitmaps, in
other programs as vectors". Every EPS saved by Illustrator 2026 fell back to
its TIFF preview (`gcheck` was unknown), and cairo/Inkscape files stopped at
their first image (`status`), dropped masked images (ImageType 3) and failed
on mesh gradients; the warning didn't say where the program failed.

Fallback never silent:
- The warning names the PostScript error, the operator that raised it and
  the named procedures it ran in, innermost first ("a PostScript error
  (typecheck in `add`), in `inner` in `outer`"); errors caught by `stopped`
  aren't reported. It reaches the status bar, document.open, app.open and
  file.open (which now return document.open's result, so MCP's open_file over
  the control channel gets it), and `vectorcraft-cli info` ("warnings").
- A file whose PostScript can't be read opens as its TIFF preview, else its
  Windows metafile preview (as vectors, scaled to the bounding box), else its
  EPSI bitmap. es @msg rows for the new messages.

Interpreter:
- Operators programs probe or use: gcheck/scheck, rootfont,
  currentcolorrendering, currenthsbcolor, status, token, read,
  bytesavailable, fileposition/setfileposition, resetfile, write*, pdfmark
  (marks dropped, as Ghostscript does), execform, nulldevice, strokepath,
  pathforall, user paths (ufill/ueofill/ustroke/uappend/upath/setbbox, the
  encoded form too), setcachedevice2, findcolorrendering, cache, device and
  VM queries, revision; (%stdout) (w) file gives a sink.
- `//name` is replaced by its value when read, inside procedures too.
- Tiling patterns become pattern swatches (their PaintProc drawn in pattern
  space, one cell; uncoloured ones in the colour under them; makepattern
  copies share one swatch); nested patterns stop at 8 levels.
- Type 3 fonts draw their glyphs (BuildGlyph/BuildChar, widths from
  setcachedevice/setcharwidth) as outlines grouped per string, spaced by
  ashow/widthshow/awidthshow/xshow/yshow/xyshow/kshow; glyphshow and
  stringwidth/cshow use them.
- Shadings of types 1 and 4-7 become gradient meshes (data from arrays,
  strings or files, packed or not, flags sharing edges), as fills through a
  shading pattern too; sampled functions (type 0) are evaluated;
  GradientMesh::coons/triangle are shared with PDF import.
- ImageType 3 masked images, interleaved by sample, by row or in separate
  sources; ReusableStreamDecode reads its data when made; Flate/LZW PNG and
  TIFF predictors.
- Bounded: DeviceN at 32 inks, sampled functions at 2^24 samples and 32
  outputs, 4096 patches a shading, strokepath ignores dashes finer than 1e5
  pieces, Type 3 glyphs nest 8 deep (a glyph that shows itself overflowed the
  stack before the limit), 64-bit index maths for 32-bit targets.

Tests: a synthetic program per generator (cairo/Inkscape, its Type 3 fonts,
matplotlib, CorelDRAW, Affinity, LibreOffice, Ghostscript eps2write,
Illustrator procsets), meshes and function shadings, masked images, fault
messages, probing operators, metafile/EPSI previews, `//name`, user paths,
hostile patterns/glyphs/shadings (run against main, 12 of the 14 generator
and feature tests fail; CorelDRAW- and LibreOffice-style programs already
read there). The import fuzz vocabulary covers the new operators. Checked
against real files made here with Inkscape 1.4 (cairo) and Illustrator 2026
(its own format and the CS-to-CC compatibility formats; all fell back or
stopped early on main and open as vectors now; its Illustrator 8 format
needs the next commit); those files aren't committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:23:36 +07:00
BFlat 690e770c92
Merge pull request #480 from storytold/fix/issue-479
Fix #479: Add/Delete Anchor Point tool icons drawn from the Pen tool's
2026-10-08 18:22:47 +07:00
bflatastic 13d5645052 Fix #479: Add/Delete Anchor Point tool icons drawn from the Pen tool's
The two tools had hand-drawn nibs that didn't match the Pen tool's icon and read as distorted
next to it. They now use Lucide's pen-tool (the Pen tool's own icon) with a plus or minus badge,
so the Pen group reads as one family; the Remove Anchor Points buttons pick up the minus one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:17:59 +07:00
BFlat 6f455f9616
Merge pull request #476 from storytold/fix/issue-472
Part of #472: Escape leaves Presentation Mode; dropped documents open as tabs; .ai off-artboard note
2026-10-08 18:06:12 +07:00
bflatastic e98a880e75 Part of #472: Escape leaves Presentation Mode; dropped documents open as tabs; .ai off-artboard note
Presentation Mode hides the menus and panels, had no Escape and was saved with the
preferences, so it came back on every restart with no way out. Escape now leaves it (as in
Illustrator), F from it goes back to Normal Screen Mode, and the screen mode is no longer
saved: the app always starts in Normal Screen Mode, and preferences saved in Presentation
Mode by older versions open normally. ui.inspect reports `screenMode` on its own.

Desktop file drags carry no pointer position or modifiers (winit 0.30 on Windows, macOS
and X11), so every drop on a window with a document open was taken for the middle of the
canvas and placed. Where the platform doesn't say, a dropped document (native, SVG, PDF,
.ai, EPS, DXF, EMF/WMF) now opens as a tab of its own and a picture or text is placed in
the middle of the view; pictures dropped together with documents are placed first, so the
last document opened is shown. The web, whose drag events give the position, keeps
Illustrator's choice: placed on the canvas, opened off it (the tab bar).

Illustrator's PDF-compatible part leaves out art outside the artboards (it lives in the
private data alone, which isn't read), so it can't open; importing a file with that
private data now says so in a note. Off-page art a PDF does draw already opens on the
pasteboard; a test keeps it so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:01:12 +07:00
BFlat 44e4e63f22
Merge pull request #463 from imachus/pr/ja-burasagari
Type: burasagari — a Japanese comma or full stop ending a line may hang outside it
2026-10-08 17:42:42 +07:00
bflatastic 10a87311d3 Type: burasagari labels Regular/Force, full-width commas and full stops hang, MCP docs (review of #463)
- The Paragraph panel menu's Burasagari submenu reads None / Regular / Force, as the reference
  app's help names them (`text.setFormat` keeps "none"/"standard"/"forced", its scripting names);
  Reset Panel goes back to new type's Regular.
- The full-width ,.and half-width 、。 hang too; Latin commas and full stops never do, so Latin
  text keeps its breaks and composer with new type's default (tested). The spaces after a hanging
  mark stay on its line.
- docs/mcp.md: a Burasagari section. ROADMAP: wording, Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:05 +07:00
imachus eb38894759 Type: new type takes Standard burasagari, as in Illustrator; Latin paragraphs keep the every-line composer
- New type (the Type tools, `text.create`, area type and type on a path) takes Standard
  burasagari, Illustrator's default (observed in its Paragraph panel menu). Documents from before
  it and imported text keep None.
- Only a paragraph with a Japanese comma or full stop is composed line by line, so Latin text keeps
  the every-line composer with the new default (tested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:05 +07:00
imachus 08e7a4f713 Type: burasagari — a Japanese comma or full stop ending a line may hang outside it
Paragraph panel menu › Burasagari › None / Standard / Forced (with the East Asian options) and
`text.setFormat {burasagari}`:

- None: a comma or full stop that doesn't fit goes to the next line with the character before it.
- Standard: one that doesn't fit hangs outside the line; one that fits stays inside.
- Forced: one ending an area type line always hangs, and the rest of the line fills the measure.

Only 、 and 。 hang (closing brackets don't), in the width their mojikumi gives them. Horizontal
and vertical. Saved only when not None; new type keeps None. Paragraphs with burasagari are
composed line by line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:05 +07:00
BFlat 58330fba83
Merge pull request #473 from storytold/feat/pen-anchor-editing
Pen and anchors: Alt edits one handle, cut and convert selected anchors from the Control bar
2026-10-08 17:26:26 +07:00
BFlat 47209d3724
Merge pull request #471 from storytold/feat/control-bar-images
Control bar: image controls (Image Trace, Embed, Crop, Mask) when an image is selected
2026-10-08 17:26:08 +07:00
bflatastic 253249717b Pen and anchors: Alt edits one handle, cut and convert selected anchors from the Control bar
Discord feedback: Alt with the Pen didn't move a direction handle on its own, and there was no
way to break a path at an anchor and pull the ends apart.

- Pen: Alt held over a handle end or an anchor of a selected path (the one being drawn too) hands
  the gesture to the Anchor Point tool, with the convert cursor: dragging a handle moves it alone,
  clicking a smooth anchor makes it a corner, dragging an anchor pulls out symmetric handles.
  Drawing goes on afterwards. Handles hidden by a partial anchor selection aren't grabbed.
- path.cutAtAnchors cuts at the direct-selected anchors in one undo step: a closed path opens
  there, an open one becomes one path per piece, and one anchor of each cut stays selected so a
  drag pulls the path apart. It answers {ids} and refuses (no undo step) when only open ends are
  selected. path.convertAnchors checks `to`, skips stale anchor refs instead of indexing past
  them, and keeps a smooth anchor's handles when converting to smooth.
- SubPath::cut_at and SubPath::smooth_anchor (geom) now back path.split, path.cutAtAnchors,
  path.convertAnchor and path.convertAnchors, replacing three copies of that logic.
- Control bar and Properties panel: "Convert:" corner/smooth and "Anchors:" remove, connect
  (Join), cut buttons when anchors are selected; Cut Path on the task bar and the context menu.
  New original icon dc-anchor-smooth.svg (ASSETS.md row).
- ja, zh-hans, zh-hant and es strings (cs for the context menu item), es @msg rows; docs/mcp.md
  section; ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:16:12 +07:00
bflatastic d6f8cc879f Control bar: image controls (Image Trace, Embed, Crop, Mask) when an image is selected
Discord feedback: Image Trace and the other image options were hidden when an image was
selected; copy, paste, Image Trace should work from the Control bar as in Illustrator.

- Control bar, one image selected: Linked File/Embedded, file name, colour mode and PPI, then
  Embed (+ Edit Original) or Unembed…, an Image Trace split button (click: Default preset;
  arrow: trace with any preset), Mask and Crop Image, in place of Fill and Stroke.
- Control bar, one Image Trace object selected: "Image Tracing", its Preset (choosing another
  traces it again), the Image Trace panel and Expand.
- Properties panel: Image Trace (split), Crop Image and Mask in Quick Actions for an image; an
  Image Trace section (Preset, Expand, Release) for an Image Trace object. Embed/Unembed buttons
  are shared with the Control bar (place::link_buttons).
- New engine command object.maskImage ("Mask"): clips the selected image with a clipping path
  on its outline (rotated with it) and selects that path so its handles crop; one undo step.
- widgets::split_button (flat button with a menu arrow).
- Translations for the new strings (es, ja, zh-hans, zh-hant; "Mask" also pt-br, cs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:15:48 +07:00
BFlat 87ba4f6454
Merge pull request #465 from switch87/desktop-log-file
Desktop: write log records to standard error and a rotating log file
2026-10-08 17:15:25 +07:00
BFlat aae7179b72
Merge pull request #470 from storytold/feat/ai-import-groups
AI import: groups and nested groups survive
2026-10-08 17:15:11 +07:00
bflatastic 20ed00784f Desktop: report an unusable log folder through the logger, ROADMAP (review of #465)
The "no log file" message went through eprintln!, which panics when
standard error is a broken pipe; log::warn! reaches standard error the
same way (the file sink has already given up) and never panics.
ROADMAP: the log file in "Shipped so far" and "Last updated".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:07:24 +07:00
Gert Pellin ba9edcdc7f Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: the graphics adapter in use, a lost graphics
device, the engine guard's report of a recovered panic. A launch from a
desktop menu has no terminal either, so there was nothing to attach to a
bug report.

apps/vectorcraft/src/logging.rs (no new dependency):
- standard error plus logs/vectorcraft.log next to the preferences
  (~/.config/vectorcraft/logs, or under $XDG_CONFIG_HOME); each launch
  rotates the previous log to vectorcraft.1.log and .2.log, so the log of
  a run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
  (bounded) and written once the file is attached
- default levels: info for vectorcraft*, warn for everything else;
  RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
  error as the only sink
- runs with VECTORCRAFT_NO_PREFS (agents' test runs) log to standard
  error only, so they don't rotate away the user's own logs

The logger is installed first in main and the file is opened after
argument parsing (--version leaves no file). Log messages of this crate
drop their "vectorcraft: " prefix, which the record's target now shows.
vectorcraft-cli keeps its own MCP logger (vectorcraft_mcp::logging).
Documented in docs/development.md (Logs, Environment variables).
2026-10-08 17:07:08 +07:00