@promptctl/rich-js 0.24.0 → 0.24.1
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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/animation.d.ts +78 -0
- package/dist/core/animation.js +107 -0
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +17 -11
- package/dist/core/cells.js +100 -25
- package/dist/core/console.js +5 -5
- package/dist/core/easing.d.ts +110 -10
- package/dist/core/easing.js +269 -36
- package/dist/core/json.js +2 -1
- package/dist/core/measure.js +2 -2
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +2 -2
- package/dist/core/protocol.d.ts +73 -19
- package/dist/core/protocol.js +56 -15
- package/dist/core/render.js +2 -1
- package/dist/core/segment.d.ts +18 -1
- package/dist/core/segment.js +45 -44
- package/dist/core/strip.d.ts +3 -2
- package/dist/core/strip.js +11 -7
- package/dist/core/style.js +17 -6
- package/dist/core/text.js +22 -14
- package/dist/core/timeline.d.ts +84 -0
- package/dist/core/timeline.js +144 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +69 -0
- package/dist/host/app.js +2 -2
- package/dist/index.d.ts +10 -7
- package/dist/index.js +13 -4
- package/dist/renderables/align.d.ts +3 -2
- package/dist/renderables/align.js +3 -0
- package/dist/renderables/columns.d.ts +3 -2
- package/dist/renderables/columns.js +7 -2
- package/dist/renderables/constrain.d.ts +3 -2
- package/dist/renderables/constrain.js +5 -1
- package/dist/renderables/effect.d.ts +43 -58
- package/dist/renderables/effect.js +58 -64
- package/dist/renderables/effects.d.ts +16 -14
- package/dist/renderables/effects.js +64 -45
- package/dist/renderables/embed.js +3 -2
- package/dist/renderables/flexStrip.d.ts +3 -2
- package/dist/renderables/flexStrip.js +6 -1
- package/dist/renderables/frames.d.ts +40 -19
- package/dist/renderables/frames.js +48 -21
- package/dist/renderables/group.d.ts +3 -2
- package/dist/renderables/group.js +5 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +3 -2
- package/dist/renderables/layout.js +15 -2
- package/dist/renderables/list-view.d.ts +3 -2
- package/dist/renderables/list-view.js +12 -5
- package/dist/renderables/live.js +2 -2
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1421 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +8 -20
- package/dist/renderables/padding.js +32 -50
- package/dist/renderables/panel.d.ts +26 -26
- package/dist/renderables/panel.js +67 -103
- package/dist/renderables/progress.d.ts +4 -2
- package/dist/renderables/progress.js +11 -2
- package/dist/renderables/progressBar.js +2 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +24 -0
- package/dist/renderables/spinner.d.ts +26 -14
- package/dist/renderables/spinner.js +49 -37
- package/dist/renderables/syntax.js +2 -1
- package/dist/renderables/table.d.ts +19 -11
- package/dist/renderables/table.js +140 -156
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +6 -5
- package/dist/renderables/tree.js +11 -7
- package/dist/renderables/viewport.d.ts +13 -5
- package/dist/renderables/viewport.js +41 -20
- package/dist/template-bindings/animation-funcs.js +2 -2
- package/dist/template-bindings/index.js +2 -1
- package/dist/themes/ramp.d.ts +18 -3
- package/dist/themes/ramp.js +23 -2
- package/dist/widgets/focus-manager.d.ts +21 -14
- package/dist/widgets/focus-manager.js +28 -29
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/static-item.d.ts +4 -3
- package/dist/widgets/static-item.js +9 -7
- package/dist/widgets/text-input.d.ts +28 -18
- package/dist/widgets/text-input.js +144 -101
- package/dist/widgets/widget-app.d.ts +19 -3
- package/dist/widgets/widget-app.js +39 -21
- package/dist/widgets/widget-base.js +0 -3
- package/package.json +2 -5
|
@@ -2,24 +2,26 @@
|
|
|
2
2
|
* Effected — a renderable whose finished cells' colours pass through an
|
|
3
3
|
* effect at a moment `t`.
|
|
4
4
|
*
|
|
5
|
-
* An effect is
|
|
6
|
-
*
|
|
7
|
-
* cell's colours and where the cell is
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
5
|
+
* An effect is, for what an element draws in a frame, an `Animation`
|
|
6
|
+
* (`core/animation.ts`) of what is done to its cells: its value at `t` is a
|
|
7
|
+
* function from a cell's colours and where the cell is to the cell's new
|
|
8
|
+
* colours (`EffectFrame`), and it says when that next changes as every
|
|
9
|
+
* animation does. Whatever holds for the whole frame is worked out once, in
|
|
10
|
+
* the effect and its `at(t)`, and every cell of the frame is handed to the
|
|
11
|
+
* function `at(t)` returns, but for those a wrapper keeps back: `confined`
|
|
12
|
+
* hands over only the cells its owners drew, so an effect never counts on
|
|
13
|
+
* seeing them all. Shimmer, pulse, fade and dissolve are all this one shape,
|
|
14
|
+
* differing by what the function does with its inputs
|
|
15
|
+
* [LAW:one-type-per-behavior]; two effects stack by nesting one wrapper in
|
|
16
|
+
* another, which is the same as calling one after the other but for what the
|
|
17
|
+
* outer one measures: the inner one's output.
|
|
16
18
|
*
|
|
17
|
-
* What the element draws (`Drawn`) — the cells the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* effect
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* What the element draws (`Drawn`) — the cells the effect will be handed,
|
|
20
|
+
* and the colours of its words — is measured here, in the render, off the
|
|
21
|
+
* same reading of each segment its cells are drawn from, and handed to the
|
|
22
|
+
* effect once a frame. It is what an effect keeping words legible is settled
|
|
23
|
+
* against (`onColors`, `./effects.ts`), so no caller derives it a second time
|
|
24
|
+
* from a render of its own.
|
|
23
25
|
*
|
|
24
26
|
* [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
|
|
25
27
|
* clock read. Whoever draws frames builds the view for each frame's `t`, so
|
|
@@ -49,9 +51,10 @@
|
|
|
49
51
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
50
52
|
*/
|
|
51
53
|
import { type Anchor } from "../core/anchor.js";
|
|
54
|
+
import { Animation } from "../core/animation.js";
|
|
52
55
|
import { ColorDepth, type ColorRgba, type TerminalTheme } from "../core/color.js";
|
|
53
56
|
import type { Beat } from "../core/easing.js";
|
|
54
|
-
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
57
|
+
import type { Composite, Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
55
58
|
import { Segment } from "../core/segment.js";
|
|
56
59
|
/**
|
|
57
60
|
* A cell's foreground and background as they appear on screen — the glyph's
|
|
@@ -119,7 +122,7 @@ export interface Drawn {
|
|
|
119
122
|
/**
|
|
120
123
|
* Every cell the element shows something in this frame — a glyph, a
|
|
121
124
|
* ground that is not the terminal's, or a line its style draws — where it
|
|
122
|
-
* sits and who drew it, listed before the
|
|
125
|
+
* sits and who drew it, listed before the effect is handed any: what
|
|
123
126
|
* an effect spread over the element — a transition, which has its last cell
|
|
124
127
|
* settle as it ends — is spread over. A blank cell on the terminal's ground
|
|
125
128
|
* that draws no line is handed but not listed, since nothing in it shows.
|
|
@@ -138,42 +141,23 @@ export interface Drawn {
|
|
|
138
141
|
/** The terminal the frame is drawn on. */
|
|
139
142
|
readonly theme: TerminalTheme;
|
|
140
143
|
}
|
|
144
|
+
/** What an effect does to the cells of one frame: each cell's colours and where it is in, its colours out. Return `colors` to leave a cell be. */
|
|
145
|
+
export type EffectFrame = (colors: CellColors, cell: EffectCell) => CellColors;
|
|
141
146
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* cell's frame is the last one's.
|
|
147
|
+
* For an element drawing `drawn`, the animation of what is done to its cells.
|
|
148
|
+
* [LAW:types-are-the-program] Per-frame work is `at(t)`, so it runs once a
|
|
149
|
+
* frame by the shape of the call, never by a cache asking whether this cell's
|
|
150
|
+
* frame is the last one's.
|
|
147
151
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* a loop that
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* function is an effect that moves every frame, and answers `t`
|
|
156
|
-
* (`nextChangeOf`): the one true answer for a frame nothing has promised to
|
|
157
|
-
* hold, so a loop needs no second shape.
|
|
152
|
+
* Its `nextChange` is when the frame next changes: a stepped effect its next
|
|
153
|
+
* step's start, a transition `t` until it is done and `Infinity` after, a
|
|
154
|
+
* loop that holds every cell still at the frame's depth `Infinity` there, and
|
|
155
|
+
* a loop that moves `t` (`Animation.continuous`). [LAW:types-are-the-program]
|
|
156
|
+
* The answer is a fact about the frame, so the animation is made from
|
|
157
|
+
* everything the frame is drawn from; one made from `t` alone could not say
|
|
158
|
+
* that a frame is still because of its depth.
|
|
158
159
|
*/
|
|
159
|
-
export
|
|
160
|
-
(t: number, drawn: Drawn): (colors: CellColors, cell: EffectCell) => CellColors;
|
|
161
|
-
/** When the frame drawn at `t` of `drawn` next changes, in seconds on `t`'s timeline, never before `t`; absent, every frame. */
|
|
162
|
-
readonly nextChange?: (t: number, drawn: Drawn) => number;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* When `effect`'s frame at `t` of `drawn` next changes: what it says, or `t`
|
|
166
|
-
* for an effect that says nothing. [LAW:single-enforcer] The one reading of
|
|
167
|
-
* an absent `nextChange`, so no caller holds its own default.
|
|
168
|
-
*/
|
|
169
|
-
export declare function nextChangeOf(effect: Effect, t: number, drawn: Drawn): number;
|
|
170
|
-
/**
|
|
171
|
-
* An effect drawing as `effect` does, with `nextChange` as its answer to when
|
|
172
|
-
* its frame next changes, in place of whatever `effect` answered; `effect`
|
|
173
|
-
* itself is left as it was. A composer whose answer depends on the inner
|
|
174
|
-
* effect's reads it with `nextChangeOf`, as `onBeat` and `confined` do.
|
|
175
|
-
*/
|
|
176
|
-
export declare function withNextChange(effect: Effect, nextChange: (t: number, drawn: Drawn) => number): Effect;
|
|
160
|
+
export type Effect = (drawn: Drawn) => Animation<EffectFrame>;
|
|
177
161
|
export interface EffectedOptions {
|
|
178
162
|
/** The moment to draw, in seconds. */
|
|
179
163
|
readonly t: number;
|
|
@@ -189,7 +173,7 @@ export interface EffectedOptions {
|
|
|
189
173
|
*/
|
|
190
174
|
readonly theme: TerminalTheme;
|
|
191
175
|
}
|
|
192
|
-
export declare class Effected implements Renderable, Measurable {
|
|
176
|
+
export declare class Effected implements Renderable, Measurable, Composite {
|
|
193
177
|
readonly renderable: Renderable;
|
|
194
178
|
readonly effect: Effect;
|
|
195
179
|
readonly t: number;
|
|
@@ -197,6 +181,7 @@ export declare class Effected implements Renderable, Measurable {
|
|
|
197
181
|
readonly theme: TerminalTheme;
|
|
198
182
|
constructor(renderable: Renderable, effect: Effect, options: EffectedOptions);
|
|
199
183
|
render(options: RenderOptions): Iterable<Segment>;
|
|
184
|
+
contents(): Iterable<Renderable>;
|
|
200
185
|
private _moved;
|
|
201
186
|
private _recolored;
|
|
202
187
|
measure(options: RenderOptions): {
|
|
@@ -222,7 +207,7 @@ export declare class Effected implements Renderable, Measurable {
|
|
|
222
207
|
}
|
|
223
208
|
/**
|
|
224
209
|
* `effect` sampled and held on `beat`: drawn at the moment the hold in
|
|
225
|
-
* progress began (`Beat.
|
|
210
|
+
* progress began (`Beat.lastChange`), so within a hold every frame is the frame
|
|
226
211
|
* at its start, and at each hold's start it moves on. A continuous effect
|
|
227
212
|
* given a short curve becomes one that steps — a `wheel` turning a sixth of
|
|
228
213
|
* a turn once a second — and at one redraw a second a hold shorter than a
|
|
@@ -230,10 +215,10 @@ export declare class Effected implements Renderable, Measurable {
|
|
|
230
215
|
* held time is a function of `t`, so the same `t` draws the same bytes at
|
|
231
216
|
* any rate. The frame next changes at a hold's start, and only one the
|
|
232
217
|
* sampled effect has moved by: the first hold start at or after what it
|
|
233
|
-
* answers for the frame held now (`Beat.
|
|
234
|
-
* frame makes the beat's own answer, the end of this hold; one that is
|
|
235
|
-
* and says never leaves the beat saying never too, since the hold after
|
|
236
|
-
* one would draw the same frame. The answer is read off what the effect
|
|
218
|
+
* answers for the frame held now (`Beat.nextChange`). An effect that moves
|
|
219
|
+
* every frame makes the beat's own answer, the end of this hold; one that is
|
|
220
|
+
* done and says never leaves the beat saying never too, since the hold after
|
|
221
|
+
* this one would draw the same frame. The answer is read off what the effect
|
|
237
222
|
* says, never off a comparison of frames: an effect that steps and is back
|
|
238
223
|
* on the same colour by the next hold still turns the beat there, an early
|
|
239
224
|
* answer the contract allows.
|
|
@@ -2,24 +2,26 @@
|
|
|
2
2
|
* Effected — a renderable whose finished cells' colours pass through an
|
|
3
3
|
* effect at a moment `t`.
|
|
4
4
|
*
|
|
5
|
-
* An effect is
|
|
6
|
-
*
|
|
7
|
-
* cell's colours and where the cell is
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
5
|
+
* An effect is, for what an element draws in a frame, an `Animation`
|
|
6
|
+
* (`core/animation.ts`) of what is done to its cells: its value at `t` is a
|
|
7
|
+
* function from a cell's colours and where the cell is to the cell's new
|
|
8
|
+
* colours (`EffectFrame`), and it says when that next changes as every
|
|
9
|
+
* animation does. Whatever holds for the whole frame is worked out once, in
|
|
10
|
+
* the effect and its `at(t)`, and every cell of the frame is handed to the
|
|
11
|
+
* function `at(t)` returns, but for those a wrapper keeps back: `confined`
|
|
12
|
+
* hands over only the cells its owners drew, so an effect never counts on
|
|
13
|
+
* seeing them all. Shimmer, pulse, fade and dissolve are all this one shape,
|
|
14
|
+
* differing by what the function does with its inputs
|
|
15
|
+
* [LAW:one-type-per-behavior]; two effects stack by nesting one wrapper in
|
|
16
|
+
* another, which is the same as calling one after the other but for what the
|
|
17
|
+
* outer one measures: the inner one's output.
|
|
16
18
|
*
|
|
17
|
-
* What the element draws (`Drawn`) — the cells the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* effect
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* What the element draws (`Drawn`) — the cells the effect will be handed,
|
|
20
|
+
* and the colours of its words — is measured here, in the render, off the
|
|
21
|
+
* same reading of each segment its cells are drawn from, and handed to the
|
|
22
|
+
* effect once a frame. It is what an effect keeping words legible is settled
|
|
23
|
+
* against (`onColors`, `./effects.ts`), so no caller derives it a second time
|
|
24
|
+
* from a render of its own.
|
|
23
25
|
*
|
|
24
26
|
* [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
|
|
25
27
|
* clock read. Whoever draws frames builds the view for each frame's `t`, so
|
|
@@ -49,30 +51,15 @@
|
|
|
49
51
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
50
52
|
*/
|
|
51
53
|
import { drawnBy, shiftAnchor } from "../core/anchor.js";
|
|
54
|
+
import { Animation } from "../core/animation.js";
|
|
52
55
|
import { cellLen, graphemes } from "../core/cells.js";
|
|
53
56
|
import { ColorDepth, ColorSpec, SURFACE_BLACK } from "../core/color.js";
|
|
54
57
|
import { fnv1a } from "../core/fnv1a.js";
|
|
55
58
|
import { Measurement } from "../core/measure.js";
|
|
59
|
+
import { render } from "../core/protocol.js";
|
|
56
60
|
import { Segment } from "../core/segment.js";
|
|
57
61
|
import { JOIN, colorsOwnedBy } from "../core/strip.js";
|
|
58
62
|
import { Style } from "../core/style.js";
|
|
59
|
-
/**
|
|
60
|
-
* When `effect`'s frame at `t` of `drawn` next changes: what it says, or `t`
|
|
61
|
-
* for an effect that says nothing. [LAW:single-enforcer] The one reading of
|
|
62
|
-
* an absent `nextChange`, so no caller holds its own default.
|
|
63
|
-
*/
|
|
64
|
-
export function nextChangeOf(effect, t, drawn) {
|
|
65
|
-
return effect.nextChange?.(t, drawn) ?? t;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* An effect drawing as `effect` does, with `nextChange` as its answer to when
|
|
69
|
-
* its frame next changes, in place of whatever `effect` answered; `effect`
|
|
70
|
-
* itself is left as it was. A composer whose answer depends on the inner
|
|
71
|
-
* effect's reads it with `nextChangeOf`, as `onBeat` and `confined` do.
|
|
72
|
-
*/
|
|
73
|
-
export function withNextChange(effect, nextChange) {
|
|
74
|
-
return Object.assign((t, drawn) => effect(t, drawn), { nextChange });
|
|
75
|
-
}
|
|
76
63
|
export class Effected {
|
|
77
64
|
renderable;
|
|
78
65
|
effect;
|
|
@@ -90,27 +77,28 @@ export class Effected {
|
|
|
90
77
|
this.theme = options.theme;
|
|
91
78
|
}
|
|
92
79
|
render(options) {
|
|
93
|
-
const segments = this.renderable
|
|
80
|
+
const segments = render(this.renderable, options);
|
|
94
81
|
// With no colour output nothing the effect does is written, so the child
|
|
95
82
|
// is what is shown, and it changes when the child says it does.
|
|
96
83
|
if (options.colorSystem === null)
|
|
97
84
|
return segments;
|
|
98
85
|
return this._moved(segments, options);
|
|
99
86
|
}
|
|
87
|
+
*contents() {
|
|
88
|
+
yield this.renderable;
|
|
89
|
+
}
|
|
100
90
|
*_moved(segments, options) {
|
|
101
91
|
// No depth named is no downgrade, as `drawnColors` reads it.
|
|
102
92
|
const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
|
|
103
93
|
// [LAW:one-source-of-truth] The frame is measured before any cell is
|
|
104
94
|
// handed to the effect, off the same looks its cells are drawn from, and
|
|
105
|
-
// the one measure is what
|
|
106
|
-
// asked with.
|
|
95
|
+
// the one measure is what the effect's animation is made from.
|
|
107
96
|
const looked = looks(segments, depth, this.theme);
|
|
108
97
|
const walked = walk(looked);
|
|
109
|
-
const
|
|
110
|
-
const frame = this.effect(this.t, drawn);
|
|
98
|
+
const animation = this.effect(measured(looked, () => walked, depth, this.theme));
|
|
111
99
|
// What the effect draws at `t` is drawn until its next change, and the
|
|
112
100
|
// cells say so, under whatever the child already said (the sooner wins).
|
|
113
|
-
yield* Segment.applyStyle(this._recolored(walked,
|
|
101
|
+
yield* Segment.applyStyle(this._recolored(walked, animation.at(this.t), depth), new Style({ nextChange: animation.nextChange(this.t) }));
|
|
114
102
|
}
|
|
115
103
|
*_recolored(walked, frame, depth) {
|
|
116
104
|
for (const { segment, look, glyphs } of walked) {
|
|
@@ -256,7 +244,7 @@ function measured(looked, walked, depth, theme) {
|
|
|
256
244
|
}
|
|
257
245
|
/**
|
|
258
246
|
* `effect` sampled and held on `beat`: drawn at the moment the hold in
|
|
259
|
-
* progress began (`Beat.
|
|
247
|
+
* progress began (`Beat.lastChange`), so within a hold every frame is the frame
|
|
260
248
|
* at its start, and at each hold's start it moves on. A continuous effect
|
|
261
249
|
* given a short curve becomes one that steps — a `wheel` turning a sixth of
|
|
262
250
|
* a turn once a second — and at one redraw a second a hold shorter than a
|
|
@@ -264,24 +252,33 @@ function measured(looked, walked, depth, theme) {
|
|
|
264
252
|
* held time is a function of `t`, so the same `t` draws the same bytes at
|
|
265
253
|
* any rate. The frame next changes at a hold's start, and only one the
|
|
266
254
|
* sampled effect has moved by: the first hold start at or after what it
|
|
267
|
-
* answers for the frame held now (`Beat.
|
|
268
|
-
* frame makes the beat's own answer, the end of this hold; one that is
|
|
269
|
-
* and says never leaves the beat saying never too, since the hold after
|
|
270
|
-
* one would draw the same frame. The answer is read off what the effect
|
|
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
|
|
271
259
|
* says, never off a comparison of frames: an effect that steps and is back
|
|
272
260
|
* on the same colour by the next hold still turns the beat there, an early
|
|
273
261
|
* answer the contract allows.
|
|
274
262
|
*/
|
|
275
263
|
export function onBeat(effect, beat) {
|
|
276
|
-
return
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
};
|
|
285
282
|
}
|
|
286
283
|
/**
|
|
287
284
|
* `effect` on the colours any of `owners` drew, itself or through an owner
|
|
@@ -302,16 +299,13 @@ export function confined(effect, ...owners) {
|
|
|
302
299
|
// The frame as the effect sees it: the owners' cells alone. [LAW:one-source-of-truth] One narrowing, handed to both stages.
|
|
303
300
|
const owned = (drawn) => ({ ...drawn, cells: () => drawn.cells().filter((cell) => any(ownedOf(cell))) });
|
|
304
301
|
// The owners' cells change when the effect's frame does.
|
|
305
|
-
return
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return { fg: owns.fg ? moved.fg : colors.fg, bg: owns.bg ? moved.bg : colors.bg };
|
|
313
|
-
};
|
|
314
|
-
}, (t, drawn) => nextChangeOf(effect, t, owned(drawn)));
|
|
302
|
+
return (drawn) => Animation.map(effect(owned(drawn)), (frame) => (colors, cell) => {
|
|
303
|
+
const owns = ownedOf(cell);
|
|
304
|
+
if (!any(owns))
|
|
305
|
+
return colors;
|
|
306
|
+
const moved = frame(colors, cell);
|
|
307
|
+
return { fg: owns.fg ? moved.fg : colors.fg, bg: owns.bg ? moved.bg : colors.bg };
|
|
308
|
+
});
|
|
315
309
|
}
|
|
316
310
|
/**
|
|
317
311
|
* What `segments`, drawn at `depth` on `theme`, draw their words in, measured
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* (`examples/effects-playground/programs.ts`), on the published library.
|
|
48
48
|
*/
|
|
49
49
|
import { ColorRgba } from "../core/color.js";
|
|
50
|
-
import { type
|
|
50
|
+
import { type Timing } from "../core/easing.js";
|
|
51
51
|
import { type Drawn, type Effect, type EffectCell } from "./effect.js";
|
|
52
52
|
/** What a loop does to one colour at strength `w` in [0, 1]: at 0, nothing. */
|
|
53
53
|
export type Touch = (color: ColorRgba, w: number) => ColorRgba;
|
|
@@ -104,12 +104,14 @@ export declare function onColors(loop: Loop, colors?: ReadonlySet<string>): Effe
|
|
|
104
104
|
* says; a colour it gives none, and one the terminal supplies, stays as it is.
|
|
105
105
|
*/
|
|
106
106
|
export declare function atShares(loop: Loop, share: (drawn: Drawn) => ReadonlyMap<string, number>): Effect;
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
/**
|
|
108
|
+
* How the curves below are tuned: a `Timing` — seconds, a looping effect's
|
|
109
|
+
* period or a transition's duration, and an ease, intensity in [0, 1] to
|
|
110
|
+
* the fraction of `swing` applied; one that overshoots, as a CSS
|
|
111
|
+
* `cubic-bezier` may, saturates, since an effect never acts past its swing —
|
|
112
|
+
* and the swing itself.
|
|
113
|
+
*/
|
|
114
|
+
export interface Curve extends Timing {
|
|
113
115
|
/** How far the move goes at full intensity, in [0, 1]; what 1 means is the effect's. */
|
|
114
116
|
readonly swing: number;
|
|
115
117
|
}
|
|
@@ -122,32 +124,32 @@ export interface Curve {
|
|
|
122
124
|
export declare const EFFECT_CURVES: {
|
|
123
125
|
readonly shimmer: {
|
|
124
126
|
readonly seconds: 109;
|
|
125
|
-
readonly ease: Ease;
|
|
127
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
126
128
|
readonly swing: 0.9;
|
|
127
129
|
};
|
|
128
130
|
readonly pulse: {
|
|
129
131
|
readonly seconds: 43;
|
|
130
|
-
readonly ease: Ease;
|
|
132
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
131
133
|
readonly swing: 0.85;
|
|
132
134
|
};
|
|
133
135
|
readonly sparkle: {
|
|
134
136
|
readonly seconds: 139;
|
|
135
|
-
readonly ease: Ease;
|
|
137
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
136
138
|
readonly swing: 0.95;
|
|
137
139
|
};
|
|
138
140
|
readonly wheel: {
|
|
139
141
|
readonly seconds: 907;
|
|
140
|
-
readonly ease: Ease;
|
|
142
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
141
143
|
readonly swing: 1;
|
|
142
144
|
};
|
|
143
145
|
readonly fade: {
|
|
144
146
|
readonly seconds: 20;
|
|
145
|
-
readonly ease: Ease;
|
|
147
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
146
148
|
readonly swing: 1;
|
|
147
149
|
};
|
|
148
150
|
readonly dissolve: {
|
|
149
151
|
readonly seconds: 30;
|
|
150
|
-
readonly ease: Ease;
|
|
152
|
+
readonly ease: import("../core/easing.js").Ease;
|
|
151
153
|
readonly swing: 1;
|
|
152
154
|
};
|
|
153
155
|
};
|
|
@@ -308,7 +310,7 @@ export interface ColorStep {
|
|
|
308
310
|
* The search is settled once for each ink, ground and step, so a frame
|
|
309
311
|
* inside a step redoes none, and nor does a later pass of the beat: at sixty
|
|
310
312
|
* frames a second a one-second step is sixty identical frames, and the
|
|
311
|
-
* frame
|
|
313
|
+
* frame changes when the beat does: the signal is the beat mapped to inks.
|
|
312
314
|
* [LAW:no-ambient-temporal-coupling] The beat is `core/easing.ts`'s, and
|
|
313
315
|
* nothing here reads a clock.
|
|
314
316
|
*/
|
|
@@ -46,13 +46,14 @@
|
|
|
46
46
|
* cards runs this file's own declarations as the program a visitor edits
|
|
47
47
|
* (`examples/effects-playground/programs.ts`), on the published library.
|
|
48
48
|
*/
|
|
49
|
+
import { Animation } from "../core/animation.js";
|
|
49
50
|
import { ColorDepth, ColorRgba, ColorSpec, contrastRatio, relativeLuminance } from "../core/color.js";
|
|
50
51
|
import { EASES, Phase, beat } from "../core/easing.js";
|
|
51
52
|
import { fbm, hash, noise, smoothstep } from "../core/noise.js";
|
|
52
53
|
import { Memo } from "../core/memo.js";
|
|
53
54
|
import { Oklch } from "../core/oklch.js";
|
|
54
55
|
import { ensureContrast } from "../themes/colorMath.js";
|
|
55
|
-
import { Effected
|
|
56
|
+
import { Effected } from "./effect.js";
|
|
56
57
|
/** The contrast no touch takes a cell below, unless it rested below it: WCAG AA for body text. */
|
|
57
58
|
const LEGIBLE = 4.5;
|
|
58
59
|
/**
|
|
@@ -182,14 +183,16 @@ function pairKey({ fg, bg, terminal, reverse }) {
|
|
|
182
183
|
* says; a colour it gives none, and one the terminal supplies, stays as it is.
|
|
183
184
|
*/
|
|
184
185
|
export function atShares(loop, share) {
|
|
185
|
-
return (
|
|
186
|
+
return (drawn) => {
|
|
186
187
|
const shared = share(drawn);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
return Animation.continuous((t) => {
|
|
189
|
+
const field = loop.field(t, drawn);
|
|
190
|
+
return ({ fg, bg }, cell) => {
|
|
191
|
+
const w = Math.round(clamp01(field(cell)) * LEVELS) / LEVELS;
|
|
192
|
+
const moved = (color, terminal) => touchedAt(loop.touch, color, (terminal ? 0 : (shared.get(color.hex) ?? 0)) * w);
|
|
193
|
+
return { fg: moved(fg, cell.terminal.fg), bg: moved(bg, cell.terminal.bg) };
|
|
194
|
+
};
|
|
195
|
+
});
|
|
193
196
|
};
|
|
194
197
|
}
|
|
195
198
|
/**
|
|
@@ -205,7 +208,7 @@ export function atShares(loop, share) {
|
|
|
205
208
|
* loop on such a terminal draws it once.
|
|
206
209
|
*/
|
|
207
210
|
function stillOnSlots(loop) {
|
|
208
|
-
return
|
|
211
|
+
return (drawn) => (onlySlots(drawn.depth) ? Animation.constant((colors) => colors) : loop(drawn));
|
|
209
212
|
}
|
|
210
213
|
/** Whether `depth` writes nothing between the slots it has: the sixteen, at either sixteen-colour depth, or none. */
|
|
211
214
|
function onlySlots(depth) {
|
|
@@ -554,18 +557,19 @@ export function sparkle(curve, glow, z) {
|
|
|
554
557
|
* hiding — 1 reaches fully invisible. A cell wholly shown is returned as it was.
|
|
555
558
|
*/
|
|
556
559
|
export function veiled(visibility, swing) {
|
|
557
|
-
return (
|
|
558
|
-
const { theme } = drawn;
|
|
559
|
-
const shows = visibility(t, drawn);
|
|
560
|
+
return (drawn) => {
|
|
560
561
|
// [LAW:one-source-of-truth] The ground is the terminal `Effected` draws on, never a second one handed here.
|
|
561
|
-
const ground = Oklch.fromRgba(theme.backgroundColor);
|
|
562
|
-
return (
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
562
|
+
const ground = Oklch.fromRgba(drawn.theme.backgroundColor);
|
|
563
|
+
return Animation.continuous((t) => {
|
|
564
|
+
const shows = visibility(t, drawn);
|
|
565
|
+
return (colors, cell) => {
|
|
566
|
+
const hidden = swing * (1 - clamp01(shows(cell)));
|
|
567
|
+
if (hidden === 0)
|
|
568
|
+
return colors;
|
|
569
|
+
const bg = blend(Oklch.fromRgba(colors.bg), ground, hidden);
|
|
570
|
+
return { fg: blend(Oklch.fromRgba(colors.fg), bg, hidden).toRgba(), bg: bg.toRgba() };
|
|
571
|
+
};
|
|
572
|
+
});
|
|
569
573
|
};
|
|
570
574
|
}
|
|
571
575
|
/**
|
|
@@ -588,7 +592,18 @@ function order(z) {
|
|
|
588
592
|
}
|
|
589
593
|
function transition(curve, effect) {
|
|
590
594
|
const done = (t) => t >= curve.seconds;
|
|
591
|
-
return {
|
|
595
|
+
return {
|
|
596
|
+
effect: (drawn) => {
|
|
597
|
+
const moving = effect(drawn);
|
|
598
|
+
return {
|
|
599
|
+
at: (t) => moving.at(t),
|
|
600
|
+
nextChange: (t) => (done(t) ? Number.POSITIVE_INFINITY : moving.nextChange(t)),
|
|
601
|
+
// Done, the frame settled when the transition did; until then it is the effect's.
|
|
602
|
+
lastChange: (t) => (done(t) ? curve.seconds : moving.lastChange(t)),
|
|
603
|
+
};
|
|
604
|
+
},
|
|
605
|
+
done,
|
|
606
|
+
};
|
|
592
607
|
}
|
|
593
608
|
/**
|
|
594
609
|
* Of a transition's `seconds`, how long each cell's own change takes. Under
|
|
@@ -735,7 +750,7 @@ export function wheel(curve, z) {
|
|
|
735
750
|
};
|
|
736
751
|
// A turned colour's bounds rest on what a frame draws, never on `t`: settled once for each.
|
|
737
752
|
const settled = new WeakMap();
|
|
738
|
-
return stillOnSlots((
|
|
753
|
+
return stillOnSlots((drawn) => {
|
|
739
754
|
const { colors, fills, depth, theme } = drawn;
|
|
740
755
|
// Whether a slot shows one of the element's colours: never the terminal's, whatever its hex.
|
|
741
756
|
const own = (terminal, color) => !terminal && colors.has(color.hex);
|
|
@@ -752,21 +767,23 @@ export function wheel(curve, z) {
|
|
|
752
767
|
// 256 colours writes a turned colour as one it has an index for; truecolor
|
|
753
768
|
// as one of the two either side of its luminance.
|
|
754
769
|
const indexed = measuredIndexed(depth, theme);
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
+
return Animation.continuous((t) => {
|
|
771
|
+
const written = new Map();
|
|
772
|
+
const choose = (color, segment) => {
|
|
773
|
+
const turn = hueTurned(color, turned(segment, t));
|
|
774
|
+
return nearestWithin(turn.looks, color, depth === ColorDepth.TRUECOLOR ? turn.either.map(measured) : indexed, bounds.get(keyOf(color, segment)) ?? UNBOUNDED);
|
|
775
|
+
};
|
|
776
|
+
const write = (color, segment) => {
|
|
777
|
+
const found = written.get(keyOf(color, segment)) ?? choose(color, segment);
|
|
778
|
+
written.set(keyOf(color, segment), found);
|
|
779
|
+
return found;
|
|
780
|
+
};
|
|
781
|
+
return (shown, cell) => {
|
|
782
|
+
const mine = { fg: own(cell.terminal.fg, shown.fg), bg: own(cell.terminal.bg, shown.bg) };
|
|
783
|
+
const turn = (color, ours, other, theirs) => (ours ? write(color, segmentOf(color, other, theirs)) : color);
|
|
784
|
+
return { fg: turn(shown.fg, mine.fg, shown.bg, mine.bg), bg: turn(shown.bg, mine.bg, shown.fg, mine.fg) };
|
|
785
|
+
};
|
|
786
|
+
});
|
|
770
787
|
});
|
|
771
788
|
}
|
|
772
789
|
/**
|
|
@@ -890,7 +907,7 @@ function nearestOf(color, offered) {
|
|
|
890
907
|
* The search is settled once for each ink, ground and step, so a frame
|
|
891
908
|
* inside a step redoes none, and nor does a later pass of the beat: at sixty
|
|
892
909
|
* frames a second a one-second step is sixty identical frames, and the
|
|
893
|
-
* frame
|
|
910
|
+
* frame changes when the beat does: the signal is the beat mapped to inks.
|
|
894
911
|
* [LAW:no-ambient-temporal-coupling] The beat is `core/easing.ts`'s, and
|
|
895
912
|
* nothing here reads a clock.
|
|
896
913
|
*/
|
|
@@ -900,15 +917,17 @@ export function steppedInk(steps) {
|
|
|
900
917
|
// `steps` said whatever the caller does to its array.
|
|
901
918
|
const inks = steps.map((step) => step.color);
|
|
902
919
|
const settled = new WeakMap();
|
|
903
|
-
return
|
|
904
|
-
const step = inks[rhythm.at(t)];
|
|
920
|
+
return ({ depth, theme }) => {
|
|
905
921
|
const memo = settled.get(theme) ?? new Memo();
|
|
906
922
|
settled.set(theme, memo);
|
|
907
|
-
return (
|
|
908
|
-
const
|
|
909
|
-
return
|
|
910
|
-
|
|
911
|
-
|
|
923
|
+
return Animation.map(rhythm, (i) => {
|
|
924
|
+
const step = inks[i];
|
|
925
|
+
return (colors, cell) => {
|
|
926
|
+
const pair = { ...colors, terminal: cell.terminal, reverse: cell.reverse };
|
|
927
|
+
return { fg: memo.get(`${depth} ${pairKey(pair)} ${step.hex}`, () => legibleInk(step, pair, depth, theme)), bg: colors.bg };
|
|
928
|
+
};
|
|
929
|
+
});
|
|
930
|
+
};
|
|
912
931
|
}
|
|
913
932
|
/**
|
|
914
933
|
* The colour nearest `step` the depth writes that reads on `pair`'s ground
|