@promptctl/rich-js 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +13 -16
- package/dist/core/strip.js +119 -110
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +132 -54
- package/dist/renderables/effect.js +188 -115
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Effected — a renderable whose finished cells' colours
|
|
3
|
-
* effect at a moment `t`.
|
|
2
|
+
* Effected — a renderable whose finished cells' looks — colours and text
|
|
3
|
+
* attributes — pass through an effect at a moment `t`.
|
|
4
4
|
*
|
|
5
5
|
* An effect is, for what an element draws in a frame, an `Animation`
|
|
6
6
|
* (`core/animation.ts`) of what is done to its cells: its value at `t` is a
|
|
7
|
-
* function from a cell's
|
|
8
|
-
*
|
|
7
|
+
* function from a cell's look and where the cell is to the cell's new look
|
|
8
|
+
* (`EffectFrame`), and it says when that next changes as every
|
|
9
9
|
* animation does. Whatever holds for the whole frame is worked out once, in
|
|
10
10
|
* the effect and its `at(t)`, and every cell of the frame is handed to the
|
|
11
11
|
* function `at(t)` returns, but for those a wrapper keeps back: `confined`
|
|
12
12
|
* hands over only the cells its owners drew, so an effect never counts on
|
|
13
|
-
* seeing them all. Shimmer, pulse, fade
|
|
14
|
-
* differing by what the function does with its
|
|
15
|
-
* [LAW:one-type-per-behavior]; two effects stack by nesting one
|
|
16
|
-
* another, which is the same as calling one after the other but
|
|
17
|
-
* outer one measures: the inner one's output.
|
|
13
|
+
* seeing them all. Shimmer, pulse, fade, dissolve and a blinking attribute
|
|
14
|
+
* are all this one shape, differing by what the function does with its
|
|
15
|
+
* inputs [LAW:one-type-per-behavior]; two effects stack by nesting one
|
|
16
|
+
* wrapper in another, which is the same as calling one after the other but
|
|
17
|
+
* for what the outer one measures: the inner one's output.
|
|
18
|
+
*
|
|
19
|
+
* A cell's look is one value, its colours and its attributes together
|
|
20
|
+
* (`CellLook`), rather than an attribute stage beside the colour one, so
|
|
21
|
+
* everything that takes an `Effect` — `confined`, `onBeat`,
|
|
22
|
+
* `Animation.all`, nesting over a glyph effect — takes one that moves
|
|
23
|
+
* attributes with no second copy of itself. `reverse` is the one attribute
|
|
24
|
+
* a look leaves out (`CellAttributes`): it decides which of a style's slots
|
|
25
|
+
* a colour on screen is in, and a swap of the two is already a colour move.
|
|
18
26
|
*
|
|
19
27
|
* What the element draws (`Drawn`) — the cells the effect will be handed,
|
|
20
28
|
* and the colours of its words — is measured here, in the render, off the
|
|
@@ -23,10 +31,12 @@
|
|
|
23
31
|
* against (`onColors`, `./effects.ts`), so no caller derives it a second time
|
|
24
32
|
* from a render of its own.
|
|
25
33
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* the
|
|
29
|
-
*
|
|
34
|
+
* An `Effected` is the element under its effect over time: an
|
|
35
|
+
* `Animation<Renderable>`, placed wherever a renderable goes and drawn at
|
|
36
|
+
* the time of the frame it is drawn in. Its value at `t` is the element
|
|
37
|
+
* through the effect at `t` (`Recolored`). [LAW:no-ambient-temporal-coupling]
|
|
38
|
+
* The time is the frame's, never a clock read, so the same time always draws
|
|
39
|
+
* the same bytes, at 30 frames a second or one every two seconds.
|
|
30
40
|
*
|
|
31
41
|
* What an effect may not do is the contract this module holds:
|
|
32
42
|
*
|
|
@@ -36,22 +46,25 @@
|
|
|
36
46
|
* segment's link and meta and carries its anchor shifted to the run's first
|
|
37
47
|
* cell (`Style.shiftedBy`), so hit testing still finds the owner.
|
|
38
48
|
* - A cell whose colours come back unchanged keeps its original `ColorSpec`,
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* the
|
|
49
|
+
* one whose attributes come back as its style set them keeps them, and a
|
|
50
|
+
* segment whose every cell is unchanged is yielded with its look as
|
|
51
|
+
* it was, so the identity effect is byte-identical; what an `Effected` gains is when
|
|
52
|
+
* the frame next changes (`Style.hold`), which writes no byte.
|
|
42
53
|
* Neighbouring cells the output depth
|
|
43
54
|
* writes alike stay one segment: a segment is cut only where the written
|
|
44
|
-
* colours change, so two colours that land on one index
|
|
55
|
+
* colours or the attributes change, so two colours that land on one index
|
|
56
|
+
* are one run.
|
|
45
57
|
* - New colours are built with `ColorSpec.fromRgba`, never parsed from a
|
|
46
58
|
* string, so no frame adds an entry to a parse cache.
|
|
47
|
-
* - With no colour output (`colorSystem: null`)
|
|
48
|
-
*
|
|
59
|
+
* - With no colour output (`colorSystem: null`) no style is written at all,
|
|
60
|
+
* attributes included, so there is nothing for an effect to change, and
|
|
61
|
+
* the child's segments pass through untouched.
|
|
49
62
|
*
|
|
50
63
|
* Positions are the child's own: row and column count from the top-left of
|
|
51
64
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
52
65
|
*/
|
|
53
66
|
import { drawnBy, shiftAnchor } from "../core/anchor.js";
|
|
54
|
-
import { Animation } from "../core/animation.js";
|
|
67
|
+
import { Animation, frameTimeOf, holdOf, shownAt, stampFrame } from "../core/animation.js";
|
|
55
68
|
import { cellLen, graphemes } from "../core/cells.js";
|
|
56
69
|
import { ColorDepth, ColorSpec, SURFACE_BLACK } from "../core/color.js";
|
|
57
70
|
import { fnv1a } from "../core/fnv1a.js";
|
|
@@ -59,8 +72,18 @@ import { Measurement } from "../core/measure.js";
|
|
|
59
72
|
import { render } from "../core/protocol.js";
|
|
60
73
|
import { Segment } from "../core/segment.js";
|
|
61
74
|
import { JOIN, colorsOwnedBy } from "../core/strip.js";
|
|
62
|
-
import { Style } from "../core/style.js";
|
|
63
|
-
|
|
75
|
+
import { ATTRIBUTE_NAMES, Style } from "../core/style.js";
|
|
76
|
+
import { finiteTime } from "../core/easing.js";
|
|
77
|
+
/** What drew nothing the effect can change: held forever, as far as the effect says. */
|
|
78
|
+
const UNMOVED = { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY };
|
|
79
|
+
/**
|
|
80
|
+
* An `Effected` at `t`, or an element through an effect at a time an
|
|
81
|
+
* animation's `at` was handed — a lifecycle's time since it entered: `renderable`
|
|
82
|
+
* with its cells' looks passed through `effect` at `t`, saying nothing of
|
|
83
|
+
* when that changes, since whatever placed the animation stamps its hold.
|
|
84
|
+
* [LAW:one-type-per-behavior]
|
|
85
|
+
*/
|
|
86
|
+
export class Recolored {
|
|
64
87
|
renderable;
|
|
65
88
|
effect;
|
|
66
89
|
t;
|
|
@@ -69,25 +92,20 @@ export class Effected {
|
|
|
69
92
|
constructor(renderable, effect, options) {
|
|
70
93
|
this.renderable = renderable;
|
|
71
94
|
this.effect = effect;
|
|
72
|
-
|
|
73
|
-
throw new RangeError(`Effected needs a finite t, got ${options.t}`);
|
|
74
|
-
}
|
|
75
|
-
this.t = options.t;
|
|
95
|
+
this.t = finiteTime("Effected", options.t);
|
|
76
96
|
this.key = options.key;
|
|
77
97
|
this.theme = options.theme;
|
|
78
98
|
}
|
|
79
99
|
render(options) {
|
|
100
|
+
return this.moved(options).segments;
|
|
101
|
+
}
|
|
102
|
+
/** The frame the effect draws at `t`, and the hold it says that is in. */
|
|
103
|
+
moved(options) {
|
|
80
104
|
const segments = render(this.renderable, options);
|
|
81
105
|
// With no colour output nothing the effect does is written, so the child
|
|
82
106
|
// is what is shown, and it changes when the child says it does.
|
|
83
107
|
if (options.colorSystem === null)
|
|
84
|
-
return segments;
|
|
85
|
-
return this._moved(segments, options);
|
|
86
|
-
}
|
|
87
|
-
*contents() {
|
|
88
|
-
yield this.renderable;
|
|
89
|
-
}
|
|
90
|
-
*_moved(segments, options) {
|
|
108
|
+
return { segments, hold: UNMOVED };
|
|
91
109
|
// No depth named is no downgrade, as `drawnColors` reads it.
|
|
92
110
|
const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
|
|
93
111
|
// [LAW:one-source-of-truth] The frame is measured before any cell is
|
|
@@ -96,9 +114,8 @@ export class Effected {
|
|
|
96
114
|
const looked = looks(segments, depth, this.theme);
|
|
97
115
|
const walked = walk(looked);
|
|
98
116
|
const animation = this.effect(measured(looked, () => walked, depth, this.theme));
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
yield* Segment.applyStyle(this._recolored(walked, animation.at(this.t), depth), new Style({ nextChange: animation.nextChange(this.t) }));
|
|
117
|
+
const shown = shownAt(animation, this.t, options);
|
|
118
|
+
return { segments: this._recolored(walked, shown.value, depth), hold: holdOf(shown, this.t) };
|
|
102
119
|
}
|
|
103
120
|
*_recolored(walked, frame, depth) {
|
|
104
121
|
for (const { segment, look, glyphs } of walked) {
|
|
@@ -106,35 +123,38 @@ export class Effected {
|
|
|
106
123
|
yield segment;
|
|
107
124
|
continue;
|
|
108
125
|
}
|
|
109
|
-
const { style: base, wire, slots, pair } = look;
|
|
126
|
+
const { style: base, wire, slots, pair, attributes } = look;
|
|
110
127
|
const { terminal } = pair;
|
|
111
|
-
const from = { fg: pair.fg, bg: pair.bg };
|
|
128
|
+
const from = { fg: pair.fg, bg: pair.bg, attributes };
|
|
112
129
|
// [LAW:one-source-of-truth] A run is cells the wire writes alike: each
|
|
113
130
|
// cell is drawn at the output depth first and merged on the colours that
|
|
114
|
-
// writes, so two truecolors landing on one index
|
|
115
|
-
// the effect gave its neighbour's
|
|
131
|
+
// writes and on its attributes, so two truecolors landing on one index
|
|
132
|
+
// stay one run. A cell the effect gave its neighbour's look is drawn as
|
|
133
|
+
// that neighbour was.
|
|
116
134
|
const runs = [];
|
|
117
135
|
for (const { glyph, cell } of glyphs) {
|
|
136
|
+
const moved = cell === undefined ? from : returned(frame(from, { ...cell, seed: cellSeed(this.key, cell.row, cell.col), terminal, reverse: pair.reverse }));
|
|
118
137
|
// Back from the screen to the style's slots: the same swap undoes itself.
|
|
119
|
-
const colors =
|
|
138
|
+
const colors = onScreen(moved, pair.reverse);
|
|
120
139
|
const last = runs[runs.length - 1];
|
|
121
140
|
const writes = last !== undefined && sameColors(last.colors, colors) ? last.drawn : this.respec(wire, slots, colors, depth);
|
|
122
|
-
if (last !== undefined && writesAlike(last.drawn, writes)) {
|
|
141
|
+
if (last !== undefined && writesAlike(last.drawn, writes) && sameAttributes(last.attributes, moved.attributes)) {
|
|
123
142
|
last.text += glyph;
|
|
124
143
|
last.cells += cellLen(glyph);
|
|
125
144
|
last.colors = colors;
|
|
126
145
|
}
|
|
127
146
|
else {
|
|
128
|
-
runs.push({ text: glyph, cells: cellLen(glyph), colors, drawn: writes });
|
|
147
|
+
runs.push({ text: glyph, cells: cellLen(glyph), colors, attributes: moved.attributes, drawn: writes });
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
|
-
if (runs.every((run) => writesAlike(run.drawn, wire))) {
|
|
150
|
+
if (runs.every((run) => writesAlike(run.drawn, wire) && sameAttributes(run.attributes, attributes))) {
|
|
132
151
|
yield segment;
|
|
133
152
|
continue;
|
|
134
153
|
}
|
|
135
154
|
let offset = 0;
|
|
136
155
|
for (const run of runs) {
|
|
137
|
-
const
|
|
156
|
+
const placed = base.shiftedBy(offset);
|
|
157
|
+
const shifted = sameAttributes(run.attributes, attributes) ? placed : placed.withAttributes(cellAttributes(run.attributes));
|
|
138
158
|
const style = writesAlike(run.drawn, wire) ? shifted : shifted.add(Style.fromColor(run.drawn.color, run.drawn.bgcolor));
|
|
139
159
|
yield new Segment(run.text, style);
|
|
140
160
|
offset += run.cells;
|
|
@@ -144,6 +164,61 @@ export class Effected {
|
|
|
144
164
|
measure(options) {
|
|
145
165
|
return Measurement.get(options, this.renderable);
|
|
146
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* The specs that draw a cell's slot colours `to` where the segment drew
|
|
169
|
+
* `from` with `wire`: each moved slot in the spec `movedSpecs` writes it
|
|
170
|
+
* in, the rest as `wire` wrote them.
|
|
171
|
+
*/
|
|
172
|
+
respec(wire, from, to, depth) {
|
|
173
|
+
const moved = movedSpecs(from, to, depth, this.theme);
|
|
174
|
+
return { color: moved.fg ?? wire.color, bgcolor: moved.bg ?? wire.bgcolor };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* `renderable` under `effect` over time: an `Animation<Renderable>` whose
|
|
179
|
+
* value at `t` is the element through the effect at `t`, placed wherever a
|
|
180
|
+
* renderable goes. Its own answer to when it changes is `t`, at every time:
|
|
181
|
+
* an `Effect` is made from what the element draws, so with nothing drawn
|
|
182
|
+
* nothing can say more — early, never false. Placed, it says exactly: its
|
|
183
|
+
* cells carry the effect's own hold at the frame's time and the output's
|
|
184
|
+
* depth (`stampFrame`), under whatever the element already said, so at a
|
|
185
|
+
* depth that shows no motion a loop says it never changes there and the
|
|
186
|
+
* host sleeps.
|
|
187
|
+
*/
|
|
188
|
+
export class Effected {
|
|
189
|
+
renderable;
|
|
190
|
+
effect;
|
|
191
|
+
key;
|
|
192
|
+
theme;
|
|
193
|
+
constructor(renderable, effect, options) {
|
|
194
|
+
this.renderable = renderable;
|
|
195
|
+
this.effect = effect;
|
|
196
|
+
this.key = options.key;
|
|
197
|
+
this.theme = options.theme;
|
|
198
|
+
}
|
|
199
|
+
/** The element through the effect at `t`, unstamped. */
|
|
200
|
+
at(t) {
|
|
201
|
+
return new Recolored(this.renderable, this.effect, { t, key: this.key, theme: this.theme });
|
|
202
|
+
}
|
|
203
|
+
nextChange(t) {
|
|
204
|
+
return t;
|
|
205
|
+
}
|
|
206
|
+
lastChange(t) {
|
|
207
|
+
return t;
|
|
208
|
+
}
|
|
209
|
+
/** The element under the effect with its motion taken out. */
|
|
210
|
+
reduced() {
|
|
211
|
+
const { effect } = this;
|
|
212
|
+
return new Effected(this.renderable, (drawn) => effect(drawn).reduced(), this);
|
|
213
|
+
}
|
|
214
|
+
/** Drawn at the frame's time, its cells stamped with the effect's exact hold there. */
|
|
215
|
+
render(options) {
|
|
216
|
+
const { segments, hold } = this.at(frameTimeOf(options)).moved(options);
|
|
217
|
+
return stampFrame(segments, hold);
|
|
218
|
+
}
|
|
219
|
+
measure(options) {
|
|
220
|
+
return Measurement.get(options, this.renderable);
|
|
221
|
+
}
|
|
147
222
|
/**
|
|
148
223
|
* The colours the screen shows for `pair` once it is drawn moved to `to`,
|
|
149
224
|
* both as the screen shows them, at `depth` on `theme`: what an effect
|
|
@@ -156,19 +231,14 @@ export class Effected {
|
|
|
156
231
|
const moved = movedSpecs(from, onScreen(to, pair.reverse), depth, theme);
|
|
157
232
|
return onScreen({ fg: moved.fg?.getTruecolor(theme, true) ?? from.fg, bg: moved.bg?.getTruecolor(theme, false) ?? from.bg }, pair.reverse);
|
|
158
233
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
/** A cell's seed: FNV-1a of the key and the position, scaled into [0, 1). */
|
|
169
|
-
seed(row, col) {
|
|
170
|
-
return Number.parseInt(fnv1a(`${this.key}:${row}:${col}`), 16) / 0x1_0000_0000;
|
|
171
|
-
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* A cell's seed: FNV-1a of the element's key and the cell's position, scaled
|
|
237
|
+
* into [0, 1). [LAW:one-source-of-truth] One rule for a colour effect's seeds
|
|
238
|
+
* and a glyph effect's, so one element under both twinkles from one seed.
|
|
239
|
+
*/
|
|
240
|
+
export function cellSeed(key, row, col) {
|
|
241
|
+
return Number.parseInt(fnv1a(`${key}:${row}:${col}`), 16) / 0x1_0000_0000;
|
|
172
242
|
}
|
|
173
243
|
const DEFAULT = ColorSpec.default();
|
|
174
244
|
/** Each of `segments` with how it is drawn at `depth` on `theme`; a control segment draws nothing. */
|
|
@@ -181,34 +251,48 @@ function looks(segments, depth, theme) {
|
|
|
181
251
|
const slots = { fg: (wire.color ?? DEFAULT).getTruecolor(theme, true), bg: (wire.bgcolor ?? DEFAULT).getTruecolor(theme, false) };
|
|
182
252
|
const reverse = style.reverse === true;
|
|
183
253
|
const terminal = onScreen({ fg: wire.color?.isDefault ?? true, bg: wire.bgcolor?.isDefault ?? true }, reverse);
|
|
184
|
-
return { segment, look: { style, wire, slots, pair: { ...onScreen(slots, reverse), terminal, reverse } } };
|
|
254
|
+
return { segment, look: { style, wire, slots, pair: { ...onScreen(slots, reverse), terminal, reverse }, attributes: cellAttributes(style) } };
|
|
185
255
|
});
|
|
186
256
|
}
|
|
187
257
|
/**
|
|
188
|
-
* Each glyph of `
|
|
189
|
-
* top-left of the output, a wide glyph one cell at its first
|
|
190
|
-
* anchor shifted to that column as `Style.shiftedBy` shifts
|
|
191
|
-
*
|
|
192
|
-
*
|
|
258
|
+
* Each glyph of each of `segments`, where its cell sits: rows and columns
|
|
259
|
+
* from the top-left of the output, a wide glyph one cell at its first
|
|
260
|
+
* column, and its anchor shifted to that column as `Style.shiftedBy` shifts
|
|
261
|
+
* a run's. A control segment draws nothing and has none.
|
|
262
|
+
* [LAW:one-source-of-truth] The one walk: what `Drawn.cells` lists, what the
|
|
263
|
+
* render hands a colour effect, and what a glyph effect (`./glyph-effect.ts`)
|
|
264
|
+
* is handed are all read off it.
|
|
193
265
|
*/
|
|
194
|
-
function
|
|
266
|
+
export function walkGlyphs(segments) {
|
|
195
267
|
let row = 0;
|
|
196
268
|
let col = 0;
|
|
197
|
-
return
|
|
198
|
-
if (
|
|
199
|
-
return
|
|
200
|
-
const anchor =
|
|
269
|
+
return segments.map((segment) => {
|
|
270
|
+
if (segment.isControl)
|
|
271
|
+
return [];
|
|
272
|
+
const anchor = segment.style?.anchor;
|
|
201
273
|
// Cells into the segment, as `base.shiftedBy` counts them in the render.
|
|
202
274
|
let into = 0;
|
|
203
|
-
|
|
275
|
+
return [...graphemes(segment.text)].map((glyph) => {
|
|
204
276
|
const cell = glyph === "\n" ? undefined : { row, col, anchor: anchor && shiftAnchor(anchor, into) };
|
|
205
277
|
[row, col] = glyph === "\n" ? [row + 1, 0] : [row, col + cellLen(glyph)];
|
|
206
278
|
into += cellLen(glyph);
|
|
207
279
|
return { glyph, cell };
|
|
208
280
|
});
|
|
209
|
-
return { segment, look, glyphs };
|
|
210
281
|
});
|
|
211
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Whether `text` draws ink: anything but blanks. [LAW:one-source-of-truth]
|
|
285
|
+
* The one test, so the words a colour effect lights are the words a glyph
|
|
286
|
+
* effect (`./glyph-effects.ts`) scrambles and its particles pass behind.
|
|
287
|
+
*/
|
|
288
|
+
export function inked(text) {
|
|
289
|
+
return text.trim() !== "";
|
|
290
|
+
}
|
|
291
|
+
/** `looked` with each segment's glyphs placed, by the one walk. */
|
|
292
|
+
function walk(looked) {
|
|
293
|
+
const placed = walkGlyphs(looked.map(({ segment }) => segment));
|
|
294
|
+
return looked.map((item, i) => ({ ...item, glyphs: placed[i] }));
|
|
295
|
+
}
|
|
212
296
|
/** Whether `style` draws in its ink on a blank glyph: a line under, through or over it, or a frame round it. */
|
|
213
297
|
function drawsLines(style) {
|
|
214
298
|
return [style.underline, style.underline2, style.strike, style.overline, style.frame, style.encircle].some((on) => on === true);
|
|
@@ -227,9 +311,9 @@ function measured(looked, walked, depth, theme) {
|
|
|
227
311
|
return [];
|
|
228
312
|
// A blank glyph shows its ground, and any line its style draws in its ink.
|
|
229
313
|
const blankShows = look.pair.bg.hex !== ground || drawsLines(look.style);
|
|
230
|
-
return glyphs.flatMap(({ glyph, cell }) => (cell !== undefined && (glyph
|
|
314
|
+
return glyphs.flatMap(({ glyph, cell }) => (cell !== undefined && (inked(glyph) || blankShows) ? [cell] : []));
|
|
231
315
|
});
|
|
232
|
-
const words = looked.flatMap(({ segment, look }) => (look !== undefined && segment.text
|
|
316
|
+
const words = looked.flatMap(({ segment, look }) => (look !== undefined && inked(segment.text) && !drawnBy(look.style.anchor, JOIN) ? [look.pair] : []));
|
|
233
317
|
const pairs = [...new Map(words.map((pair) => [`${pair.fg.hex}/${pair.bg.hex}/${pair.terminal.fg}/${pair.terminal.bg}/${pair.reverse}`, pair])).values()];
|
|
234
318
|
// [LAW:one-source-of-truth] A slot the terminal fills is no colour of the element's, whatever its hex.
|
|
235
319
|
const own = (slot) => pairs.flatMap((pair) => (pair.terminal[slot] ? [] : [pair[slot].hex]));
|
|
@@ -242,44 +326,6 @@ function measured(looked, walked, depth, theme) {
|
|
|
242
326
|
theme,
|
|
243
327
|
};
|
|
244
328
|
}
|
|
245
|
-
/**
|
|
246
|
-
* `effect` sampled and held on `beat`: drawn at the moment the hold in
|
|
247
|
-
* progress began (`Beat.lastChange`), so within a hold every frame is the frame
|
|
248
|
-
* at its start, and at each hold's start it moves on. A continuous effect
|
|
249
|
-
* given a short curve becomes one that steps — a `wheel` turning a sixth of
|
|
250
|
-
* a turn once a second — and at one redraw a second a hold shorter than a
|
|
251
|
-
* second is skipped, as frames are. [LAW:no-ambient-temporal-coupling] The
|
|
252
|
-
* held time is a function of `t`, so the same `t` draws the same bytes at
|
|
253
|
-
* any rate. The frame next changes at a hold's start, and only one the
|
|
254
|
-
* sampled effect has moved by: the first hold start at or after what it
|
|
255
|
-
* answers for the frame held now (`Beat.nextChange`). An effect that moves
|
|
256
|
-
* every frame makes the beat's own answer, the end of this hold; one that is
|
|
257
|
-
* done and says never leaves the beat saying never too, since the hold after
|
|
258
|
-
* this one would draw the same frame. The answer is read off what the effect
|
|
259
|
-
* says, never off a comparison of frames: an effect that steps and is back
|
|
260
|
-
* on the same colour by the next hold still turns the beat there, an early
|
|
261
|
-
* answer the contract allows.
|
|
262
|
-
*/
|
|
263
|
-
export function onBeat(effect, beat) {
|
|
264
|
-
return (drawn) => {
|
|
265
|
-
const sampled = effect(drawn);
|
|
266
|
-
return {
|
|
267
|
-
at: (t) => sampled.at(beat.lastChange(t)),
|
|
268
|
-
nextChange(t) {
|
|
269
|
-
const moved = sampled.nextChange(beat.lastChange(t));
|
|
270
|
-
if (moved === Number.POSITIVE_INFINITY)
|
|
271
|
-
return moved;
|
|
272
|
-
const end = beat.nextChange(t);
|
|
273
|
-
// The hold boundaries after `t` are `end`, then each hold's end in turn;
|
|
274
|
-
// the frame turns at the first one the effect has moved by.
|
|
275
|
-
return moved <= end ? end : beat.lastChange(moved) === moved ? moved : beat.nextChange(moved);
|
|
276
|
-
},
|
|
277
|
-
// The frame held now began at this hold's start at the latest: an early
|
|
278
|
-
// answer where the effect had not moved by it, which the contract allows.
|
|
279
|
-
lastChange: (t) => beat.lastChange(t),
|
|
280
|
-
};
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
329
|
/**
|
|
284
330
|
* `effect` on the colours any of `owners` drew, itself or through an owner
|
|
285
331
|
* nested in its output, and every other colour left as it is: one item of a
|
|
@@ -299,12 +345,13 @@ export function confined(effect, ...owners) {
|
|
|
299
345
|
// The frame as the effect sees it: the owners' cells alone. [LAW:one-source-of-truth] One narrowing, handed to both stages.
|
|
300
346
|
const owned = (drawn) => ({ ...drawn, cells: () => drawn.cells().filter((cell) => any(ownedOf(cell))) });
|
|
301
347
|
// The owners' cells change when the effect's frame does.
|
|
302
|
-
return (drawn) => Animation.map(effect(owned(drawn)), (frame) => (
|
|
348
|
+
return (drawn) => Animation.map(effect(owned(drawn)), (frame) => (look, cell) => {
|
|
303
349
|
const owns = ownedOf(cell);
|
|
304
350
|
if (!any(owns))
|
|
305
|
-
return
|
|
306
|
-
const moved = frame(
|
|
307
|
-
|
|
351
|
+
return look;
|
|
352
|
+
const moved = frame(look, cell);
|
|
353
|
+
// An attribute is drawn on the glyph, in its ink, so it moves with whoever owns the ink.
|
|
354
|
+
return { fg: owns.fg ? moved.fg : look.fg, bg: owns.bg ? moved.bg : look.bg, attributes: owns.fg ? moved.attributes : look.attributes };
|
|
308
355
|
});
|
|
309
356
|
}
|
|
310
357
|
/**
|
|
@@ -351,6 +398,32 @@ function slotWritesAlike(a, b, foreground) {
|
|
|
351
398
|
function written(spec, foreground) {
|
|
352
399
|
return spec?.getAnsiCodes(foreground).join(";") ?? "";
|
|
353
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* The look a frame returned, refused at once when it carries no attributes.
|
|
403
|
+
* [LAW:no-silent-failure] A frame written before an effect moved attributes
|
|
404
|
+
* returns `{ fg, bg }`, and the effects playground runs a visitor's frame with
|
|
405
|
+
* its types stripped, not checked; unrefused, it fails as a TypeError in run
|
|
406
|
+
* merging that names neither the frame nor the contract.
|
|
407
|
+
*/
|
|
408
|
+
function returned(look) {
|
|
409
|
+
if (look.attributes === undefined) {
|
|
410
|
+
throw new TypeError("an effect frame returns the look it was handed with its changes, { ...look, fg, bg }: this one returned no attributes");
|
|
411
|
+
}
|
|
412
|
+
return look;
|
|
413
|
+
}
|
|
414
|
+
/** The attributes a look carries: `ATTRIBUTE_NAMES` but `reverse`. */
|
|
415
|
+
const MOVED = ATTRIBUTE_NAMES.filter((name) => name !== "reverse");
|
|
416
|
+
/**
|
|
417
|
+
* The attributes a look carries, read off anything that sets them — a
|
|
418
|
+
* `Style`, or a look a frame returned with more on it than `CellAttributes`
|
|
419
|
+
* names. [LAW:one-source-of-truth] The one place `reverse` is left out.
|
|
420
|
+
*/
|
|
421
|
+
export function cellAttributes(of) {
|
|
422
|
+
return Object.fromEntries(MOVED.map((name) => [name, of[name]]));
|
|
423
|
+
}
|
|
424
|
+
function sameAttributes(a, b) {
|
|
425
|
+
return a === b || MOVED.every((name) => a[name] === b[name]);
|
|
426
|
+
}
|
|
354
427
|
function sameColors(a, b) {
|
|
355
428
|
return sameColor(a.fg, b.fg) && sameColor(a.bg, b.bg);
|
|
356
429
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The effect catalogue: shimmer, pulse, sparkle and wheel, which loop for as
|
|
3
|
-
* long as they are drawn,
|
|
4
|
-
*
|
|
3
|
+
* long as they are drawn, fade-in and dissolve-out, which run once and are
|
|
4
|
+
* done, a blink and a bold pulse, which move an attribute on a beat, and a
|
|
5
|
+
* gradient, which lays a colour ramp across an element and may move it. Each is an `Effect` for `Effected` (`./effect.ts`); the defaults
|
|
5
6
|
* in `EFFECT_CURVES` are the ones signed off by eye in the effects-feel demo
|
|
6
7
|
* (`npm run effects-feel`), at 30 fps and at one frame a second, and
|
|
7
8
|
* `EFFECT_CATALOGUE` makes every one of them at those, by name, one way.
|
|
@@ -13,7 +14,10 @@
|
|
|
13
14
|
* One effect here is not ambience but a signal, and keeps none of this on
|
|
14
15
|
* purpose: `steppedInk` steps its ink through colours on a beat, the news
|
|
15
16
|
* that something is at its limit, and jumps from slot to slot where the
|
|
16
|
-
* loops hold still.
|
|
17
|
+
* loops hold still. Nor is `gradient` a light: it paints an ink rather than
|
|
18
|
+
* lighting it, so it may darken one, and a ramp scrolled across it jumps
|
|
19
|
+
* where the ramp wraps unless its ends are one colour. Of the list below it
|
|
20
|
+
* keeps the words. What makes the loops' motions familiar is what every
|
|
17
21
|
* curve of theirs keeps:
|
|
18
22
|
*
|
|
19
23
|
* - Nothing jumps. Every curve is continuous in time, built from smooth
|
|
@@ -46,9 +50,11 @@
|
|
|
46
50
|
* cards runs this file's own declarations as the program a visitor edits
|
|
47
51
|
* (`examples/effects-playground/programs.ts`), on the published library.
|
|
48
52
|
*/
|
|
53
|
+
import { Animation } from "../core/animation.js";
|
|
49
54
|
import { ColorRgba } from "../core/color.js";
|
|
50
|
-
import { type Timing } from "../core/easing.js";
|
|
51
|
-
import
|
|
55
|
+
import { type Beat, type Timing } from "../core/easing.js";
|
|
56
|
+
import type { ColorRamp } from "../themes/ramp.js";
|
|
57
|
+
import { type CellAttributes, type Drawn, type Effect, type EffectCell } from "./effect.js";
|
|
52
58
|
/** What a loop does to one colour at strength `w` in [0, 1]: at 0, nothing. */
|
|
53
59
|
export type Touch = (color: ColorRgba, w: number) => ColorRgba;
|
|
54
60
|
/**
|
|
@@ -98,6 +104,28 @@ export declare function shares({ pairs, depth, theme }: Drawn, colors: ReadonlyS
|
|
|
98
104
|
* drawing what an earlier one drew costs no settling.
|
|
99
105
|
*/
|
|
100
106
|
export declare function onColors(loop: Loop, colors?: ReadonlySet<string>): Effect;
|
|
107
|
+
/**
|
|
108
|
+
* A ramp laid across the element: each lit cell's ink is the colour `ramp`
|
|
109
|
+
* shows at `t` at the cell's column, the element's leftmost cell reading
|
|
110
|
+
* the ramp's first stop and its rightmost its last, each at its cell's
|
|
111
|
+
* centre — or, where that colour would not read on the cell's ground, the
|
|
112
|
+
* nearest that does (`legibleInks`, as `steppedInk` writes its steps). A
|
|
113
|
+
* ramp that moves (`ColorRamp.moving`) scrolls along the element and turns
|
|
114
|
+
* its hues, and the frame changes when the ramp does — every frame for a
|
|
115
|
+
* loop, never again once a tween settles; a still one is
|
|
116
|
+
* `Animation.constant(ramp)`. Lit cells are those whose ink is one of
|
|
117
|
+
* `colors`, named as a style names them (`"#a6e3a1"`, `"bright_green"`),
|
|
118
|
+
* opaque, and matched as the screen shows them at the frame's depth — so
|
|
119
|
+
* below truecolor every ink that lands on the same palette colour is lit —
|
|
120
|
+
* by default every colour the element sets itself — never an ink the terminal supplies: a
|
|
121
|
+
* rule's line by its colour and not its title, a panel's border and not its
|
|
122
|
+
* body. The element is where its frame lists
|
|
123
|
+
* cells, as the fireflies read it, so a padded or confined element has the
|
|
124
|
+
* ramp across what it shows. On a sixteen-colour terminal nothing moves
|
|
125
|
+
* (`stillOnSlots`): a gradient there is jumps between slots, and the cells
|
|
126
|
+
* hold as drawn.
|
|
127
|
+
*/
|
|
128
|
+
export declare function gradient(ramp: Animation<ColorRamp>, colors?: readonly string[]): Effect;
|
|
101
129
|
/**
|
|
102
130
|
* A loop as an `Effect` on colours by hex, each touched at the share
|
|
103
131
|
* `share` gives it of the cell's strength in a frame drawing what `Drawn`
|
|
@@ -115,6 +143,14 @@ export interface Curve extends Timing {
|
|
|
115
143
|
/** How far the move goes at full intensity, in [0, 1]; what 1 means is the effect's. */
|
|
116
144
|
readonly swing: number;
|
|
117
145
|
}
|
|
146
|
+
/** Throws, naming the field, unless `curve` has a positive, finite `seconds` and a `swing` in [0, 1]. */
|
|
147
|
+
export declare function requireCurve(curve: Curve): void;
|
|
148
|
+
/**
|
|
149
|
+
* Throws, naming `what`, unless `z` is a finite place in the noise. Noise at
|
|
150
|
+
* a place that is not a number is not a number, which no effect could tell
|
|
151
|
+
* from a dim cell or an empty sky. [LAW:no-silent-failure]
|
|
152
|
+
*/
|
|
153
|
+
export declare function requireZ(what: string, z: number): void;
|
|
118
154
|
/**
|
|
119
155
|
* Each effect's curve as it was signed off. Every loop's period is a prime
|
|
120
156
|
* number of seconds, so no two loops come back into step within a sitting.
|
|
@@ -241,13 +277,48 @@ export declare function veiled(visibility: Visibility, swing: number): Effect;
|
|
|
241
277
|
* draws it at the time since it began. `done(t)` is true from the moment
|
|
242
278
|
* every cell has arrived or gone, so the caller knows when to stop drawing an
|
|
243
279
|
* element that is leaving, or to drop the effect from one that has arrived.
|
|
244
|
-
*
|
|
245
|
-
*
|
|
280
|
+
*
|
|
281
|
+
* `schedule` is when its frame next changes on that clock, whatever the
|
|
282
|
+
* element draws: what a caller drawing the transition inside an animation —
|
|
283
|
+
* a lifecycle — says its frame does, since the frame it hands over carries
|
|
284
|
+
* no word of its own (`stampFrame`, `../core/animation.ts`). Until done it changes
|
|
285
|
+
* every frame, or on each hold of a beat (`onBeat`), and from then never.
|
|
286
|
+
* [LAW:one-source-of-truth] `done` is that schedule read as a fact — done
|
|
287
|
+
* from the first moment it never changes again — so no transition carries
|
|
288
|
+
* two answers to when it is over.
|
|
246
289
|
*/
|
|
247
|
-
export
|
|
248
|
-
readonly effect:
|
|
290
|
+
export declare class Transition<E = Effect> {
|
|
291
|
+
readonly effect: E;
|
|
292
|
+
readonly schedule: Animation<unknown>;
|
|
293
|
+
constructor(effect: E, schedule: Animation<unknown>);
|
|
249
294
|
done(t: number): boolean;
|
|
250
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* `effect` run once over `seconds` as a `Transition`: what it draws until it
|
|
298
|
+
* is done, then held. [LAW:single-enforcer] The one rule for when a
|
|
299
|
+
* transition is done and what it answers after, for a colour effect and a
|
|
300
|
+
* glyph effect (`./glyph-effects.ts`) alike.
|
|
301
|
+
*/
|
|
302
|
+
export declare function transition<D, F>(seconds: number, effect: (drawn: D) => Animation<F>): Transition<(drawn: D) => Animation<F>>;
|
|
303
|
+
/**
|
|
304
|
+
* `effect` sampled and held on `beat`: drawn at the moment the hold in
|
|
305
|
+
* progress began (`Beat.lastChange`), so within a hold every frame is the frame
|
|
306
|
+
* at its start, and at each hold's start it moves on. A continuous effect
|
|
307
|
+
* given a short curve becomes one that steps — a `wheel` turning a sixth of
|
|
308
|
+
* a turn once a second — and at one redraw a second a hold shorter than a
|
|
309
|
+
* second is skipped, as frames are. [LAW:no-ambient-temporal-coupling] The
|
|
310
|
+
* held time is a function of `t`, so the same `t` draws the same bytes at
|
|
311
|
+
* any rate. The frame next changes at a hold's start, and only one the
|
|
312
|
+
* sampled effect has moved by (`Animation.onBeat`): an effect that moves
|
|
313
|
+
* every frame turns the beat at the end of this hold, and one that is done
|
|
314
|
+
* and says never leaves the beat saying never too.
|
|
315
|
+
*
|
|
316
|
+
* A transition steps the same way, its schedule with it, so a fade-in on a
|
|
317
|
+
* beat arrives a hold at a time and is done at the first hold that starts
|
|
318
|
+
* after it was: an element arriving through it wakes its host on the beats.
|
|
319
|
+
*/
|
|
320
|
+
export declare function onBeat(effect: Effect, beat: Beat): Effect;
|
|
321
|
+
export declare function onBeat<E extends (drawn: never) => Animation<unknown>>(transition: Transition<E>, beat: Beat): Transition<E>;
|
|
251
322
|
/**
|
|
252
323
|
* Fade-in, as ink blooming in water: patches of the element surface first
|
|
253
324
|
* and the rest follows outward from them, each cell rising smoothly over its
|
|
@@ -307,7 +378,8 @@ export interface ColorStep {
|
|
|
307
378
|
* that does (`stillOnSlots`). A cell whose ground no offered colour reads
|
|
308
379
|
* on keeps the ink it rested in.
|
|
309
380
|
*
|
|
310
|
-
* The search is settled once for each
|
|
381
|
+
* The search is settled once for each ground and step — below truecolor,
|
|
382
|
+
* which colours read on a ground once for that ground — so a frame
|
|
311
383
|
* inside a step redoes none, and nor does a later pass of the beat: at sixty
|
|
312
384
|
* frames a second a one-second step is sixty identical frames, and the
|
|
313
385
|
* frame changes when the beat does: the signal is the beat mapped to inks.
|
|
@@ -315,9 +387,47 @@ export interface ColorStep {
|
|
|
315
387
|
* nothing here reads a clock.
|
|
316
388
|
*/
|
|
317
389
|
export declare function steppedInk(steps: readonly ColorStep[]): Effect;
|
|
390
|
+
/**
|
|
391
|
+
* A text attribute turned on and off on a beat: each pass, the cell as its
|
|
392
|
+
* element drew it for `off` seconds, then with `attribute` on for `on`.
|
|
393
|
+
*/
|
|
394
|
+
export interface Blink {
|
|
395
|
+
readonly attribute: keyof CellAttributes;
|
|
396
|
+
readonly off: number;
|
|
397
|
+
readonly on: number;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* `spec.attribute` blinking: off, the cell as drawn, so that hold's frame is
|
|
401
|
+
* the identity and writes the element's own bytes; on, the attribute set.
|
|
402
|
+
* It changes when its beat does, at a hold's end, and never between.
|
|
403
|
+
* A blink and a bold pulse are this one function at two specs
|
|
404
|
+
* (`ATTRIBUTE_DEFAULTS`) [LAW:one-type-per-behavior].
|
|
405
|
+
*/
|
|
406
|
+
export declare function blink({ attribute, off, on }: Blink): Effect;
|
|
407
|
+
/** The names of the effects that move an attribute. */
|
|
408
|
+
export type AttributeLoopName = "blink" | "boldPulse";
|
|
409
|
+
/**
|
|
410
|
+
* Each attribute effect's spec: a blink hides the words for half of each
|
|
411
|
+
* second, as a terminal's own blink does, and a bold pulse holds them bold
|
|
412
|
+
* for the last three tenths of each second.
|
|
413
|
+
*/
|
|
414
|
+
export declare const ATTRIBUTE_DEFAULTS: {
|
|
415
|
+
readonly blink: {
|
|
416
|
+
readonly attribute: "conceal";
|
|
417
|
+
readonly off: 0.5;
|
|
418
|
+
readonly on: 0.5;
|
|
419
|
+
};
|
|
420
|
+
readonly boldPulse: {
|
|
421
|
+
readonly attribute: "bold";
|
|
422
|
+
readonly off: 0.7;
|
|
423
|
+
readonly on: 0.3;
|
|
424
|
+
};
|
|
425
|
+
};
|
|
318
426
|
/**
|
|
319
427
|
* What a catalogue effect is made for: the element it is drawn on, at `z` in
|
|
320
|
-
* the noise, so two elements under one effect do not move in
|
|
428
|
+
* the noise, so two elements under one effect drawn from noise do not move in
|
|
429
|
+
* lockstep. An effect on a beat alone (a blink) draws no noise, and keeps
|
|
430
|
+
* every element in step, as a terminal's own blink does. How
|
|
321
431
|
* wide the element is, the effect reads off each frame (`Drawn.cells`).
|
|
322
432
|
*/
|
|
323
433
|
export interface EffectTarget {
|
|
@@ -341,4 +451,5 @@ export type TransitionName = "fade" | "dissolve";
|
|
|
341
451
|
export declare const EFFECT_CATALOGUE: {
|
|
342
452
|
readonly loops: Readonly<Record<LoopName, (element: EffectTarget, curve?: Curve) => Effect>>;
|
|
343
453
|
readonly transitions: Readonly<Record<TransitionName, (element: EffectTarget, curve?: Curve) => Transition>>;
|
|
454
|
+
readonly attributes: Readonly<Record<AttributeLoopName, (element: EffectTarget) => Effect>>;
|
|
344
455
|
};
|