@promptctl/rich-js 0.23.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/clock.d.ts +9 -1
- package/dist/core/clock.js +22 -2
- package/dist/core/color.d.ts +10 -0
- package/dist/core/color.js +36 -1
- package/dist/core/console.js +5 -5
- package/dist/core/easing.d.ts +138 -7
- package/dist/core/easing.js +355 -17
- 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 +28 -1
- package/dist/core/segment.js +60 -44
- package/dist/core/strip.d.ts +107 -29
- package/dist/core/strip.js +242 -131
- package/dist/core/style.d.ts +18 -1
- package/dist/core/style.js +49 -9
- package/dist/core/text.js +26 -15
- 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 +4 -6
- package/dist/host/index.d.ts +1 -1
- package/dist/host/input.d.ts +37 -13
- package/dist/host/input.js +146 -56
- package/dist/host/terminal-host.d.ts +16 -0
- package/dist/host/terminal-host.js +1 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.js +15 -5
- package/dist/node/terminal-host.d.ts +8 -1
- package/dist/node/terminal-host.js +2 -0
- 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 +83 -30
- package/dist/renderables/effect.js +155 -57
- package/dist/renderables/effects.d.ts +88 -39
- package/dist/renderables/effects.js +444 -105
- package/dist/renderables/embed.d.ts +10 -0
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.d.ts +3 -2
- package/dist/renderables/flexStrip.js +6 -1
- package/dist/renderables/frames.d.ts +116 -0
- package/dist/renderables/frames.js +133 -0
- 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.d.ts +23 -7
- package/dist/renderables/live.js +69 -35
- 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 +7 -6
- package/dist/renderables/markdown.js +363 -499
- 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 +5 -3
- package/dist/renderables/progress.js +14 -3
- package/dist/renderables/progressBar.js +4 -4
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +24 -0
- package/dist/renderables/spinner.d.ts +73 -27
- package/dist/renderables/spinner.js +110 -51
- package/dist/renderables/status.d.ts +8 -3
- package/dist/renderables/status.js +8 -5
- 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.d.ts +37 -0
- package/dist/template-bindings/animation-funcs.js +83 -0
- package/dist/template-bindings/color-funcs.d.ts +13 -0
- package/dist/template-bindings/color-funcs.js +1 -1
- package/dist/template-bindings/helpers.d.ts +6 -0
- package/dist/template-bindings/helpers.js +12 -3
- package/dist/template-bindings/index.d.ts +6 -2
- package/dist/template-bindings/index.js +8 -3
- 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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Measurement } from "../core/measure.js";
|
|
12
12
|
import { normalizePadding } from "./padding.js";
|
|
13
|
-
import { withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
13
|
+
import { render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
14
14
|
import { cellCount } from "../core/cells.js";
|
|
15
15
|
import { EmbeddedText, embed } from "./embed.js";
|
|
16
16
|
import { Constrain } from "./constrain.js";
|
|
@@ -68,7 +68,12 @@ export class Columns {
|
|
|
68
68
|
// single column is laid out. An unbounded width reaches the same place by
|
|
69
69
|
// the same route, which is why the parse here is the bounded one.
|
|
70
70
|
const options = withBoundedWidth(rawOptions, this);
|
|
71
|
-
yield* this._grid(options)
|
|
71
|
+
yield* render(this._grid(options), options);
|
|
72
|
+
}
|
|
73
|
+
// The items, in the order they were given. Laid out column-first, the grid
|
|
74
|
+
// draws them in an order its width decides, and handed no column it has none.
|
|
75
|
+
contents() {
|
|
76
|
+
return this.renderables;
|
|
72
77
|
}
|
|
73
78
|
measure(rawOptions) {
|
|
74
79
|
// The maximum is the grid's, which reports its width before `expand`: a
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Constrain — wraps a renderable and constrains its maximum width.
|
|
3
3
|
*/
|
|
4
4
|
import { Segment } from "../core/segment.js";
|
|
5
|
-
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
6
|
-
export declare class Constrain implements Renderable, Measurable {
|
|
5
|
+
import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
6
|
+
export declare class Constrain implements Renderable, Measurable, Composite {
|
|
7
7
|
readonly renderable: Renderable;
|
|
8
8
|
readonly width: number | undefined;
|
|
9
9
|
constructor(renderable: Renderable, width?: number);
|
|
10
10
|
render(options: RenderOptions): Iterable<Segment>;
|
|
11
|
+
contents(): Iterable<Renderable>;
|
|
11
12
|
measure(options: RenderOptions): {
|
|
12
13
|
minimum: number;
|
|
13
14
|
maximum: number;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Constrain — wraps a renderable and constrains its maximum width.
|
|
3
3
|
*/
|
|
4
4
|
import { Measurement } from "../core/measure.js";
|
|
5
|
+
import { render } from "../core/protocol.js";
|
|
5
6
|
export class Constrain {
|
|
6
7
|
renderable;
|
|
7
8
|
width;
|
|
@@ -18,7 +19,10 @@ export class Constrain {
|
|
|
18
19
|
...options,
|
|
19
20
|
maxWidth: constrainedWidth,
|
|
20
21
|
};
|
|
21
|
-
yield* this.renderable
|
|
22
|
+
yield* render(this.renderable, innerOptions);
|
|
23
|
+
}
|
|
24
|
+
*contents() {
|
|
25
|
+
yield this.renderable;
|
|
22
26
|
}
|
|
23
27
|
measure(options) {
|
|
24
28
|
// Measured at the width it will be drawn at, as Rich's `update_width` does;
|
|
@@ -2,23 +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`)
|
|
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
|
|
18
21
|
* same reading of each segment its cells are drawn from, and handed to the
|
|
19
|
-
* effect
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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.
|
|
22
25
|
*
|
|
23
26
|
* [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
|
|
24
27
|
* clock read. Whoever draws frames builds the view for each frame's `t`, so
|
|
@@ -33,8 +36,10 @@
|
|
|
33
36
|
* segment's link and meta and carries its anchor shifted to the run's first
|
|
34
37
|
* cell (`Style.shiftedBy`), so hit testing still finds the owner.
|
|
35
38
|
* - A cell whose colours come back unchanged keeps its original `ColorSpec`,
|
|
36
|
-
* and a segment whose every cell is unchanged is yielded
|
|
37
|
-
* identity effect is byte-identical
|
|
39
|
+
* and a segment whose every cell is unchanged is yielded with its look as
|
|
40
|
+
* it was, so the identity effect is byte-identical; what it gains is when
|
|
41
|
+
* the frame next changes (`Style.nextChange`), which writes no byte.
|
|
42
|
+
* Neighbouring cells the output depth
|
|
38
43
|
* writes alike stay one segment: a segment is cut only where the written
|
|
39
44
|
* colours change, so two colours that land on one index are one run.
|
|
40
45
|
* - New colours are built with `ColorSpec.fromRgba`, never parsed from a
|
|
@@ -46,8 +51,10 @@
|
|
|
46
51
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
47
52
|
*/
|
|
48
53
|
import { type Anchor } from "../core/anchor.js";
|
|
54
|
+
import { Animation } from "../core/animation.js";
|
|
49
55
|
import { ColorDepth, type ColorRgba, type TerminalTheme } from "../core/color.js";
|
|
50
|
-
import type {
|
|
56
|
+
import type { Beat } from "../core/easing.js";
|
|
57
|
+
import type { Composite, Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
51
58
|
import { Segment } from "../core/segment.js";
|
|
52
59
|
/**
|
|
53
60
|
* A cell's foreground and background as they appear on screen — the glyph's
|
|
@@ -78,6 +85,8 @@ export interface EffectCell {
|
|
|
78
85
|
readonly fg: boolean;
|
|
79
86
|
readonly bg: boolean;
|
|
80
87
|
};
|
|
88
|
+
/** Whether the cell's style swaps its two slots, as `Pair.reverse` says: with `colors` and `terminal`, the cell's `Pair`. */
|
|
89
|
+
readonly reverse: boolean;
|
|
81
90
|
/**
|
|
82
91
|
* Who drew the cell, and where it sits in their output, as
|
|
83
92
|
* `Segment.anchorAt` reads it off a frame: what tells one owner's cells
|
|
@@ -85,6 +94,8 @@ export interface EffectCell {
|
|
|
85
94
|
*/
|
|
86
95
|
readonly anchor: Anchor | undefined;
|
|
87
96
|
}
|
|
97
|
+
/** Where a cell sits and who drew it: an `EffectCell` before it has a seed or colours. */
|
|
98
|
+
export type Placed = Pick<EffectCell, "row" | "col" | "anchor">;
|
|
88
99
|
/**
|
|
89
100
|
* A cell's ink and ground as the screen shows them, which of the two are the
|
|
90
101
|
* terminal's, and whether its style swaps them (`reverse`): what an `Effect`
|
|
@@ -101,13 +112,24 @@ export interface Pair extends CellColors {
|
|
|
101
112
|
readonly reverse: boolean;
|
|
102
113
|
}
|
|
103
114
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* not words.
|
|
115
|
+
* What the element an effect wraps draws in one frame: the cells the effect
|
|
116
|
+
* is handed, and the colours the element draws its words in, as the screen
|
|
117
|
+
* shows them — what an effect that must keep the words legible is measured
|
|
118
|
+
* against. A word cell is one whose glyph is not blank and that a strip's
|
|
119
|
+
* joiner did not draw (`JOIN`): a seam's ink is the fill beside it, not words.
|
|
109
120
|
*/
|
|
110
121
|
export interface Drawn {
|
|
122
|
+
/**
|
|
123
|
+
* Every cell the element shows something in this frame — a glyph, a
|
|
124
|
+
* ground that is not the terminal's, or a line its style draws — where it
|
|
125
|
+
* sits and who drew it, listed before the effect is handed any: what
|
|
126
|
+
* an effect spread over the element — a transition, which has its last cell
|
|
127
|
+
* settle as it ends — is spread over. A blank cell on the terminal's ground
|
|
128
|
+
* that draws no line is handed but not listed, since nothing in it shows.
|
|
129
|
+
* `confined` narrows it to the cells its owners drew. A call, so a frame
|
|
130
|
+
* whose effect does not ask lists none.
|
|
131
|
+
*/
|
|
132
|
+
cells(): readonly Placed[];
|
|
111
133
|
/** Every distinct ink and ground of a word cell. */
|
|
112
134
|
readonly pairs: readonly Pair[];
|
|
113
135
|
/** The colours the element sets itself in its word cells, by hex: never the terminal's, whatever its hex. */
|
|
@@ -119,14 +141,23 @@ export interface Drawn {
|
|
|
119
141
|
/** The terminal the frame is drawn on. */
|
|
120
142
|
readonly theme: TerminalTheme;
|
|
121
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;
|
|
122
146
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
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.
|
|
151
|
+
*
|
|
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.
|
|
128
159
|
*/
|
|
129
|
-
export type Effect = (
|
|
160
|
+
export type Effect = (drawn: Drawn) => Animation<EffectFrame>;
|
|
130
161
|
export interface EffectedOptions {
|
|
131
162
|
/** The moment to draw, in seconds. */
|
|
132
163
|
readonly t: number;
|
|
@@ -142,7 +173,7 @@ export interface EffectedOptions {
|
|
|
142
173
|
*/
|
|
143
174
|
readonly theme: TerminalTheme;
|
|
144
175
|
}
|
|
145
|
-
export declare class Effected implements Renderable, Measurable {
|
|
176
|
+
export declare class Effected implements Renderable, Measurable, Composite {
|
|
146
177
|
readonly renderable: Renderable;
|
|
147
178
|
readonly effect: Effect;
|
|
148
179
|
readonly t: number;
|
|
@@ -150,6 +181,9 @@ export declare class Effected implements Renderable, Measurable {
|
|
|
150
181
|
readonly theme: TerminalTheme;
|
|
151
182
|
constructor(renderable: Renderable, effect: Effect, options: EffectedOptions);
|
|
152
183
|
render(options: RenderOptions): Iterable<Segment>;
|
|
184
|
+
contents(): Iterable<Renderable>;
|
|
185
|
+
private _moved;
|
|
186
|
+
private _recolored;
|
|
153
187
|
measure(options: RenderOptions): {
|
|
154
188
|
minimum: number;
|
|
155
189
|
maximum: number;
|
|
@@ -171,6 +205,25 @@ export declare class Effected implements Renderable, Measurable {
|
|
|
171
205
|
/** A cell's seed: FNV-1a of the key and the position, scaled into [0, 1). */
|
|
172
206
|
private seed;
|
|
173
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* `effect` sampled and held on `beat`: drawn at the moment the hold in
|
|
210
|
+
* progress began (`Beat.lastChange`), so within a hold every frame is the frame
|
|
211
|
+
* at its start, and at each hold's start it moves on. A continuous effect
|
|
212
|
+
* given a short curve becomes one that steps — a `wheel` turning a sixth of
|
|
213
|
+
* a turn once a second — and at one redraw a second a hold shorter than a
|
|
214
|
+
* second is skipped, as frames are. [LAW:no-ambient-temporal-coupling] The
|
|
215
|
+
* held time is a function of `t`, so the same `t` draws the same bytes at
|
|
216
|
+
* any rate. The frame next changes at a hold's start, and only one the
|
|
217
|
+
* sampled effect has moved by: the first hold start at or after what it
|
|
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
|
|
222
|
+
* says, never off a comparison of frames: an effect that steps and is back
|
|
223
|
+
* on the same colour by the next hold still turns the beat there, an early
|
|
224
|
+
* answer the contract allows.
|
|
225
|
+
*/
|
|
226
|
+
export declare function onBeat(effect: Effect, beat: Beat): Effect;
|
|
174
227
|
/**
|
|
175
228
|
* `effect` on the colours any of `owners` drew, itself or through an owner
|
|
176
229
|
* nested in its output, and every other colour left as it is: one item of a
|
|
@@ -2,23 +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`)
|
|
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
|
|
18
21
|
* same reading of each segment its cells are drawn from, and handed to the
|
|
19
|
-
* effect
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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.
|
|
22
25
|
*
|
|
23
26
|
* [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
|
|
24
27
|
* clock read. Whoever draws frames builds the view for each frame's `t`, so
|
|
@@ -33,8 +36,10 @@
|
|
|
33
36
|
* segment's link and meta and carries its anchor shifted to the run's first
|
|
34
37
|
* cell (`Style.shiftedBy`), so hit testing still finds the owner.
|
|
35
38
|
* - A cell whose colours come back unchanged keeps its original `ColorSpec`,
|
|
36
|
-
* and a segment whose every cell is unchanged is yielded
|
|
37
|
-
* identity effect is byte-identical
|
|
39
|
+
* and a segment whose every cell is unchanged is yielded with its look as
|
|
40
|
+
* it was, so the identity effect is byte-identical; what it gains is when
|
|
41
|
+
* the frame next changes (`Style.nextChange`), which writes no byte.
|
|
42
|
+
* Neighbouring cells the output depth
|
|
38
43
|
* writes alike stay one segment: a segment is cut only where the written
|
|
39
44
|
* colours change, so two colours that land on one index are one run.
|
|
40
45
|
* - New colours are built with `ColorSpec.fromRgba`, never parsed from a
|
|
@@ -46,10 +51,12 @@
|
|
|
46
51
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
47
52
|
*/
|
|
48
53
|
import { drawnBy, shiftAnchor } from "../core/anchor.js";
|
|
54
|
+
import { Animation } from "../core/animation.js";
|
|
49
55
|
import { cellLen, graphemes } from "../core/cells.js";
|
|
50
56
|
import { ColorDepth, ColorSpec, SURFACE_BLACK } from "../core/color.js";
|
|
51
57
|
import { fnv1a } from "../core/fnv1a.js";
|
|
52
58
|
import { Measurement } from "../core/measure.js";
|
|
59
|
+
import { render } from "../core/protocol.js";
|
|
53
60
|
import { Segment } from "../core/segment.js";
|
|
54
61
|
import { JOIN, colorsOwnedBy } from "../core/strip.js";
|
|
55
62
|
import { Style } from "../core/style.js";
|
|
@@ -69,21 +76,32 @@ export class Effected {
|
|
|
69
76
|
this.key = options.key;
|
|
70
77
|
this.theme = options.theme;
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
const segments = this.renderable
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
render(options) {
|
|
80
|
+
const segments = render(this.renderable, options);
|
|
81
|
+
// With no colour output nothing the effect does is written, so the child
|
|
82
|
+
// is what is shown, and it changes when the child says it does.
|
|
83
|
+
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) {
|
|
78
91
|
// No depth named is no downgrade, as `drawnColors` reads it.
|
|
79
92
|
const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
|
|
80
93
|
// [LAW:one-source-of-truth] The frame is measured before any cell is
|
|
81
|
-
// handed to the effect, off the same looks its cells are drawn from
|
|
94
|
+
// handed to the effect, off the same looks its cells are drawn from, and
|
|
95
|
+
// the one measure is what the effect's animation is made from.
|
|
82
96
|
const looked = looks(segments, depth, this.theme);
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
const walked = walk(looked);
|
|
98
|
+
const animation = this.effect(measured(looked, () => walked, depth, this.theme));
|
|
99
|
+
// What the effect draws at `t` is drawn until its next change, and the
|
|
100
|
+
// cells say so, under whatever the child already said (the sooner wins).
|
|
101
|
+
yield* Segment.applyStyle(this._recolored(walked, animation.at(this.t), depth), new Style({ nextChange: animation.nextChange(this.t) }));
|
|
102
|
+
}
|
|
103
|
+
*_recolored(walked, frame, depth) {
|
|
104
|
+
for (const { segment, look, glyphs } of walked) {
|
|
87
105
|
if (look === undefined) {
|
|
88
106
|
yield segment;
|
|
89
107
|
continue;
|
|
@@ -96,21 +114,9 @@ export class Effected {
|
|
|
96
114
|
// writes, so two truecolors landing on one index stay one run. A cell
|
|
97
115
|
// the effect gave its neighbour's colours is drawn as that neighbour was.
|
|
98
116
|
const runs = [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let colors = slots;
|
|
103
|
-
if (glyph === "\n") {
|
|
104
|
-
row++;
|
|
105
|
-
col = 0;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const anchor = base.anchor && shiftAnchor(base.anchor, into);
|
|
109
|
-
// Back from the screen to the style's slots: the same swap undoes itself.
|
|
110
|
-
colors = onScreen(frame(from, { row, col, seed: this.seed(row, col), terminal, anchor }), pair.reverse);
|
|
111
|
-
col += cellLen(glyph);
|
|
112
|
-
}
|
|
113
|
-
into += cellLen(glyph);
|
|
117
|
+
for (const { glyph, cell } of glyphs) {
|
|
118
|
+
// Back from the screen to the style's slots: the same swap undoes itself.
|
|
119
|
+
const colors = cell === undefined ? slots : onScreen(frame(from, { ...cell, seed: this.seed(cell.row, cell.col), terminal, reverse: pair.reverse }), pair.reverse);
|
|
114
120
|
const last = runs[runs.length - 1];
|
|
115
121
|
const writes = last !== undefined && sameColors(last.colors, colors) ? last.drawn : this.respec(wire, slots, colors, depth);
|
|
116
122
|
if (last !== undefined && writesAlike(last.drawn, writes)) {
|
|
@@ -178,13 +184,101 @@ function looks(segments, depth, theme) {
|
|
|
178
184
|
return { segment, look: { style, wire, slots, pair: { ...onScreen(slots, reverse), terminal, reverse } } };
|
|
179
185
|
});
|
|
180
186
|
}
|
|
181
|
-
/**
|
|
182
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Each glyph of `looked`, where its cell sits: rows and columns from the
|
|
189
|
+
* top-left of the output, a wide glyph one cell at its first column, and its
|
|
190
|
+
* anchor shifted to that column as `Style.shiftedBy` shifts a run's.
|
|
191
|
+
* [LAW:one-source-of-truth] The one walk: what `Drawn.cells` lists and what
|
|
192
|
+
* the render hands the effect are read off it.
|
|
193
|
+
*/
|
|
194
|
+
function walk(looked) {
|
|
195
|
+
let row = 0;
|
|
196
|
+
let col = 0;
|
|
197
|
+
return looked.map(({ segment, look }) => {
|
|
198
|
+
if (look === undefined)
|
|
199
|
+
return { segment, look, glyphs: [] };
|
|
200
|
+
const anchor = look.style.anchor;
|
|
201
|
+
// Cells into the segment, as `base.shiftedBy` counts them in the render.
|
|
202
|
+
let into = 0;
|
|
203
|
+
const glyphs = [...graphemes(segment.text)].map((glyph) => {
|
|
204
|
+
const cell = glyph === "\n" ? undefined : { row, col, anchor: anchor && shiftAnchor(anchor, into) };
|
|
205
|
+
[row, col] = glyph === "\n" ? [row + 1, 0] : [row, col + cellLen(glyph)];
|
|
206
|
+
into += cellLen(glyph);
|
|
207
|
+
return { glyph, cell };
|
|
208
|
+
});
|
|
209
|
+
return { segment, look, glyphs };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/** Whether `style` draws in its ink on a blank glyph: a line under, through or over it, or a frame round it. */
|
|
213
|
+
function drawsLines(style) {
|
|
214
|
+
return [style.underline, style.underline2, style.strike, style.overline, style.frame, style.encircle].some((on) => on === true);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* What `looked` draws: `Drawn`'s measure, taken once per frame. Its `cells`
|
|
218
|
+
* are listed off `walked` the first time they are asked for, since only an
|
|
219
|
+
* effect spread over the element asks, and a glyph walk is what measuring the
|
|
220
|
+
* words alone, a segment at a time, is spared (`drawnOf`).
|
|
221
|
+
*/
|
|
222
|
+
function measured(looked, walked, depth, theme) {
|
|
223
|
+
const ground = theme.backgroundColor.hex;
|
|
224
|
+
let listed;
|
|
225
|
+
const list = () => walked().flatMap(({ look, glyphs }) => {
|
|
226
|
+
if (look === undefined)
|
|
227
|
+
return [];
|
|
228
|
+
// A blank glyph shows its ground, and any line its style draws in its ink.
|
|
229
|
+
const blankShows = look.pair.bg.hex !== ground || drawsLines(look.style);
|
|
230
|
+
return glyphs.flatMap(({ glyph, cell }) => (cell !== undefined && (glyph.trim() !== "" || blankShows) ? [cell] : []));
|
|
231
|
+
});
|
|
183
232
|
const words = looked.flatMap(({ segment, look }) => (look !== undefined && segment.text.trim() !== "" && !drawnBy(look.style.anchor, JOIN) ? [look.pair] : []));
|
|
184
233
|
const pairs = [...new Map(words.map((pair) => [`${pair.fg.hex}/${pair.bg.hex}/${pair.terminal.fg}/${pair.terminal.bg}/${pair.reverse}`, pair])).values()];
|
|
185
234
|
// [LAW:one-source-of-truth] A slot the terminal fills is no colour of the element's, whatever its hex.
|
|
186
235
|
const own = (slot) => pairs.flatMap((pair) => (pair.terminal[slot] ? [] : [pair[slot].hex]));
|
|
187
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
cells: () => (listed ??= list()),
|
|
238
|
+
pairs,
|
|
239
|
+
colors: new Set([...own("fg"), ...own("bg")]),
|
|
240
|
+
fills: new Set(own("bg")),
|
|
241
|
+
depth,
|
|
242
|
+
theme,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
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
|
+
};
|
|
188
282
|
}
|
|
189
283
|
/**
|
|
190
284
|
* `effect` on the colours any of `owners` drew, itself or through an owner
|
|
@@ -199,16 +293,19 @@ function measured(looked, depth, theme) {
|
|
|
199
293
|
*/
|
|
200
294
|
export function confined(effect, ...owners) {
|
|
201
295
|
const confinedTo = new Set(owners);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
296
|
+
// [LAW:single-enforcer] One reading of which slots are owned, for the cells listed and the cells handed alike.
|
|
297
|
+
const ownedOf = (cell) => colorsOwnedBy(cell.anchor, confinedTo);
|
|
298
|
+
const any = ({ fg, bg }) => fg || bg;
|
|
299
|
+
// The frame as the effect sees it: the owners' cells alone. [LAW:one-source-of-truth] One narrowing, handed to both stages.
|
|
300
|
+
const owned = (drawn) => ({ ...drawn, cells: () => drawn.cells().filter((cell) => any(ownedOf(cell))) });
|
|
301
|
+
// The owners' cells change when the effect's frame does.
|
|
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
|
+
});
|
|
212
309
|
}
|
|
213
310
|
/**
|
|
214
311
|
* What `segments`, drawn at `depth` on `theme`, draw their words in, measured
|
|
@@ -216,7 +313,8 @@ export function confined(effect, ...owners) {
|
|
|
216
313
|
* output under an effect to read the contrast its words are left at.
|
|
217
314
|
*/
|
|
218
315
|
export function drawnOf(segments, depth, theme) {
|
|
219
|
-
|
|
316
|
+
const looked = looks(segments, depth, theme);
|
|
317
|
+
return measured(looked, () => walk(looked), depth, theme);
|
|
220
318
|
}
|
|
221
319
|
/**
|
|
222
320
|
* A style's colour slots as the screen shows them, or the reverse: `reverse`
|