Commit Graph
381 Commits
Author SHA1 Message Date
skylartaylor 3d9c8279ec E21: reconcile current text, export and Dev Mode changes 2026-10-25 05:49:49 +01:00
aiongg 86bcfaddb9 Merge #1305
# Conflicts:
#	docs/file-format-parity.md
2026-10-25 02:21:44 +00:00
aiongg b95a04dc43 Merge #1304
# Conflicts:
#	docs/prototyping-parity.md
2026-10-25 02:21:29 +00:00
aiongg 7aab188389 Merge #1301 2026-10-25 02:21:19 +00:00
skylartaylor 99d66401d6 EGUI037: port FigCraft lifecycle, capture and text input to egui 0.37
Preserve current main and pin the reviewed fork dependency family.
Qualify bundled and system-font configurations, plugins and regression tests.
2026-10-25 02:31:42 +02:00
dotCooCoo 9c20d06abd M7: with Contents only on, a slice in a frame or group exports only that container
A slice exported every layer of its page that showed in its area, whatever
its Contents only setting said. With Contents only on (the default), a slice
inside a frame or group now exports only that frame or group and the layers
in it. With it off, or for a slice on the page or in a section, the slice
exports everything in its area as before. The Export section's preview of a
slice follows its first export setting in the same way.
2026-10-24 15:53:33 -07:00
dotCooCoo ae57bf84d0 M5: a notice before a prototype's Open link leaves for a website
The player opened a prototype's Open link at once, also from an After
delay trigger with no click. The help center's Prototype actions page
says that the viewer is told they are leaving before an external
website opens, with a checkbox that skips this page on the current
device.

An Open link to an http or https address now shows a notice with the
link, Open link, Cancel and "Don't show this again". Open link opens
it; Cancel, the close button or Escape drops it. While the notice
shows, the prototype takes no keys, and the presenter's C, Shift+C and
F keys do nothing. When Open link is pressed with the box ticked, the
desktop app saves the choice with its preferences; the web build keeps
it until the page reloads. proto.linkNotice {show} turns the notice on
or off. A mail link opens at once, as before, also while the notice
shows. A second link to a website that fires while the notice shows is
dropped.
2026-10-24 14:10:41 -07:00
dotCooCoo 0978b44385 M4: text on a path takes a fill binding made through fills
The bindings that move with the path's fills and effects are now found with list_binding, which reads `fills` as the first fill's color, as style_sets in cmd/styles.rs does.
2026-10-24 14:02:50 -07:00
dotCooCoo 949d6c2830 M4: text on a path keeps its effects when the path's effect style has none
A path linked to an effect style with no effects gave the text an empty effect list and the style link. The text now takes the path's effects only when there are any.
2026-10-24 13:51:32 -07:00
dotCooCoo 98d061df10 M4: text put on a path takes the path's fill and effects
text.onPath, and the Design panel's Put text on path button that runs
it, kept the text layer's own fill and effects when the text went onto
a path. The help center's Guide to text says that the vector object's
fill and effects are transferred to the text layer.

The text now takes the path layer's fills, with their fill style and
variable bindings, when one of them shows, and the path layer's effects,
with their effect style and bindings, when it has any. The text's own
bindings on those fields are dropped. When the path has no visible fill
or no effects, the text keeps its own.
2026-10-24 12:37:59 -07:00
dotCooCoo d8900e1c16 M4: the Text on a path tool
Text went onto a path only with the Design panel's Put text on path
button (a text layer and a path selected together) or with text.onPath.
The Text on a path tool, in the Text flyout of the toolbar
(tool.textOnPath), shows the I-beam over a shape's or vector's path, and
a click there puts new, empty text on the path, starting at the click,
and starts typing. The text takes the path layer's place, transform,
box, constraints and effects, and its fills when one of them shows; the
path layer is removed. Ending the edit with nothing typed puts the path
back with no undo step.

The tool measures the distance on the page, so a layer scaled more one
way than the other, or shown smaller, is reached where it shows. Shapes
in front whose outline is away from the click do not hide a path behind
them, and paths that a mask or a clipping frame hides at the click are
skipped, as are locked layers and layers inside instances.

A double-click into a text layer that was just made no longer makes it
count as an existing layer, so leaving it empty records no undo step,
with this tool and with the Text tool.
2026-10-24 11:37:47 -07:00
skylartaylor 9825a4926a M2.1: migrate FigCraft to egui 0.37 and reconcile current main 2026-10-24 11:43:04 +02:00
NetterSilverandClaude Haiku 5.5 7bc594632d Cap layout.setTrack track count at MAX_TRACKS (#1168)
Fixes #1098

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-24 05:24:53 -04:00
BFlat 70227e30fc Merge pull request #1009 from lucasmartins-ai/feat/data-fill
data.fill and text.fit: fill copies of a frame from rows of data
2026-10-24 14:17:14 +07:00
BFlat 127247433a Merge pull request #1004 from kkilchrist/feat-1001-vector-path
node.set: accept `path` to replace a vector's geometry
2026-10-24 14:17:06 +07:00
BFlat 448019c43a Merge pull request #1005 from kkilchrist/fix-992-reparent-index
node.reparent: index is the layer's final position
2026-10-24 14:16:56 +07:00
BFlat 62e1e57626 Merge pull request #1006 from kkilchrist/fix-993-boolean-default-main
Show a boolean property's default on the main component's bound layers
2026-10-24 14:16:37 +07:00
BFlat a111638378 Merge pull request #1008 from lucasmartins-ai/feat/css-export-px
variable.export css: number lengths in px
2026-10-24 14:16:29 +07:00
bflatastic 9f766dcbf7 Merge main into #1009 2026-10-24 13:47:22 +07:00
BFlat e3e39bbc4d M6: Dev Mode measurements kept on the canvas, a read-only layers panel (#1248)
* M6: Dev Mode measurements kept on the canvas, a read-only layers panel

Measurements placed between two layers, along a side or to the layer
around one (devMode.addMeasurement / removeMeasurement / measurements),
stored on the layer they start from (saved, synced, undoable; old files
open without them), drawn in Dev Mode as labelled redlines that follow
the layers and listed, placed and removed in the Inspect tab. In Dev Mode
the layers panel only selects and expands.

* M6: Dev Mode measurements: explicit imports, a measurement row of its own (Sonar)
2026-10-24 13:33:26 +07:00
bflatastic bc0f61eeee #1004: a size given with a new path still fixes its auto layout axis 2026-10-24 13:01:46 +07:00
bflatastic a0bd65f7b4 Merge main into #1009 2026-10-24 12:53:58 +07:00
bflatastic ede3c634ad Merge main into #1004 2026-10-24 12:53:55 +07:00
bflatastic 6a172e4e52 Merge main into #1005 2026-10-24 12:53:53 +07:00
bflatastic 7cd4c5216d Merge main into #1006 2026-10-24 12:53:50 +07:00
bflatastic ce32e07fe0 Merge main into #1008 2026-10-24 12:53:48 +07:00
BFlat 3476f4b1e7 Merge pull request #1244 from storytold/batch/r2-1
Community batch r2-1: 63 reviewed PRs
2026-10-24 11:15:53 +07:00
bflatastic bd80829a27 Batch r2-1 review: a paint keeps its own opacity through unbind and rebind
color_alpha is the part of a solid paint's opacity that is its typed colour's alpha, not a flag:
binding a colour variable takes only that part out, unbinding puts the variable's alpha back in,
and REST import records it. A 50% paint bound to a 50% variable stays at 25% bound or not.
2026-10-24 10:30:49 +07:00
a1a842af9a chore: resolve SonarCloud maintainability issues (#1241)
* chore: resolve SonarCloud maintainability issues

- Define SHA256_FMT constant in packaging script to remove duplicated string literal (shelldre:S1192)
- Remove redundant 'static lifetime annotations from const items (rust:S8863)
- Replace closures with method and function pointers (rust:S1612)
- Extract helper functions in synthetic document generation, bezier polyline flattening, and component restoration to reduce cognitive complexity (rust:S3776)

* fix(packaging): use verify_sha256 helper function to avoid shellcheck SC2059

* fix(engine): remove duplicate closing brace in large_document

* fix(engine): take &mut Document in restore_id_map; keep 'static on associated consts

restore_id_map allocates ids, so it needs a mutable document. Elided
lifetimes in associated consts trip elided_lifetimes_in_associated_constant,
so those annotations are restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Assign positional parameters to locals in sha256 verification

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-23 23:28:13 -04:00
bflatastic 489da09905 Merge #740: engine: fill rules as vector.fillRule and vector.reverseLoop (@yelintong) 2026-10-24 09:23:50 +07:00
bflatastic 70536a6265 Merge #948: Keep a solid paint's own opacity when binding a colour variable (@Silvertree2010) 2026-10-24 09:21:42 +07:00
bflatastic eada8b0ff3 Merge #1042: M3: style links given in a call apply after its values (@MortenHusted)
# Conflicts:
#	crates/engine/src/cmd/props.rs
2026-10-24 09:16:54 +07:00
bflatastic 90e5e03b23 Merge #1160: M0.9: figcraft-cli export names files as the app's export does (#1154) (@switch87) 2026-10-24 09:16:12 +07:00
bflatastic 15321a56ff Merge #1198: fill/stroke props: accept 'none' and null as no paint (@lileeei)
# Conflicts:
#	ROADMAP.md
2026-10-24 09:16:04 +07:00
bflatastic 98040b48a5 Merge #1037: M3: a variable mode set on a layer inside an instance is an override (@MortenHusted) 2026-10-24 09:13:23 +07:00
bflatastic 2053098e8e Merge #1200: The colour picker's eyedropper applies the colour to the paint the picker edits (Reported in storytold/vectorcraft#3342) (@marcoarnulfo)
# Conflicts:
#	crates/engine/src/tools.rs
2026-10-24 09:12:42 +07:00
vetlucasmartins 3734965cce Merge remote-tracking branch 'upstream/main' into feat/css-export-px 2026-10-24 00:41:08 +01:00
vetlucasmartins 3a3746d45d Merge upstream/main into feat/data-fill 2026-10-24 00:33:14 +01:00
BFlat 8167f13a6a M12: Boards: a collaborative whiteboard file kind (#1229)
Board files (File > New board) with sticky notes, shapes with text, connectors that follow what they join (straight or elbow, arrowheads, labels), marker, highlighter and eraser, and stamps; a board toolbar and a contextual bar; the Design panel hidden on boards, Prototype and Dev Mode refused there. Boards are ordinary FigCraft files built from ordinary layers, so saving, undo, export, MCP and live sessions work unchanged. File tabs, recent files and Open files show a pen nib for design files and a note for boards (original icons drawn in code).
2026-10-24 04:18:55 +07:00
BFlat 17ed21d128 M6: Dev Mode tail: imports in layer trees, compare with versions, an inspect-only canvas (#538)
Import lines for connected components inside generated layer trees; Compare changes against any saved version; a Dev Mode behaviour suite with an inspect-only canvas (menus, shortcuts, paste and Revert to saved refuse design edits; a drag under way is dropped on entering), Changed in the ready list, statuses only on top-level frames and sections; read-only plugins' panels from the Inspect tab.
2026-10-24 02:56:55 +07:00
Marco Arnulfo 410c1f10e2 The colour picker's eyedropper applies the colour to the paint the picker edits (Reported in storytold/vectorcraft#3342) 2026-10-23 04:08:48 +02:00
Gert Pellin eb07717783 M0.9: trim mixed edge spaces and dots, document the name limits (#1154)
Review: " . .name. . " kept inner dots at the edges; trim spaces and dots
in one pass. docs/cli.md and safe_file_name's comment give the 100 and
200 character limits.
2026-10-22 21:43:52 +02:00
Gert Pellin ca30791fd7 M0.9: figcraft-cli export names files as the app's export does (#1154)
export into a folder replaced every character outside letters, digits,
'-', '_', space and '.' with '_', so a frame called
"Homepage - desktop (live + verbeteringen)" became
"Homepage - desktop _live _ verbeteringen_.png". The app's
file.exportLayers keeps those names and only replaces what file systems
refuse. The CLI now uses the engine's safe_file_name, and docs/cli.md
says which characters are replaced.

Also adds my own entry to contributors/people.toml.
2026-10-22 21:22:20 +02:00
lileeei 2437a65d1b M1: fill/stroke props accept "none" and null (#1036)
node.create, node.set and the other prop appliers map fill/stroke
"none" (either case) and null to an empty fills/strokes list, as
fills: [] already did (undocumented), so an outline-only shape is one
call - agents write "none" for no paint. KEYS and docs/mcp.md
document it; the error names the accepted forms. fill_none_tests
covers create, set, undo, text and the error.
2026-10-22 23:04:57 +08:00
MortenHusted 6394e96005 M3: a variable mode set on a layer inside an instance is an override
A variable mode chosen on a layer inside an instance (the icon inside a
Close button, recoloured through an icon colour collection) wasn't an
override group. A full rebuild of the instance starts each layer over
from its main and carries only override groups, so the mode fell back
to the main's. Library import always rebuilds, as do main edits such as
a new auto layout direction, so a modal's recoloured close icon came
into other files in its default colour.

"modes" is now an override group: setting a mode inside an instance
records it, rebuilds carry it, rebasing a nested instance finds it, the
component panel lists it, and resetting it brings back the main's mode.
A top-level instance's own modes stay its own and record no override,
like its resizing.
2026-10-22 13:41:56 +02:00
MortenHusted 24a5855cab M3: style links given in a call apply after its values
apply_order gave fillStyle, strokeStyle, effectStyle, gridStyle and
textStyle the rank of plain values, so the JSON map's byte order decided:
fills, strokes, effects, grids, layoutGrids and variationSettings sort
after their link and overrode it (node.set then unlinked the style;
node.create kept a link its values contradicted). Style links now apply
after the values and x/y, just before boundVariables. A deviation set in a
later call still unlinks.
2026-10-22 11:11:59 +02:00
vetlucasmartins 196e3432dd data.fill and text.fit: fill copies of a frame from rows of data
- data.fill {id, rows, gap?, formats?, dir?, format?, scale?}: repeats a
  frame once per row in one undo step; each row key names layers in the
  copy (text, or {text?, fill?, visible?, fit?}). `formats` makes each row
  once per size (feed, story, square), the copy resized so its auto layout
  reflows. With `dir`, each copy is exported under a unique file name; a
  failed export is reported in `errors` and keeps the copies.
- text.fit {ids?, maxLines?, minSize?, orphans?}: the largest font size,
  down to a floor, at which fixed-width text stays within N lines and its
  box; avoids a one-word last line; `fits` comes from the final text.
- Plugins can't call data.fill: it runs other commands inside, past the
  plugin policy, like command.batch.
2026-10-21 19:54:47 +01:00
vetlucasmartins 40fd169bec variable.export css: number lengths in px
A radius exported as `--radius: 12` was invalid where CSS reads it as a
length (`border-radius: var(--radius)` ignores a bare number). Number
values now carry px unless the layers bound to them, their scopes, or the
variables aliasing them make them an opacity or a font weight, which stay
bare. The roles come from a small token_roles module.

Updates the variables_io_tests expectation for the radius (4 → 4px,
6.5 → 6.5px), and adds a test for a bound radius, a bound opacity and an
opacity alias's target.
2026-10-21 19:38:30 +01:00
KameronandClaude Opus 5.5 014098b6c9 fix(components): a boolean property's default is its bound layers' visibility on the main (#993)
component.addProperty {type: "boolean", default: false, layer} hid the
bound layer in new instances but left it showing in the main component,
so the main didn't show its own default.

On the main, a boolean property's value is now the visibility of the
layers bound to it:

- addProperty, bindProperty {field: "visible"} and editProperty {default}
  show or hide the main's bound layers to match the default (every
  variant of a set for editProperty). Without a default, addProperty
  takes the bound layer's current visibility instead of always `true`.
- Showing or hiding a bound layer on the main sets the default, and the
  property's other bound layers (in every variant) follow. Edits inside
  instances stay overrides.
- Existing instances keep their stored values; documents open as saved
  (no normalisation on load).

Fixes #993

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:27:46 -07:00
KameronandClaude Opus 5.5 afdc31b727 Let node.set replace a vector's path
node.set took no `path`, so redrawing a vector meant deleting and recreating it and
losing its id, place in the layer list, paints, styles, bindings and step references.

`path` now takes SVG path data for vector layers and replaces the geometry through
the same reader node.create uses (path_network, shared with create). width/height in
the same call scale it as create does. The path's own box goes at the layer's current
origin, so rotation and flips stay; with x/y in the same call it goes at x/y plus the
box's offset, which is where create puts it.

On several layers, layers that aren't vectors are skipped with a warning, as auto
layout keys already are (the skip logic is shared now). A single non-vector is
refused, and so is a vector inside an instance: there is no geometry override, so
the next sync would undo it. Data that doesn't parse, draws nothing or has
non-finite bounds is refused cleanly; create now refuses non-finite bounds too.

Tests cover keeping the layer, placement and scaling against create, rotation,
undo/redo, invalid data, non-vectors, instances, boolean operands, edit mode, and a
junk-path sweep. The engine-wide junk sweep has node.set path entries.

Fixes #1001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:21:24 -07:00