Commit Graph

133 Commits

Author SHA1 Message Date
bflatastic 64d366e2ae M3.41: Stroke panel follows Units > Stroke, shows mixed values blank and reaches the Control bar
- 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>
2026-10-05 01:10:14 +07:00
bflatastic 323b178ea7 M3.40: stroke on type — the Stroke panel edits the characters' stroke
With type selected, stroke.set (without an item) gives every run the
weight, cap, join, miter limit and dashes instead of adding an object
stroke; while the Type tool edits text, the Stroke panel styles the
selected characters through text.setRangeStyle `strokeOptions`, and the
panel reads the first run (or the selection's style).

CharStyle gains stroke_cap, stroke_join, stroke_miter_limit and
stroke_dash (defaulted, skipped when default). Character strokes and
object strokes on type now draw through the shared stroke geometry on
the canvas, in PDF and in SVG (live text writes cap/join/miter/dash and
tspans reset what the first run sets; object strokes are drawn on the
glyph outlines), so every stroke option, opacity and blend applies.
Outlines, the Eyedropper and graphic styles keep the options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:09:55 +07:00
bflatastic 3a79e6d922 M3.58: gradient libraries; Swatch Options renames gradient swatches; Alt-drop replaces a swatch
Window > Swatch Libraries > Gradients lists five generated gradient
libraries (Spectrum, Sky, Neutral incl. fades to transparent, Duotone,
Radial Glows); the library panel's menu lists them too.
Swatch Options on a gradient swatch shows its name and the gradient; OK
renames it. Alt-dropping an edited gradient (or a colour) on a swatch of its
kind in the Swatches panel replaces it, via swatch.edit {paint}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:00:00 +07:00
bflatastic 786f2c05d3 M3.57: save swatch libraries as .vcswatches, .gpl or CSS and load them back
Save Swatch Library… (Swatches panel menu, libraries menu, Window menu)
opens a dialog: name, format and destination. .vcswatches is lossless JSON
(colour models, global, spot, gradients, groups), .gpl an RGB palette with
groups as comment headers, CSS custom properties with escaped names.
Libraries saved to the user library folder show under User Defined.
Other Library… (and opening a .vcswatches/.gpl file) loads a library or
another document's swatches into the library panel.

Commands: swatch.library.save {path?, format?, names?, name?, user?}
(no path returns {data}), swatch.library.load {path?|data?|dataBase64?,
name?}; UI: window.swatchLibrary.other, ui.saveSwatchLibrary (dialog
saveSwatchLibrary), window.userSwatchLibrary1..10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:59:59 +07:00
bflatastic e7f6328188 M3.56: swatch library panel with code-generated swatch libraries
Window > Swatch Libraries and the Swatches panel's library button open a
read-only, floating library panel (thumbnail and list views, find field,
libraries menu, previous/next arrows). Clicking a swatch adds it to the
document and applies it as one undo step; Shift/Cmd-clicks select swatches
and groups for Add to Swatches. Default Swatches restores missing defaults.

Libraries are computed in code (OKLCH, rounded to 8-bit sRGB): Web Safe 216,
Grays and Neutrals, Earth Tones, Skin Tone Ramps, Pastels, Brights,
Metallic Gradients, Perceptual Scales, Harmony Sets.

Commands: swatch.library.list, swatch.library.get, swatch.library.add
{library, names?, apply?, focus?}, swatch.resetDefaults {replace?};
UI command window.swatchLibrary {library}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:59:59 +07:00
bflatastic 8a837fd4b9 M3.68: groups, layers and type have their own fills, strokes and effects, with Contents and Characters rows
A group's or layer's own fills and strokes paint every member's geometry, below or above the
members as the new contents slot (Appearance.contents_index) says; its geometry effects reshape
the members as one piece and its raster effects apply to the composite (one combined shadow),
respecting knockout groups. Type's own fills and strokes paint under or over its characters.
Render, SVG/PDF (via bake) and Expand Appearance evaluate them alike.

Commands: appearance.* / effect.* take target: "object"|"contents" and layer ids;
appearance.moveItem moves the Contents slot (from: "contents") or an item across it
(contents); appearance.targetContents selects a group's members. Graphic styles applied to a
group now style the group itself. The Appearance panel shows Contents/Characters rows that
drag, and double-click to target the members or edit the characters' paint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:59:34 +07:00
bflatastic 45ff33e144 M3.75: layer clipping masks from the Layers panel button
layer.clippingMask.toggle {id?} -> {clip}: the top object of the current
layer (or of the one selected group, or of `id`) becomes the clipping path,
unpainted and moved to the bottom so art added later is clipped too; pressing
the button again releases the mask and leaves the path unpainted. One undo
step. The Layers panel button runs it and underlines clipping-path names.

Model: NodeKind::Layer.clip (skipped when false). Clipped layers render, hit
test, bound, and export to SVG and PDF like clip groups (Node::clips /
set_clips); the clip-path helpers are shared with Make/Release Clipping Mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:27:31 +07:00
bflatastic 2edac7e317 M3.55: Edit Colors and Recolor Artwork also recolour gradient meshes, embedded images and pattern fills
One shared colour visitor (`colorcmds::Recolor`, used by Edit Colors, Recolor Artwork, the Blend
commands, the proxy Invert/Complement and Document Color Mode) maps solid colours, gradient stops
and text runs as before, and now also:
- gradient-mesh points (with fills); Blend grades meshes by their average and keeps their shading;
- embedded images' pixels (`includeImages?`, default on): the image becomes a recoloured copy under
  a content key, the original stays for undo and other objects; linked images are left alone;
- the tiles of pattern fills and strokes (`includePatterns?`, default on): one recoloured copy per
  pattern saved as a new pattern swatch ("Dots 2"), the original untouched.
Everything stays one undo step; only nodes that change are copied, in one walk of the tree.

- doc: `ImageBlob::map_rgb` (decode, map each distinct colour once, keep alpha, re-encode PNG),
  `ImageBlob::content_key` (now also used by image import) and `ImageBlob::png`.
- `recolor.colors` lists mesh points and pattern tile colours too.
- patterncmds: `add_pattern` adds a pattern and its swatch (shared by Make Pattern and Save a Copy).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:16:08 +07:00
bflatastic 1de1c6446f M3.54: Adjust Color Balance and Saturate open live-preview dialogs, committed as one undo step
- Edit > Edit Colors > Adjust Color Balance... opens Adjust Colors (`ui.colorBalanceDialog`, kind
  `colorBalance`): colour mode Grayscale, RGB, CMYK or Global, a slider and field per channel
  (-100..100 %), Convert, Fill and Stroke, and Preview.
- Saturate... opens Saturate (`ui.saturateDialog`, kind `saturate`): Intensity -100..100 % and
  Preview.
- Both preview through the session interaction (fields changed by `ui.dialog.set` preview on the
  next frame too); OK keeps the result as one undo step, Cancel rolls it back.
- `edit.colors.adjustBalance` takes `mode` (rgb|cmyk|gray|global; default from the channels
  given). Global mode needs tints of global and spot colours and answers with a clear error until
  they exist; the dialog stays open and the colours keep their links.
- Shared dialog helpers: `form::slider`, `form::preview`, `form::commit_preview`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:14:46 +07:00
bflatastic bfebf66404 M3.53: Color Guide has 21 harmony rules, Steps and Variation options, a fixed base colour and Save Colors as Swatches
The harmony rules (Complementary 1/2, Split Complementary, Left/Right Complement, Analogous 1/2,
Monochromatic, Shades, Triad 1-3, Tetrad 1-3, Compound 1/2, High Contrast 1-3, Pentagram) live in
the color crate with our own math, base colour first. The variation grid moves there too
(`harmony::Guide`): a row per harmony colour with the colour in the centre column, shades/cool/muted
to the left and tints/warm/vivid to the right, 1-20 steps and a variation amount.

- `color.harmony {color, rule, steps?, variation?, amount?}` query returns {rule, colors, grid}.
- The panel keeps its base colour until "Set base color" takes the current one; Shift/Cmd-click
  selects several cells and Save Colors as Swatches saves them (`swatch.new {colors}`, one step).
- Color Guide Options dialog (`ui.colorGuideOptions`, kind `colorGuideOptions`) with a preview row;
  the options persist in `UiState::color_guide`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:13:36 +07:00
bflatastic cb881b082f M3.50: Overprint Black dialog with fill/stroke, rich and spot black options
Edit > Edit Colors > Overprint Black... opens a parameter dialog (Remove,
Percentage, Fill, Stroke, Include Blacks with CMY, Include Spot Blacks).
edit.colors.overprintBlack takes fill?, stroke?, includeCmyBlacks? and
includeSpotBlacks?; it covers type characters and gradients whose every stop
is black, and records no undo step when nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:01:02 +07:00
bflatastic 0c82a3f5c5 M3.49: each fill and stroke can overprint
Fill and stroke layers (and type characters' fill and stroke) carry an
overprint flag. object.setOverprint {fill?, stroke?, item?, ids?} sets it
(groups: their contents; type: its characters too; one undo step, none when
nothing changes) and attributes.info reads it back (null where mixed).
Overprint Preview and Separations Preview draw overprinting fills and strokes
with Multiply. The old Document.unknown["overprintBlack"] id list migrates to
the new flags on load; Overprint Black now sets the flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:01:02 +07:00
bflatastic 3981acdb06 Merge wp/C3: dash alignment and variable-width dashes (M3.38, M3.39)
# Conflicts:
#	crates/pdf/src/lib.rs
#	crates/ui-egui/src/panels/mod.rs
2026-10-04 23:48:47 +07:00
bflatastic 43d7ac420d Merge wp/D2
# Conflicts:
#	crates/pdf/src/export.rs
#	crates/render/src/fx.rs
#	crates/render/src/lib.rs
#	crates/svg/src/export.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/dialogs/mod.rs
#	crates/ui-egui/src/panels/appearance.rs
#	docs/control-protocol.md
#	docs/mcp.md
2026-10-04 23:46:38 +07:00
bflatastic 42ad4540b7 M3.38: dashes fitted to corners and path ends, a flag of their own that keeps the offset
With alignDashes every run between corners and path ends holds a whole number of
periods, scaled to fit, with a dash (or dot) centred on each corner and on both
ends of an open path. It lives in the shared dash generator, so the canvas, SVG
and PDF export (as outlines) and Outline Stroke all draw it.

stroke.set: alignDashes and dashOffset edit the current pattern on their own, and
a new dash array keeps the offset and alignment it replaces. The Stroke panel
turns dashes on fitted to corners (or as last turned off), and its Exact/Fit
buttons send only the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:37:58 +07:00
bflatastic 0f169b5c5d Merge wp/B2
# Conflicts:
#	crates/doc/src/lib.rs
#	crates/engine/src/cmd/style.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/menus.rs
#	crates/ui-egui/src/panels/swatches.rs
#	docs/control-protocol.md
2026-10-04 23:37:27 +07:00
bflatastic 818980a789 Merge wp/A2
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-04 23:20:10 +07:00
bflatastic 0358c4df95 M3.35: gradient handles snap and honour Constrain Angle; double-clicking a tool button opens its options
Gradient tool handles (a drawn vector's start and end, the end handle and the moved start) snap to anchors, edges
and smart guides, with the guide overlays; Shift constrains to 45° steps from the Constrain Angle preference
(`ToolContext::constrain_angle`, `constrain_angle_from` in geom). Double-clicking a toolbar button runs the new UI
command `tool.options {tool}`: the Gradient tool opens the Gradient panel, the Eyedropper opens Eyedropper Options
(`eyedropper.setOptions {appearance?, transparency?}`, which `appearance.copyFrom` honours and its params override).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:18:34 +07:00
bflatastic abd77d58b0 Merge wp/F2: knockout groups, knockout shape, page groups (M3.46-M3.48)
# Conflicts:
#	crates/pdf/src/export.rs
#	crates/svg/src/export.rs
2026-10-04 23:15:10 +07:00
bflatastic f48ba26380 M3.32: the document colour mode governs new colours
A CMYK document (file.new {colorMode: "cmyk"}, Document::new_with_mode)
starts with CMYK default swatches (default_swatches(model)), and RGB
colours and gradient stops applied to it are stored as CMYK (Gray stays
Gray) unless paint.setFill/setStroke get keepModel; the Color panel keeps
the model picked on its sliders. RGB documents keep colours as given.
Harmonies and Color Guide variations keep the base colour's model, Recolor
Artwork the replaced colour's, and Edit Colors blends between models take
the document's, through a shared Color::in_model / keep_model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:10:15 +07:00
bflatastic 039c0cbef7 M3.48: Page Isolated Blending and Page Knockout Group
New Document.page_isolate / page_knockout, set by
transparency.togglePageIsolatedBlending / togglePageKnockoutGroup {value?}
(one undo step each, saved with the document, reported by
transparency.info) and the Transparency panel menu's check items. The page
renders as an isolated group (top-level blend modes no longer blend with
what lies under the page) and/or a knockout group whose elements are the
layers (neutral layers pass their objects through). PDF and SVG wrap the
page content in an isolated group and write page knockout as soft masks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:10:06 +07:00
bflatastic b85ef3de5a Merge wp/C2: stroke export parity and Outline Stroke fidelity (M3.36, M3.37) 2026-10-04 23:08:27 +07:00
bflatastic 93640efb36 M3.34: copied appearances place their gradients on the target's own bounds
The eyedropper (`appearance.copyFrom`), `graphicStyle.new` and `graphicStyle.apply` move placed gradients so they
sit at the same place relative to each target's bounds (type: its layout box in text space) instead of where the
source had them; the paint defaults take the gradient unplaced, so new art fits it. Model: `GradientGeom::rebase`
/ `GradientPaint::rebase` (color), `Appearance::rebase_gradients` (strokes relative to their inflated boxes) and
`GraphicStyle::unit_box`: new styles store placed gradients in unit-box space; styles from older files keep
document coordinates and apply as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:07:51 +07:00
bflatastic c95098d15a M3.47: opacity and opacity mask can define an object's knockout shape
New Node.knockout_shape (transparency.set / object.setProps knockoutShape,
reported by transparency.info). In a knockout group such an object knocks
out only as much as its opacity and mask let through (a 50% object knocks
out 50%); otherwise its full coverage knocks out. The renderer, PDF and
SVG masks honour it, and the panel's checkbox is now live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:07:01 +07:00
bflatastic 6e0af835c6 M3.46: three-state knockout groups (on, neutral, off) render, export and edit
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>
2026-10-04 23:04:20 +07:00
bflatastic bdf8f704af M3.31: Swatches panel menu adds used colours, selects unused, merges, ungroups and sorts by kind
New commands: swatch.addUsedColors {selection?, global?} adds a swatch for
each colour the art uses that no solid swatch has (optionally global, the
art linking to them); swatch.unused lists the swatches nothing uses (links,
colours, gradients and patterns in art, symbols, pattern tiles and graphic
styles count); swatch.merge keeps the first colour and relinks the others'
art; swatch.ungroup and swatch.sortByKind (process, spot, gradients,
patterns). Each edit is one undo step; the panel menu items are enabled.
Document::visit_paints walks every paint of a document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:03:56 +07:00
bflatastic 321ab52aea M3.45: graphic style links: redefine, break link, options, unused, sort, Select > Same
The Appearance panel names the style a linked object uses and its menu offers Redefine
Graphic Style "name", which updates every object still linked (edited objects keep their
look and are unlinked). The Graphic Styles panel breaks links (button and menu), selects
all unused styles, sorts by name, deletes several styles, and opens Graphic Style Options
(rename; links follow) on double-click; New Graphic Style from the menu or with Alt asks
for a name. Select > Same > Graphic Style and Appearance Attribute work, and Document Info
lists the style names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:03:45 +07:00
bflatastic 10ea4d5daf M3.42: Appearance editing depth — drag effect rows to reorder them or move them into/out of fills and strokes (Alt copies), click an effect name to edit it in its prefilled dialog, choosing an applied effect asks to edit it or add another, Show All Hidden Attributes, multi-select and Alt-drag duplicate of fills/strokes, drag the thumbnail onto art to copy the appearance, stroke rows note brush/Dashed/profile, Shift-click a chip for the mixer, Properties fx opens the effect menu
Engine: effect.move {from, to, fromItem?, toItem?, copy?}; appearance.showAllHidden;
appearance.duplicateItem takes to? and indices?; appearance.removeItem takes indices?;
Appearance::has_hidden/show_all. UI: effect.dialog takes index? (edit in place via
effect.setParams) and opens the effectExists question for an effect already in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:01:34 +07:00
bflatastic eb83efdb19 M3.44: graphic styles keep opacity and blend, capture groups and type, add on Alt-click
Styles now store the object's opacity, blend mode, isolate and knockout and apply them.
New Graphic Style from a group without its own paint captures its topmost object's
appearance, and from type its characters' fill and stroke. Names are made unique.
graphicStyle.apply takes ids and add (Alt-click in the panel adds the style on top of the
existing appearance). graphicStyle.list reports every style. Applying links objects to a
stable style id (Node.graphic_style), which the management commands build on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:01:10 +07:00
bflatastic 059f623410 M3.33: Gradient panel: proxy, swatch menu, diamond location, stop popover, Alt-drag copies and swaps, swatch drops, stop eyedropper
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>
2026-10-04 23:00:38 +07:00
bflatastic fe258915e3 M3.30: Swatches panel multi-selection, find field and drag and drop
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>
2026-10-04 22:54:56 +07:00
bflatastic 18d870c7df M3.37: Outline Stroke and the live Outline Stroke effect outline strokes as the canvas paints them
Outline Stroke (and the stroke part of Expand) now builds each stroke from the
shared canvas geometry: dash shapes, arrowheads, width profiles and inside/outside
alignment, united into one clean path (pathops::stroke_region). Every stroke item
is outlined in paint order (runs of fills stay on copies of the path), stroke
opacity, blend mode and effects move to the new fill, and brushed strokes become
their brush art. The live Effect > Path > Outline Stroke outlines the stroke it
sits on (on a fill or the object: the top stroke) with that stroke's cap, join,
dashes, alignment, profile, arrowheads and weight: GeomContext now carries the
stroke and the fill rule, built per item by the renderer, export baking and
Expand Appearance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:53:58 +07:00
bflatastic 6bd27a05da Merge wp/I1
# Conflicts:
#	crates/engine/src/cmd/gradient.rs
#	crates/engine/src/cmd/paint.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/dialogs/mod.rs
#	crates/ui-egui/src/menus.rs
#	crates/ui-egui/src/panels/color.rs
#	crates/ui-egui/src/panels/mod.rs
#	crates/ui-egui/src/toolbar.rs
#	docs/control-protocol.md
2026-10-04 22:14:37 +07:00
bflatastic 96f1b6852a Merge wp/F1
# Conflicts:
#	crates/doc/src/node.rs
#	crates/engine/src/cmd/appearance.rs
#	crates/engine/src/cmd/paint.rs
#	crates/ui-egui/src/chrome.rs
#	crates/ui-egui/src/panels/transparency.rs
2026-10-04 22:07:49 +07:00
bflatastic 0b547ec597 M4.13: raster exports check the format's size limit first and share the renderer's pixel budget
The encoder kept its own copy of the renderer's pixel budget and checked it after
render::raster_size, so a WebP too wide for its 14-bit sizes reported the generic limit
(fileio test oversized_raster_exports_are_refused). Only the WebP side limit stays local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:02:26 +07:00
bflatastic 721bb483c3 Merge wp/D1
# Conflicts:
#	crates/engine/src/cmd/appearance.rs
#	crates/engine/src/cmd/gradient.rs
#	crates/engine/src/cmd/paint.rs
#	crates/engine/src/cmd/stroke.rs
#	crates/engine/src/lib.rs
#	crates/engine/src/tooling.rs
#	crates/render/src/lib.rs
#	crates/tools/src/lib.rs
#	crates/tools/src/xform/gradient.rs
#	crates/ui-egui/src/panels/stroke.rs
2026-10-04 22:01:05 +07:00
bflatastic 41f2af932c Merge wp/C1
# Conflicts:
#	crates/doc/src/appearance.rs
#	crates/effects/src/stroke/width.rs
#	crates/engine/src/lib.rs
#	crates/pdf/src/export.rs
#	crates/render/src/lib.rs
2026-10-04 21:47:58 +07:00
bflatastic efc0ac31d9 Merge wp/B1
# Conflicts:
#	crates/engine/src/cmd/swatch.rs
#	crates/ui-egui/src/dialogs/mod.rs
#	crates/ui-egui/src/panels/mod.rs
#	docs/control-protocol.md
2026-10-04 21:47:07 +07:00
bflatastic 1ae0c22c26 Merge wp/A1 2026-10-04 21:46:02 +07:00
bflatastic 0af6734b70 Merge M3/M4 wave 0: neutral labels and brands gate, per-area paint modules, one engine loader/encoder (M3.7, M3.8, M4.10-M4.13)
# Conflicts:
#	crates/engine/src/cmd/file.rs
#	crates/mcp/src/headless.rs
#	crates/mcp/src/tools.rs
#	crates/ui-egui/src/io.rs
2026-10-04 21:46:01 +07:00
bflatastic ad82e6e9a9 M3.27: Color Picker reads colours with the engine's color_value (one parser; CMYK and grey in percent as the paint commands take them) instead of its own copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:24:22 +07:00
bflatastic c071dcd4d0 M3.28: proxies look through groups like the paint commands (a group shows its contents' paint, and contents that differ make a "?" proxy), the toolbar and panels share the engine's Session::proxy_paints, Magic Wand and Select Text by Font bump the revision so the cached "?" state follows them; double-click proxy test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:22:05 +07:00
bflatastic ff9af2628a M3.29: Live Paint Bucket and Appearance item colours feed the recent colours; the last-colour/gradient commands read "Apply Last Color" / "Apply Last Gradient" (no clash with Window > Gradient); `,` and `.` keyboard test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:20:19 +07:00
bflatastic 6d2dc7cbbc M3.28: Color panel behaviour — the mode follows the colour's own model (the last mode picked returns for colours in its model), Alt-clicking the spectrum, None/Black/White, swatches, recent colours or Color Guide chips paints the inactive proxy, Hide Options leaves the proxy and spectrum, RGB/HSB show the out-of-gamut warning (click to correct), Shift-clicking the spectrum cycles modes, Shift-dragging a slider moves the others in tandem, and a mixed selection draws "?" proxies (paint.proxies reports fillMixed/strokeMixed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:01:58 +07:00
bflatastic 8a871d98a9 M3.29: proxy paint commands — paint.invert / paint.complement recolour the active proxy of each selected object keeping its colour model (the defaults when nothing is selected), paint.lastColor (,) and paint.lastGradient (.) re-apply the last solid colour or gradient (the toolbar chips draw and run them), swap and default work on type, and recent colours live in the Session, fed by every paint command (MCP, eyedropper, live previews on commit) and listed by paint.recent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:01:58 +07:00
bflatastic cca1d11032 M3.14: gradient swatches and new art fit a placed gradient to themselves
Lossless gradient params carried a saved gradient's vector (document coordinates of the art it
came from) into swatch applications and the default paint, so a swatch applied elsewhere, or the
next shape drawn, got a gradient placed off the object. Applying a gradient swatch now fits it to
each object (keeping its aspect, on type in text space), and the default for new art keeps all
but the placement. Saving a swatch stays lossless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:58:05 +07:00
bflatastic 6ebffb9156 M3.24: Clear Appearance and Reduce to Basic add no fill or stroke rows to a group
A group's own stack now stays empty after Clear (and keeps only the rows it had after Reduce to
Basic), so the proxies keep showing the defaults for it. The Layers target-dot helper no longer
takes over the thumbnail function's doc comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:52:46 +07:00
bflatastic d3dc4a2af3 M3.17: the stop popover survives its opening double-click, and the selected stop belongs to its gradient
The double-click that opens the popover lands outside it in the same frame, so the popover
closed as soon as it opened; only a later click elsewhere closes it now. The selected stop
remembers the document, first selected object and proxy it was picked on (Session::selected_stop),
so selecting other art or toggling Fill/Stroke no longer lets Delete or the arrows edit a stop
on a different gradient.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:52:28 +07:00
bflatastic 1b3e7110a3 M3.21: active-row edits of the other kind stay on the painted leaves, item transparency is one undo step
- A fill row no longer turns stroke edits (stroke.set, setAdvanced, paint.setStroke, gradients) into
  a new stroke on a selected group's own stack: an active row only stands for edits of its kind.
- Removing, duplicating or moving items of other objects (`ids`) no longer re-points the active row.
- transparency.set with an item touches the item only for opacity/blend (isolate or knockout alone
  is one undo step) and rejects unknown blend modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:50:46 +07:00
bflatastic d1d133bf09 M3.13: swatch.newGroup falls back to "Color Group" for a blank name, never gives a swatch its own group's name, and takes mesh point colours from the artwork
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:45:49 +07:00