Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,27 +136,28 @@ shutter shot --widget 'PrimaryButton(label: "OK")' --import lib/ui/button.dart -
`--import` names a file under `lib/`, or a `package:` URI of a dependency, that the widget expression needs imported (repeatable; `package:flutter/widgets.dart` is always imported).
The same `--widget` and `--import` give the same shot id, so two such runs line up in `diff`.

A state that comes from a gesture is shot by acting on the preview first, and what opens above it by capturing the whole screen:
A state that comes from a gesture or typing is shot by acting on the preview first, and what opens above it by capturing the whole screen:

```bash
shutter shot lib/preview/button_preview.dart --press text:Save
shutter shot lib/preview/filter_preview.dart --tap 'type:DropdownButton<String>' --capture screen --viewport 390x400
shutter shot lib/preview/login_preview.dart --enter 'label:Email=example@example.com' --tap 'text:Sign in' --settle 700
```

`--tap` (repeatable, in the order given), then `--press`, `--hover`, or `--focus`, name their widget by `key:`, `text:`, or `type:`.
`--tap` and `--enter` (repeatable, in the order given), then `--press`, `--hover`, or `--focus`, name their widget by `key:`, `text:`, `label:` (a semantics label, such as a text field's label), or `type:`.
Each is followed by `--settle` milliseconds; shoot again with another `--settle` for another point of an animation.
`--capture screen` holds the whole viewport, with the menus, dialogs, and tooltips the app draws above the preview.

## Subcommands

| Command | Purpose |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent` | The step-by-step playbook for AI agents. |
| `manual` | The reference: preview files, drawing model, engine, runs, ids, diff, output, exit codes. |
| `doctor` | Check the Flutter SDK version, its font cache, and the project's shell (`--shell`). |
| `init` | Write `<preview dir>/shell.dart`, or the `--shell` file. |
| `shot` | Render the named preview files, or one `--widget`, into a new run (`--widget`/`--import`/`--size`, `--settle`, `--shell`, `--tap`/`--press`/`--hover`/`--focus`, `--capture`/`--viewport`). |
| `diff <a> <b>` | Compare two runs (`--images`). |
| Command | Purpose |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent` | The step-by-step playbook for AI agents. |
| `manual` | The reference: preview files, drawing model, engine, runs, ids, diff, output, exit codes. |
| `doctor` | Check the Flutter SDK version, its font cache, and the project's shell (`--shell`). |
| `init` | Write `<preview dir>/shell.dart`, or the `--shell` file. |
| `shot` | Render the named preview files, or one `--widget`, into a new run (`--widget`/`--import`/`--size`, `--settle`, `--shell`, `--tap`/`--enter`/`--press`/`--hover`/`--focus`, `--capture`/`--viewport`). |
| `diff <a> <b>` | Compare two runs (`--images`). |

## Exit codes

Expand All @@ -170,7 +171,7 @@ Each is followed by `--settle` milliseconds; shoot again with another `--settle`

## Limits

* One capture per preview: state comes from the widget's construction expression and from taps, a press, a hover, or focus before the capture; scrolling, dragging, and typing are not shot.
* One capture per preview: state comes from the widget's construction expression and from taps, entered text, a press, a hover, or focus before the capture; scrolling and dragging are not shot, nor is the on-screen keyboard.
* HTTP is blocked while rendering, so network images fail to load.
* Text renders with the project's fonts plus Roboto; CJK and emoji fall back to the host's system fonts, and Cupertino text uses SF Pro on macOS (Roboto elsewhere), as a device would. Compare runs made on the same machine.

Expand Down
7 changes: 4 additions & 3 deletions doc/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,17 @@ shutter shot --widget 'PrimaryButton(label: "OK")' --import lib/ui/button.dart -
* Without `--size` the widget is shot at its own size; `--size` fixes both dimensions and stretches the widget to them.
* The same `--widget` and `--import` give the same shot id in every run, so before and after line up in `diff`.

## Pressed, hovered, focused, opened
## Pressed, hovered, focused, opened, typed

A preview shows the state its construction gives. For a state a gesture gives, act on the preview in the shot:
A preview shows the state its construction gives. For a state a gesture or typing gives, act on the preview in the shot:

```bash
shutter shot lib/preview/button_preview.dart --press text:Save
shutter shot lib/preview/filter_preview.dart --tap 'type:DropdownButton<String>' --capture screen --viewport 390x400
shutter shot lib/preview/login_preview.dart --enter 'label:Email=example@example.com' --tap 'text:Sign in' --settle 700
```

* `--tap` (repeatable, run in the order given), then at most one of `--press`, `--hover`, `--focus`. A target is `key:<ValueKey<String>>`, `text:<Text data>`, or `type:<Widget>`.
* `--tap` and `--enter <target>=<text>` (both repeatable, run in the order given), then at most one of `--press`, `--hover`, `--focus`. A target is `key:<ValueKey<String>>`, `text:<Text data>`, `label:<semantics label>` (a text field's label or hint, a button's text), or `type:<Widget>`.
* The actions apply to every preview of the named files; a preview where the target matches no widget, or several, is an `error` shot. Name the files whose previews have the target, and give the same actions to the before and after shots.
* Menus, dialogs, bottom sheets, and tooltips open above the preview and are in the image only with `--capture screen`; `--viewport` gives a small preview the room they open into.
* `--settle` (default 300 ms) is how long after the last action the image is taken. A tapped button's ink is still fading at 300 ms and gone by 700 ms: pass `--settle 700` for the state after a tap without it.
Expand Down
31 changes: 17 additions & 14 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,22 @@ A widget expression that does not compile is an `error` shot carrying the compil

## Actions

A state that comes from a gesture is shot by acting on the preview before the capture:
A state that comes from a gesture or from typing is shot by acting on the preview before the capture:

* `--tap <target>` taps the widget: a pointer down and up at its centre. Repeatable; the taps run in the order given.
* `--tap <target>` taps the widget: a pointer down and up at its centre. Repeatable.
* `--enter <target>=<text>` enters the text into the text field the target is or holds (exactly one `EditableText`), as the platform's keyboard does: the field takes focus and its text is replaced. The target ends at the first `=`. Repeatable; taps and entries run in the order given.
* `--press <target>` holds a pointer down on it through the capture: its pressed state, with the ink the press has drawn by then.
* `--hover <target>` keeps a mouse pointer over it through the capture.
* `--focus <target>` gives it keyboard focus, highlighted as with a keyboard: `flutter_test` runs as a touch device, where focus draws no highlight. The node is the first focus node inside the widget (a button's or text field's own), else the one around it.
* At most one of `--press`, `--hover`, and `--focus`; it follows the taps.
* At most one of `--press`, `--hover`, and `--focus`; it follows the taps and entries.

A target is `key:<value>` (a `ValueKey<String>`), `text:<string>` (a `Text` showing exactly that string, or an `EditableText` holding it), or `type:<Widget>` (a widget of that type, with or without type arguments: `type:Checkbox`, `type:DropdownButton<String>`); offstage widgets do not count.
A target is `key:<value>` (a `ValueKey<String>`), `text:<string>` (a `Text` showing exactly that string, or an `EditableText` holding it), `label:<string>` (a widget whose semantics label is exactly that string: a text field's `labelText` or `hintText`, a button's text), or `type:<Widget>` (a widget of that type, with or without type arguments: `type:Checkbox`, `type:DropdownButton<String>`); offstage widgets do not count.
`label:` names a text field without a key: `--enter 'label:Email=example@example.com'`.
Each action is followed by `--settle` milliseconds drawn in 16 ms frames, as on a device, so `--settle` is also how long after an action the capture comes: an animation the action starts (ink, a check mark, a menu opening) is shot at that point of its course.
`flutter test` runs as Android, so Material 3 presses use `InkSparkle`: its sparkle shows from about 100 ms to about 600 ms of a press, then the flat pressed overlay stays, as on a device.

The actions apply to every preview of the run.
A preview is an `error` shot, without a PNG and with `at` pointing at the preview, when a target matches no widget or more than one, when a pointer at the target's centre does not reach it (covered, outside the viewport, or ignoring pointers), or when a `--focus` target cannot take focus.
A preview is an `error` shot, without a PNG and with `at` pointing at the preview, when a target matches no widget or more than one, when a pointer at the target's centre does not reach it (covered, outside the viewport, or ignoring pointers), when an `--enter` target holds no text field or more than one, or when a `--focus` target cannot take focus.

The run records its actions in `manifest.json`; `shot` prints them, and `diff` prints both runs' when they differ, since they change every image without any widget changing.
They are not part of shot ids, so a run with actions pairs shot for shot with one without.
Expand Down Expand Up @@ -166,7 +168,8 @@ Fonts bundled in the project's assets are used as they are.

These come from rendering through `flutter test`.

* One capture per preview, after the actions: scrolling, dragging, and typing are not shot. A sequence of states, such as the course of an animation, is shot as one run per point in time, each with its own `--settle`.
* One capture per preview, after the actions: scrolling and dragging are not shot. A sequence of states, such as the course of an animation, is shot as one run per point in time, each with its own `--settle`.
* No on-screen keyboard: text entered with `--enter` reaches the field, but the keyboard a device would show is not drawn, even with `--capture screen`.
* HTTP is blocked, so network images fail to load.
* `flutter test` runs the engine with test fonts, which has no system font fallback. Shutter's host fonts are added to the text themes and the default text style, so a text style that sets its own `fontFamilyFallback` does not get them. google_fonts styles do this: glyphs outside the Google font (for example Japanese in a Latin-only font) render as boxes, where a device would fall back to a system font.

Expand Down Expand Up @@ -231,11 +234,11 @@ Other failures follow sysexits: 64 usage, 66 missing run or file, 69 no Flutter

## Commands

| command | purpose |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `agent` | the step-by-step playbook |
| `manual` | this document |
| `doctor` | SDK version, font cache, shell (`--shell`) |
| `init` | write `shell.dart` (`--shell`) |
| `shot` | render the named preview files, or one `--widget`, into a new run (`--widget`/`--import`/`--size`, `--settle`, `--shell`, `--tap`/`--press`/`--hover`/`--focus`, `--capture`/`--viewport`) |
| `diff` | compare two runs (`--images`) |
| command | purpose |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent` | the step-by-step playbook |
| `manual` | this document |
| `doctor` | SDK version, font cache, shell (`--shell`) |
| `init` | write `shell.dart` (`--shell`) |
| `shot` | render the named preview files, or one `--widget`, into a new run (`--widget`/`--import`/`--size`, `--settle`, `--shell`, `--tap`/`--enter`/`--press`/`--hover`/`--focus`, `--capture`/`--viewport`) |
| `diff` | compare two runs (`--images`) |
7 changes: 4 additions & 3 deletions lib/src/cli/agent_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,17 @@ shutter shot --widget 'PrimaryButton(label: "OK")' --import lib/ui/button.dart -
* Without `--size` the widget is shot at its own size; `--size` fixes both dimensions and stretches the widget to them.
* The same `--widget` and `--import` give the same shot id in every run, so before and after line up in `diff`.

## Pressed, hovered, focused, opened
## Pressed, hovered, focused, opened, typed

A preview shows the state its construction gives. For a state a gesture gives, act on the preview in the shot:
A preview shows the state its construction gives. For a state a gesture or typing gives, act on the preview in the shot:

```bash
shutter shot lib/preview/button_preview.dart --press text:Save
shutter shot lib/preview/filter_preview.dart --tap 'type:DropdownButton<String>' --capture screen --viewport 390x400
shutter shot lib/preview/login_preview.dart --enter 'label:Email=example@example.com' --tap 'text:Sign in' --settle 700
```

* `--tap` (repeatable, run in the order given), then at most one of `--press`, `--hover`, `--focus`. A target is `key:<ValueKey<String>>`, `text:<Text data>`, or `type:<Widget>`.
* `--tap` and `--enter <target>=<text>` (both repeatable, run in the order given), then at most one of `--press`, `--hover`, `--focus`. A target is `key:<ValueKey<String>>`, `text:<Text data>`, `label:<semantics label>` (a text field's label or hint, a button's text), or `type:<Widget>`.
* The actions apply to every preview of the named files; a preview where the target matches no widget, or several, is an `error` shot. Name the files whose previews have the target, and give the same actions to the before and after shots.
* Menus, dialogs, bottom sheets, and tooltips open above the preview and are in the image only with `--capture screen`; `--viewport` gives a small preview the room they open into.
* `--settle` (default 300 ms) is how long after the last action the image is taken. A tapped button's ink is still fading at 300 ms and gone by 700 ms: pass `--settle 700` for the state after a tap without it.
Expand Down
Loading
Loading