mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-02 16:29:34 -06:00
Merge remote-tracking branch 'origin/main' into feat/spell-check
# Conflicts: # ROADMAP.md # docs/gaps.md # docs/roadmap.md # docs/target-app-parity.md # docs/ui-parity.md
This commit is contained in:
+53
-8
@@ -218,6 +218,16 @@ points: `vector.edit.widthPoint {t, width? (px) | scale?}` adds one, `{index, t?
|
||||
moves or resizes it, `{action: "remove", index}` deletes it. Rendering, PNG/SVG/PDF export and
|
||||
`vector.outlineStroke` follow the profile.
|
||||
|
||||
One-sided widths: a width point can be wider on one side of the path than the other (left and
|
||||
right looking along the path, y down). Points are `[t, left, right]` (or `{t, left, right}`) where
|
||||
each side's scale is what `scale` would be with both sides that wide, so `[t, s]` equals
|
||||
`[t, s, s]`; `get_node` reports one-sided points that way. `vector.edit.widthPoint {index, side:
|
||||
left|right, width? | scale?}` changes one side (`width`: the distance from the centre line to that
|
||||
edge; with `t` it adds a point keeping the other side as it is); a width without `side` makes the
|
||||
point symmetric again. With `pointer`, `alt: true` while dragging a handle (or the path) changes
|
||||
that side only; `vector.edit.state` lists `widthPoints` with both handle positions (`sides`:
|
||||
right, left) and `oneSided`.
|
||||
|
||||
### Prototypes
|
||||
|
||||
Interactions are `proto.interaction.add` / `set` / `remove` / `clear` (a navigate action can keep
|
||||
@@ -367,15 +377,35 @@ remove and reorder layers there, unlike the rest of an instance). It is a compon
|
||||
|
||||
`get_node` shows `"slot": "<property>"` on slot layers.
|
||||
|
||||
### Boolean groups
|
||||
|
||||
`bool.union` / `bool.subtract` / `bool.intersect` / `bool.exclude {ids?}` wrap layers in a new
|
||||
boolean group, except: with one boolean group selected they set its operation (every operand follows
|
||||
it again), and with layers inside boolean groups they set those operands' own operation. Each
|
||||
operand above the bottom one combines with the result of the operands below it by its own
|
||||
operation, or the group's (`node.set {operandOperation: union|subtract|intersect|exclude | null}`
|
||||
on any operand, or `booleanOperation` on an operand that isn't itself a boolean group; on a group
|
||||
`booleanOperation` is the group's). `get_node` shows `operandOperation` on operands that have one.
|
||||
Operands stay editable layers: `select.children` (Enter) or a double-click selects them, `cmd: true`
|
||||
clicks reach them directly, and moving, resizing or editing an operand's points (`vector.edit.*`)
|
||||
updates the group's result on every pointer move, one undo step per gesture; a dragged operand
|
||||
stays in its group. `bool.flatten` bakes the result. Only the operands from the edited one up are
|
||||
recombined during a drag (`figcraft_render::boolean`; timed by `cargo xtask bench`).
|
||||
|
||||
### Shape builder
|
||||
|
||||
The selected shapes' outlines split the canvas into regions (areas covered by the same shapes).
|
||||
`shapeBuilder.regions {ids?}` lists them: an SVG `path` in page coordinates, the `sources` covering
|
||||
each one (top-most first), `bounds` and a `point` inside. `shapeBuilder.merge {ids?, regions?,
|
||||
points?}` makes one vector from the regions (by index, or the regions under page points), styled
|
||||
like the top-most shape over the first one; `shapeBuilder.remove {ids?, regions?, points?}` deletes
|
||||
them. Either way every shape covering a picked region loses that area (it becomes a vector, or goes
|
||||
when nothing is left), and each call is one undo step. The tool is `tool.shapeBuilder` (Shift+M):
|
||||
each one (top-most first), the `parts` covering it (`{id, stroke}`: a shape's fill, or its stroke's
|
||||
outlined area; top-most first), `bounds` and a `point` inside. Visible strokes take part as their
|
||||
outlined areas, so stroked lines and open paths split the regions they cross and a stroke's band is a
|
||||
region of its own. `shapeBuilder.merge {ids?, regions?, points?}` makes one vector from the regions
|
||||
(by index, or the regions under page points), styled like the top-most fill or stroke over the first
|
||||
one (a stroke's region is filled with the stroke's paints); `shapeBuilder.remove {ids?, regions?,
|
||||
points?}` deletes them. Either way every shape covering a picked region loses that area (it becomes
|
||||
a vector, or goes when nothing is left); the strokes of the shapes involved become outlined vectors
|
||||
just above them (`outlines` in the result; an open path is replaced by its outline), and each call
|
||||
is one undo step. The tool is `tool.shapeBuilder` (Shift+M):
|
||||
click or drag across regions with `pointer` to merge, add `alt` to remove; `shapeBuilder.mode
|
||||
{mode: merge|remove}` swaps what a plain click does.
|
||||
|
||||
@@ -510,12 +540,24 @@ joined file opens as a new document).
|
||||
`collab.peers` does, with `link` (`figcraft://join?host=…&file=…&token=…`) and `url`
|
||||
(`ws://…/?file=…&token=…`): both carry the session's secret, so share them only with people you
|
||||
invite. `collab.stop {}` ends it.
|
||||
- `collab.join {link}` (either form) returns at once with `status: "connecting"`; poll
|
||||
- `collab.join {link}` (any form: an app's invite, a `figcraft-server` share link, or the
|
||||
`ws(s)://…?file=…&token=…` URL) returns at once with `status: "connecting"`; poll
|
||||
`collab.peers` until `status` is `"live"`, or `"failed"` with a `reason` (a wrong token, a full
|
||||
session, an unreachable host). `collab.leave {}` leaves.
|
||||
session, a revoked link, an unreachable host). `collab.leave {}` leaves; the synced document
|
||||
stays open as your copy.
|
||||
- `collab.publish {server, name?, key?}` puts the active document on a `figcraft-server`
|
||||
([`server.md`](server.md)) and syncs it from there: `server` is the address
|
||||
`figcraft-server key` prints (`ws://host:47900/?key=…`; or give `key` separately), `name` the
|
||||
file's name (default: the document's). It returns at once with `status: "connecting"`; the
|
||||
document is read-only until the upload is done, then `collab.peers` has `published: {server,
|
||||
file, name, edit, view}` with the file's first edit link and view link (they carry their
|
||||
tokens: share them like passwords). Someone joining with the view link gets `access: "view"`:
|
||||
every edit on the synced document is refused with "View only: …" (the server refuses them too).
|
||||
- `collab.peers {}` → `{available, canHost (false in the web build), status
|
||||
(off|connecting|live|reconnecting|failed), reason?,
|
||||
attempt?, role (host|guest), me, name, color, fileName, pages, link, url, address, network,
|
||||
access (edit|view), readOnly (the active document can't be edited now: view only, or being
|
||||
published), published (a file you published: {server, file, name, edit, view}),
|
||||
follow, syncing (the document arrived), document (the index of the synced open document),
|
||||
editing (the text layer you are typing in: {node, layer, caret, selection}, or null), peers: [{id,
|
||||
name, color, editing (the text layer they are typing in, same shape, or null), presence: {cursor:
|
||||
@@ -726,7 +768,10 @@ guide, along the frame's axis nearer the ruler's.
|
||||
the start. **Start** turns the arc and keeps its sweep. **Ratio** (the centre, or the inner edge
|
||||
halfway round a ring) drags out to make a ring, and into the gap to show the other part.
|
||||
`shift: true` snaps angles to 15° steps and the ratio to 5% steps. Each drag is one undo step
|
||||
and writes `node.set`'s `arcStart` / `arcSweep` (degrees) and `arcInner` (0–0.99).
|
||||
and writes `node.set`'s `arcStart` / `arcSweep` (degrees) and `arcInner` (0–0.99). The Design
|
||||
panel's Arc fields (Start °, Sweep %, Ratio %) write the same keys; number variables bound to
|
||||
`arcSweep` and `arcInner` (`variable.bind`) hold percentages, as the fields show them, and ones
|
||||
bound to `arcStart` degrees.
|
||||
`tool.overlay.arc` holds the readout while dragging (`{handle, label: "Sweep 75%", at}`), and
|
||||
`tool.overlay.arc_hover` holds the handle under the pointer.
|
||||
- **Select**: `select.layersAt {x, y}` lists every layer under a page point, front-most first with its
|
||||
|
||||
Reference in New Issue
Block a user