mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-25 11:07:01 -06:00
There was no way to turn a presentation into an animated GIF. file.export now writes one: format
`gif` (or a .gif path), one frame per slide drawn by the slide renderer and shown for
secondsPerSlide, encoded with the `image` crate's GIF encoder (already a dependency, no new
crates) with a 256-colour palette per frame: exact when a slide has 256 colours or fewer,
NeuQuant otherwise. Slides are composited onto white.
Options: width (small 640, medium 960 (default), large 1280, extraLarge 1920, or 16-3840 px; the
height follows the slide's aspect ratio, no side over 3840), secondsPerSlide (0.1-600, default 5,
written in GIF hundredths), loop (forever by default, or once: no NETSCAPE2.0 block), slides (a
list of indices, in play order) and includeHidden (hidden slides are skipped by default).
Out-of-range or wrongly typed values, NaN and infinities are BadParams errors, never panics; at
most 500 frames and 200 million pixels in total, checked before anything is rendered.
- Engine: crates/engine/src/gif_export.rs (options, frame list, size, encoder);
cmd::file::gif_bytes, save_bytes("gif"), format_for_path(".gif").
- UI: the Export… dialog (Command palette; the app has no File menu yet) gets "Animated GIF (.gif)" with Size, Seconds per slide, From/To slide,
Loop forever and Include hidden slides; an invalid range or value keeps the dialog open with a
message. The web build downloads the bytes (image/gif).
- CLI: convert IN OUT.gif, run --export OUT.gif --export-params JSON; usage lists the format and
options.
- MCP: the export tool lists gif and width/secondsPerSlide/loop (slides and includeHidden now
apply to PDF and GIF).
- Docs: file-format parity GIF row, gaps (video export remains), parity checklist row M -> P
(P2 breadth 36% -> 37%, overall 79%), control protocol, MCP, README, ROADMAP progress log.
Transitions and build animations are not animated in the GIF (tracked with video export).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
2.6 KiB
Markdown
33 lines
2.6 KiB
Markdown
# Control protocol
|
||
|
||
Start the app with `--control <port>`. The server listens on `127.0.0.1` only and speaks JSON
|
||
lines: one request object per line, one reply per line.
|
||
|
||
```json
|
||
{"id": 1, "method": "engine.execute", "params": {"command": "shape.insert", "params": {"preset": "roundRect", "rect": [100, 100, 240, 120], "text": "Hello"}}}
|
||
{"id": 1, "ok": true, "result": {"id": 258}}
|
||
```
|
||
|
||
Coordinates are slide points (1/72 inch; a 16:9 slide is 960 × 540) unless noted.
|
||
|
||
| Method | Params | What it does |
|
||
|---|---|---|
|
||
| `engine.execute` / `ui.menu.invoke` / `command` | `{command, params}` | Run any command (see `engine.commands`; `deckcraft-cli describe ID` documents one) |
|
||
| `engine.commands` | — | Every command: id, label, menu path, shortcut, params doc, enabled / disabled reason |
|
||
| `document.inspect` | — | Slides, sections, layouts, shapes, selection |
|
||
| `ui.inspect` | — | UI state, active tool, window/canvas/slide rects, dialog, Home (start screen over the open presentations), slide show state, documents, perf (fps, render ms) |
|
||
| `ui.tool.select` | `{tool, preset?}` | `select`, `text`, `shape` + preset, any preset name, `curve`, `freeform`, `scribble`, `pen`, `highlighter`, `eraser` |
|
||
| `ui.pointer` | `{events: [{kind: down\|drag\|up\|move\|doubleclick\|tripleclick, x, y, mods?}], mods?}` | Drive the active tool in slide points through the same code path as the mouse |
|
||
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Keyboard input (also drives a running slide show) |
|
||
| `ui.move` / `ui.click` / `ui.drag` | screen points | Real egui pointer input — reaches every widget, menu and pane |
|
||
| `ui.set` | `{tab?, view?, pane?, notes?, zoom?, brightness?, ruler?, gridlines?, guides?, …}` | UI state (returns the full state) |
|
||
| `ui.dialog.open` / `ui.dialog.close` | `{id, fields?}` | Open or close a dialog by id; `fields` pre-fills its fields (About: `{"tab": "contributors"}` or `"models"`) |
|
||
| `ui.screenshot` | `{path?}` | PNG of the whole window (base64 when no path) |
|
||
| `ui.render` | `{path?, slide?, scale?}` | Render a slide headlessly |
|
||
| `ui.resize` / `ui.focus` | | Window control |
|
||
| `show.start` / `show.end` | `{from?: slide index, presenter?, reading?}` | Slide show |
|
||
| `app.open` / `app.save` / `app.export` / `app.quit` | `{path, …}` | Files (export takes the `file.export` params, e.g. `{"path": "deck.pdf", "layout": "handouts", "perPage": 3}` or `{"path": "deck.gif", "width": "large", "secondsPerSlide": 3, "loop": false}`) |
|
||
|
||
The MCP server (`deckcraft-cli mcp`, see [mcp.md](mcp.md)) wraps the same methods for Claude
|
||
and other agents; `deckcraft-cli app METHOD [JSON]` sends one request from a shell.
|