M3: text depth: RTL names, text style browser, axis previews (#72)

Right-to-left and mixed-direction names in the panels: layers, pages,
Assets, tab titles, the file header and canvas frame titles show in
visual order (Unicode bidi, joined Arabic letters, mirrored brackets),
elided at their end (the left end of a right-to-left name). egui shapes
each font run itself, so shown names are laid out a cluster at a time.
Rename fields keep egui's editing and draw the shown text over it: the
caret, selection, clicks, drags and arrow keys follow what's on screen.
System fonts are added for scripts the built-in UI fonts lack.

The Typography section's text style browser: list and grid views with
each style in its own font, search, folders, this file's and enabled
libraries' styles, apply, detach, edit the typography, create from the
selected chars, hover to preview on the canvas, and "Mixed" when the
chars use several styles. Text styles now link chars (style_id on text
styles), so a range can take a style; editing what a style sets
unlinks the chars edited; files from before link on open.

Live text previews (text.preview / text.endPreview): a hovered text
style, font style or named instance and a dragged axis slider show on
the canvas without an undo step; release commits one step, Escape
cancels. ui.input sends raw window input over the control channel.

Visual QA: list markers keep the paragraph's font, renames start with
the name selected and centred, Max lines shows while truncating.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BFlat
2026-10-10 13:03:09 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 9375279198
commit 340b673666
38 changed files with 2892 additions and 377 deletions
+24 -5
View File
@@ -72,6 +72,8 @@ was resized to (`responsive`: `[w, h]`, else `null`), the size option (`scale`:
`fit|fitWidth|fill|actual`, `deviceFrame`), the `zoom` it shows at, how
far the view is panned (`pan`, `panRange`) and whether a scroll is still coasting (`flinging`)
(`null` when nothing plays). `presentOptions` holds the options the next presentation starts with.
`textPreview` is true while a live text preview shows on the canvas (a hovered text style or named
instance, a dragged axis slider; see `text.preview` in [mcp.md](mcp.md)).
### `ui.pointer {kind, x, y | sx, sy, to?, steps?, shift?, alt?, cmd?, ctrl?}`
@@ -95,6 +97,22 @@ edited, keys go to the text editor. Returns `{"command": id}` for shortcuts.
Type into the text being edited (start editing with a double-click, `text.edit.begin {id}`, or the
text tool).
### `ui.input {events}`
Raw window input for the next frame, as if from the mouse and keyboard: it reaches the panels,
popovers and fields (`ui.pointer` and `ui.key` drive the canvas tools and shortcuts instead).
`events` is a list of `{move: [x, y]}`, `{down: [x, y], button?: "secondary"}`,
`{up: [x, y], button?}`, `{key, shift?, alt?, cmd?}` (pressed and released; egui key names such as
`ArrowLeft`, `Escape`, `A`), `{text}` (typed text) and `{leave: true}` (the pointer left the
window); positions are window points. Events of one call arrive in the same frame, so send a press
and its release (or a double-click's two clicks) as separate calls. Returns `{queued}`.
```json
{"id":6,"method":"ui.input","params":{"events":[{"move":[1000,315]}]}}
{"id":7,"method":"ui.input","params":{"events":[{"down":[1000,315]}]}}
{"id":8,"method":"ui.input","params":{"events":[{"up":[1000,315]}]}}
```
### `ui.menu.invoke {id}`
Invoke a menu item exactly like clicking it (this may open a dialog). Returns `{ok, lastError}`.
@@ -112,11 +130,12 @@ layers tree), `exportPreview` (bool: the Design panel Export section's preview),
frame id: open the prototype preview pane like `proto.preview`; false or `null` closes it),
`presentScroll` (`[x, y]`: scroll the presented screen, the full window's else the pane's, clamped
to what it can scroll), `colorProfileDialog` (`srgb|displayP3`: open File ▸ Color profile with that
profile chosen; `null` closes it), `stylePicker` (`fill|stroke|effect|grid`: open the style picker
for the selection; `null` closes it) with `stylePickerEdit` (a style id or name: open its editor),
`stylePickerView` (`list|grid`), `protoEditor` (`[layer id, interaction index]`: open that
interaction's editor in the Prototype panel; `null` closes it). Returns `ui.inspect` (which reports
`stylePicker` and `stylePickerView`).
profile chosen; `null` closes it), `stylePicker` (`fill|stroke|effect|grid|text`: open the style
picker for the selection, `text` being the text style browser of the Typography section; `null`
closes it) with `stylePickerEdit` (a style id or name: open its editor), `stylePickerView`
(`list|grid`), `protoEditor` (`[layer id, interaction index]`: open that interaction's editor in the
Prototype panel; `null` closes it). Returns `ui.inspect` (which reports `stylePicker` and
`stylePickerView`).
### `ui.screenshot {path}`
+13
View File
@@ -103,6 +103,19 @@ Or in a project's `.mcp.json`:
`text.setVariations {variations, replace?}` merges instead. `text.fonts {family, style?, axes: true}`
lists a font's axes and named instances (named instances are also valid `fontStyle` values).
OpenType features: `features {"liga": false, "tnum": true}`.
- **Text styles** link chars, not just layers: `style.apply {style, ids?, start?, end?}` with a text
style links chars `start`..`end` (while text is edited, the selected chars; else whole layers), and
`style.detach {which: "text", start?, end?}` unlinks them (values stay). A layer's `styles.text` is
the style all its chars share (none when they differ); `get_node` lists the linked chars as
`textStyleRanges` (`[start, end, style id]`).
Changing what a text style sets (font, size, line height, letter spacing, decoration, case, axis
values) on linked chars unlinks them; `style.update` moves every linked char. `style.create
{type: "text", name}` takes the selected chars while editing; a `value` may use the `node.set`
spellings (`{"fontSize": 32, "lineHeight": "120%", "letterSpacing": "2%"}`).
- **Live previews**: `text.preview {command, params}` shows what `style.apply`,
`library.applyStyle`, `text.setVariations` or `text.style` would do on the canvas without an undo
step (in place of an earlier preview); `text.endPreview` puts the document back. Any other command
ends the preview first, so running the command for real commits it as one undo step.
- Every mutation is one undo step (`batch` makes several one step). A failing command leaves the
document as it was.
- Programmatic calls never open dialogs.
+1 -1
View File
@@ -1,6 +1,6 @@
# Parity report
Generated by `cargo xtask parity`: FigCraft commands registered for the incumbent's main menu and toolbar (396 commands in the engine).
Generated by `cargo xtask parity`: FigCraft commands registered for the incumbent's main menu and toolbar (398 commands in the engine).
**197 / 202 menu items (97.5%)**