@flyingrobots/bijou-tui 2.1.0 → 3.0.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 +64 -21
- package/dist/animate.d.ts +0 -2
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +17 -26
- package/dist/animate.js.map +1 -1
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +2 -2
- package/dist/app-frame-actions.js.map +1 -1
- package/dist/app-frame-render.d.ts +2 -0
- package/dist/app-frame-render.d.ts.map +1 -1
- package/dist/app-frame-render.js +49 -7
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.js +4 -4
- package/dist/app-frame-types.js.map +1 -1
- package/dist/app-frame.d.ts +3 -1
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +61 -13
- package/dist/app-frame.js.map +1 -1
- package/dist/canvas.d.ts +37 -25
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +116 -30
- package/dist/canvas.js.map +1 -1
- package/dist/commands.js +2 -2
- package/dist/commands.js.map +1 -1
- package/dist/css/install.d.ts +4 -0
- package/dist/css/install.d.ts.map +1 -0
- package/dist/css/install.js +24 -0
- package/dist/css/install.js.map +1 -0
- package/dist/css/parser.d.ts +14 -0
- package/dist/css/parser.d.ts.map +1 -0
- package/dist/css/parser.js +92 -0
- package/dist/css/parser.js.map +1 -0
- package/dist/css/resolver.d.ts +36 -0
- package/dist/css/resolver.d.ts.map +1 -0
- package/dist/css/resolver.js +130 -0
- package/dist/css/resolver.js.map +1 -0
- package/dist/css/text-style.d.ts +17 -0
- package/dist/css/text-style.d.ts.map +1 -0
- package/dist/css/text-style.js +59 -0
- package/dist/css/text-style.js.map +1 -0
- package/dist/css/types.d.ts +27 -0
- package/dist/css/types.d.ts.map +1 -0
- package/dist/css/types.js +5 -0
- package/dist/css/types.js.map +1 -0
- package/dist/driver.d.ts +10 -2
- package/dist/driver.d.ts.map +1 -1
- package/dist/driver.js +25 -2
- package/dist/driver.js.map +1 -1
- package/dist/eventbus.d.ts +37 -3
- package/dist/eventbus.d.ts.map +1 -1
- package/dist/eventbus.js +91 -4
- package/dist/eventbus.js.map +1 -1
- package/dist/focus-area.d.ts +4 -0
- package/dist/focus-area.d.ts.map +1 -1
- package/dist/focus-area.js +11 -1
- package/dist/focus-area.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/layout-v3.d.ts +10 -0
- package/dist/layout-v3.d.ts.map +1 -0
- package/dist/layout-v3.js +35 -0
- package/dist/layout-v3.js.map +1 -0
- package/dist/motion/motion.d.ts +14 -0
- package/dist/motion/motion.d.ts.map +1 -0
- package/dist/motion/motion.js +31 -0
- package/dist/motion/motion.js.map +1 -0
- package/dist/motion/reconciler.d.ts +15 -0
- package/dist/motion/reconciler.d.ts.map +1 -0
- package/dist/motion/reconciler.js +109 -0
- package/dist/motion/reconciler.js.map +1 -0
- package/dist/motion/types.d.ts +52 -0
- package/dist/motion/types.d.ts.map +1 -0
- package/dist/motion/types.js +2 -0
- package/dist/motion/types.js.map +1 -0
- package/dist/pipeline/middleware/css.d.ts +20 -0
- package/dist/pipeline/middleware/css.d.ts.map +1 -0
- package/dist/pipeline/middleware/css.js +41 -0
- package/dist/pipeline/middleware/css.js.map +1 -0
- package/dist/pipeline/middleware/grayscale.d.ts +9 -0
- package/dist/pipeline/middleware/grayscale.d.ts.map +1 -0
- package/dist/pipeline/middleware/grayscale.js +39 -0
- package/dist/pipeline/middleware/grayscale.js.map +1 -0
- package/dist/pipeline/middleware/motion.d.ts +6 -0
- package/dist/pipeline/middleware/motion.d.ts.map +1 -0
- package/dist/pipeline/middleware/motion.js +19 -0
- package/dist/pipeline/middleware/motion.js.map +1 -0
- package/dist/pipeline/middleware/paint.d.ts +6 -0
- package/dist/pipeline/middleware/paint.d.ts.map +1 -0
- package/dist/pipeline/middleware/paint.js +21 -0
- package/dist/pipeline/middleware/paint.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +56 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +45 -0
- package/dist/pipeline/pipeline.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +153 -8
- package/dist/runtime.js.map +1 -1
- package/dist/screen.d.ts +12 -1
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +14 -1
- package/dist/screen.js.map +1 -1
- package/dist/subapp/mount.d.ts +67 -0
- package/dist/subapp/mount.d.ts.map +1 -0
- package/dist/subapp/mount.js +60 -0
- package/dist/subapp/mount.js.map +1 -0
- package/dist/types.d.ts +45 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -1
- package/dist/view-output.d.ts +15 -0
- package/dist/view-output.d.ts.map +1 -0
- package/dist/view-output.js +53 -0
- package/dist/view-output.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { LayoutRect } from '@flyingrobots/bijou';
|
|
2
|
+
import type { SpringConfig, SpringPreset } from '../spring.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the motion() declarative wrapper.
|
|
5
|
+
*/
|
|
6
|
+
export interface MotionOptions {
|
|
7
|
+
/**
|
|
8
|
+
* A unique key to track this component across frames.
|
|
9
|
+
* Essential for stable transitions during list re-ordering.
|
|
10
|
+
*/
|
|
11
|
+
key: string;
|
|
12
|
+
/**
|
|
13
|
+
* Transition configuration.
|
|
14
|
+
* Defaults to a 'gentle' spring.
|
|
15
|
+
*/
|
|
16
|
+
transition?: {
|
|
17
|
+
type?: 'spring' | 'tween';
|
|
18
|
+
spring?: SpringPreset | SpringConfig;
|
|
19
|
+
duration?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Initial layout offsets or size overrides when the component first appears.
|
|
23
|
+
* e.g. { x: -10, y: 2, width: 0 }
|
|
24
|
+
*/
|
|
25
|
+
initial?: Partial<LayoutRect>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Internal state for a tracked motion component.
|
|
29
|
+
*/
|
|
30
|
+
export interface TrackedMotion {
|
|
31
|
+
key: string;
|
|
32
|
+
/** The target rect we are moving towards. */
|
|
33
|
+
targetRect: LayoutRect;
|
|
34
|
+
/** The current interpolated rect being rendered. */
|
|
35
|
+
currentRect: LayoutRect;
|
|
36
|
+
/** Velocity for spring physics. */
|
|
37
|
+
velocity: {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
w: number;
|
|
41
|
+
h: number;
|
|
42
|
+
};
|
|
43
|
+
/** Resolved motion mode for the current transition. */
|
|
44
|
+
mode: 'spring' | 'tween';
|
|
45
|
+
/** Elapsed time for tween transitions. */
|
|
46
|
+
tweenElapsedMs: number;
|
|
47
|
+
/** Starting rect for the active tween. */
|
|
48
|
+
tweenFromRect: LayoutRect;
|
|
49
|
+
/** Whether the motion has settled. */
|
|
50
|
+
done: boolean;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/motion/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;QAC1B,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,UAAU,EAAE,UAAU,CAAC;IACvB,oDAAoD;IACpD,WAAW,EAAE,UAAU,CAAC;IACxB,mCAAmC;IACnC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,uDAAuD;IACvD,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,aAAa,EAAE,UAAU,CAAC;IAC1B,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;CACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/motion/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RenderMiddleware } from '../pipeline.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a BCSS styling middleware for the rendering pipeline.
|
|
4
|
+
*
|
|
5
|
+
* This middleware resolves CSS rules against component identities
|
|
6
|
+
* and applies them to the render state.
|
|
7
|
+
*
|
|
8
|
+
* @param css - The BCSS stylesheet string.
|
|
9
|
+
* @returns A RenderMiddleware.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bcssMiddleware(css: string): RenderMiddleware;
|
|
12
|
+
/**
|
|
13
|
+
* Helper for components to resolve their own BCSS styles.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useBCSS(state: any, identity: {
|
|
16
|
+
type: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
classes?: string[];
|
|
19
|
+
}): import("../../css/resolver.js").ResolvedStyles;
|
|
20
|
+
//# sourceMappingURL=css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../../src/pipeline/middleware/css.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKvD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAoB5D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,kDAU9F"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseBCSS } from '../../css/parser.js';
|
|
2
|
+
import { resolveStyles } from '../../css/resolver.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a BCSS styling middleware for the rendering pipeline.
|
|
5
|
+
*
|
|
6
|
+
* This middleware resolves CSS rules against component identities
|
|
7
|
+
* and applies them to the render state.
|
|
8
|
+
*
|
|
9
|
+
* @param css - The BCSS stylesheet string.
|
|
10
|
+
* @returns A RenderMiddleware.
|
|
11
|
+
*/
|
|
12
|
+
export function bcssMiddleware(css) {
|
|
13
|
+
let cachedSheet = null;
|
|
14
|
+
return (state, next) => {
|
|
15
|
+
if (!cachedSheet) {
|
|
16
|
+
cachedSheet = parseBCSS(css);
|
|
17
|
+
}
|
|
18
|
+
// Pass the sheet through the data bag so stages can use it
|
|
19
|
+
state.data['bcss_sheet'] = cachedSheet;
|
|
20
|
+
// The actual application of styles happens within the components
|
|
21
|
+
// by calling a helper that uses this sheet, OR we can do a global
|
|
22
|
+
// post-layout pass here.
|
|
23
|
+
// For now, we'll just ensure the sheet is available.
|
|
24
|
+
// Future: implement global layout override pass here.
|
|
25
|
+
next();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper for components to resolve their own BCSS styles.
|
|
30
|
+
*/
|
|
31
|
+
export function useBCSS(state, identity) {
|
|
32
|
+
const sheet = state.data['bcss_sheet'];
|
|
33
|
+
if (!sheet)
|
|
34
|
+
return {};
|
|
35
|
+
const terminal = {
|
|
36
|
+
width: state.ctx.runtime.columns,
|
|
37
|
+
height: state.ctx.runtime.rows
|
|
38
|
+
};
|
|
39
|
+
return resolveStyles(identity, sheet, terminal);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../../src/pipeline/middleware/css.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,WAAW,GAAqB,IAAI,CAAC;IAEzC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,2DAA2D;QAC3D,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,WAAW,CAAC;QAEvC,kEAAkE;QAClE,mEAAmE;QACnE,yBAAyB;QAEzB,qDAAqD;QACrD,sDAAsD;QAEtD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAU,EAAE,QAA2D;IAC7F,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAA0B,CAAC;IAChE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,MAAM,QAAQ,GAAG;QACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO;QAChC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI;KAC/B,CAAC;IAEF,OAAO,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderMiddleware } from '../pipeline.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a post-processing middleware that converts all colors in the
|
|
4
|
+
* target surface to grayscale luminance values.
|
|
5
|
+
*
|
|
6
|
+
* @returns A RenderMiddleware for the 'PostProcess' stage.
|
|
7
|
+
*/
|
|
8
|
+
export declare function grayscaleFilter(): RenderMiddleware;
|
|
9
|
+
//# sourceMappingURL=grayscale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grayscale.d.ts","sourceRoot":"","sources":["../../../src/pipeline/middleware/grayscale.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAiBlD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a post-processing middleware that converts all colors in the
|
|
3
|
+
* target surface to grayscale luminance values.
|
|
4
|
+
*
|
|
5
|
+
* @returns A RenderMiddleware for the 'PostProcess' stage.
|
|
6
|
+
*/
|
|
7
|
+
export function grayscaleFilter() {
|
|
8
|
+
return (state, next) => {
|
|
9
|
+
const { targetSurface } = state;
|
|
10
|
+
for (let i = 0; i < targetSurface.cells.length; i++) {
|
|
11
|
+
const cell = targetSurface.cells[i];
|
|
12
|
+
if (cell.fg) {
|
|
13
|
+
cell.fg = hexToGrayscale(cell.fg);
|
|
14
|
+
}
|
|
15
|
+
if (cell.bg) {
|
|
16
|
+
cell.bg = hexToGrayscale(cell.bg);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
next();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Convert a #RRGGBB hex string to a grayscale hex string based on luminance. */
|
|
23
|
+
function hexToGrayscale(hex) {
|
|
24
|
+
if (hex.length !== 7 || hex[0] !== '#')
|
|
25
|
+
return hex;
|
|
26
|
+
const digits = hex.slice(1);
|
|
27
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(digits))
|
|
28
|
+
return hex;
|
|
29
|
+
const r = parseInt(digits.slice(0, 2), 16);
|
|
30
|
+
const g = parseInt(digits.slice(2, 4), 16);
|
|
31
|
+
const b = parseInt(digits.slice(4, 6), 16);
|
|
32
|
+
if (isNaN(r) || isNaN(g) || isNaN(b))
|
|
33
|
+
return hex;
|
|
34
|
+
// Standard luminance formula
|
|
35
|
+
const lum = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
36
|
+
const hexLum = lum.toString(16).padStart(2, '0');
|
|
37
|
+
return `#${hexLum}${hexLum}${hexLum}`;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=grayscale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grayscale.js","sourceRoot":"","sources":["../../../src/pipeline/middleware/grayscale.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;QAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;YAErC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBACZ,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpC,CAAC;YACD,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBACZ,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,GAAG,CAAC;IAEnD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,GAAG,CAAC;IAEjD,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE3C,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IAEjD,6BAA6B;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../../../src/pipeline/middleware/motion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,CAenD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MotionReconciler } from '../../motion/reconciler.js';
|
|
2
|
+
const reconciler = new MotionReconciler();
|
|
3
|
+
/**
|
|
4
|
+
* Pipeline middleware that performs layout interpolation for motion components.
|
|
5
|
+
*/
|
|
6
|
+
export function motionMiddleware() {
|
|
7
|
+
return (state, next) => {
|
|
8
|
+
const { dt } = state;
|
|
9
|
+
// We assume the Layout pass has already populated targetSurface
|
|
10
|
+
// with a LayoutNode tree if it's not a raw Surface.
|
|
11
|
+
// In our simplified V3, App.view might return a LayoutNode.
|
|
12
|
+
const root = state.layoutRoot;
|
|
13
|
+
if (root) {
|
|
14
|
+
reconciler.reconcile(root, dt);
|
|
15
|
+
}
|
|
16
|
+
next();
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=motion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.js","sourceRoot":"","sources":["../../../src/pipeline/middleware/motion.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG9D,MAAM,UAAU,GAAG,IAAI,gBAAgB,EAAE,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;QAErB,iEAAiE;QACjE,oDAAoD;QACpD,4DAA4D;QAE5D,MAAM,IAAI,GAAI,KAAa,CAAC,UAAoC,CAAC;QACjE,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paint.d.ts","sourceRoot":"","sources":["../../../src/pipeline/middleware/paint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGvD;;GAEG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAQlD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline middleware that paints a LayoutNode tree onto the target surface.
|
|
3
|
+
*/
|
|
4
|
+
export function paintMiddleware() {
|
|
5
|
+
return (state, next) => {
|
|
6
|
+
const root = state.layoutRoot;
|
|
7
|
+
if (root) {
|
|
8
|
+
paintNode(state.targetSurface, root);
|
|
9
|
+
}
|
|
10
|
+
next();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function paintNode(target, node) {
|
|
14
|
+
if (node.surface) {
|
|
15
|
+
target.blit(node.surface, node.rect.x, node.rect.y);
|
|
16
|
+
}
|
|
17
|
+
for (const child of node.children) {
|
|
18
|
+
paintNode(target, child);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=paint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paint.js","sourceRoot":"","sources":["../../../src/pipeline/middleware/paint.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,IAAI,GAAI,KAAa,CAAC,UAAoC,CAAC;QACjE,IAAI,IAAI,EAAE,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,MAAe,EAAE,IAAgB;IAClD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Surface, BijouContext } from '@flyingrobots/bijou';
|
|
2
|
+
/**
|
|
3
|
+
* The state passed through the rendering pipeline.
|
|
4
|
+
*/
|
|
5
|
+
export interface RenderState {
|
|
6
|
+
/** The model being rendered. */
|
|
7
|
+
model: any;
|
|
8
|
+
/** The Bijou context (ports, theme, mode). */
|
|
9
|
+
ctx: BijouContext;
|
|
10
|
+
/** Time delta in seconds since the last frame. */
|
|
11
|
+
dt: number;
|
|
12
|
+
/**
|
|
13
|
+
* The surface currently visible on the terminal.
|
|
14
|
+
* This should NOT be modified by pipeline stages.
|
|
15
|
+
*/
|
|
16
|
+
readonly currentSurface: Surface;
|
|
17
|
+
/**
|
|
18
|
+
* The target surface being painted.
|
|
19
|
+
* Middleware in the `Paint` and `PostProcess` stages modify this surface.
|
|
20
|
+
*/
|
|
21
|
+
targetSurface: Surface;
|
|
22
|
+
/**
|
|
23
|
+
* Layout geometry calculated during the `Layout` stage.
|
|
24
|
+
* Keyed by component ID.
|
|
25
|
+
*/
|
|
26
|
+
layoutMap: Map<string, any>;
|
|
27
|
+
/**
|
|
28
|
+
* Custom data bag for middleware to pass state between stages.
|
|
29
|
+
*/
|
|
30
|
+
data: Record<string, any>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A middleware function in the rendering pipeline.
|
|
34
|
+
*
|
|
35
|
+
* @param state - The current render state.
|
|
36
|
+
* @param next - Function to yield control to the next middleware.
|
|
37
|
+
*/
|
|
38
|
+
export type RenderMiddleware = (state: RenderState, next: () => void) => void | Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* A stage in the rendering pipeline.
|
|
41
|
+
*/
|
|
42
|
+
export type RenderStage = 'Layout' | 'Paint' | 'PostProcess' | 'Diff' | 'Output';
|
|
43
|
+
/**
|
|
44
|
+
* The rendering pipeline registry.
|
|
45
|
+
*/
|
|
46
|
+
export interface RenderPipeline {
|
|
47
|
+
/** Register middleware for a specific stage. */
|
|
48
|
+
use(stage: RenderStage, middleware: RenderMiddleware): void;
|
|
49
|
+
/** Execute the entire pipeline for a given state. */
|
|
50
|
+
execute(state: RenderState): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a new programmable rendering pipeline.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPipeline(): RenderPipeline;
|
|
56
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,KAAK,EAAE,GAAG,CAAC;IACX,8CAA8C;IAC9C,GAAG,EAAE,YAAY,CAAC;IAClB,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9F;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5D,qDAAqD;IACrD,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,cAAc,CA8C/C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new programmable rendering pipeline.
|
|
3
|
+
*/
|
|
4
|
+
export function createPipeline() {
|
|
5
|
+
const stages = {
|
|
6
|
+
Layout: [],
|
|
7
|
+
Paint: [],
|
|
8
|
+
PostProcess: [],
|
|
9
|
+
Diff: [],
|
|
10
|
+
Output: [],
|
|
11
|
+
};
|
|
12
|
+
const STAGE_ORDER = ['Layout', 'Paint', 'PostProcess', 'Diff', 'Output'];
|
|
13
|
+
function use(stage, middleware) {
|
|
14
|
+
stages[stage].push(middleware);
|
|
15
|
+
}
|
|
16
|
+
function execute(state) {
|
|
17
|
+
// Flatten all middleware into a single execution chain based on stage order
|
|
18
|
+
const chain = [];
|
|
19
|
+
for (const stage of STAGE_ORDER) {
|
|
20
|
+
chain.push(...stages[stage]);
|
|
21
|
+
}
|
|
22
|
+
let index = 0;
|
|
23
|
+
const next = () => {
|
|
24
|
+
if (index < chain.length) {
|
|
25
|
+
const mw = chain[index++];
|
|
26
|
+
try {
|
|
27
|
+
// Note: The pipeline is currently synchronous. If a middleware returns a Promise,
|
|
28
|
+
// it won't block the TEA update loop, but it might resolve out-of-order.
|
|
29
|
+
// For now, we expect render middleware to be entirely synchronous.
|
|
30
|
+
void mw(state, next);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
// Log and continue if a specific middleware fails, preventing full crash
|
|
34
|
+
if (state.ctx.io.writeError) {
|
|
35
|
+
state.ctx.io.writeError(`[Pipeline Error] ${err instanceof Error ? err.stack : err}\n`);
|
|
36
|
+
}
|
|
37
|
+
next();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
next();
|
|
42
|
+
}
|
|
43
|
+
return { use, execute };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AAwDA;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAA4C;QACtD,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,MAAM,WAAW,GAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAExF,SAAS,GAAG,CAAC,KAAkB,EAAE,UAA4B;QAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAED,SAAS,OAAO,CAAC,KAAkB;QACjC,4EAA4E;QAC5E,MAAM,KAAK,GAAuB,EAAE,CAAC;QACrC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBACzB,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC1B,IAAI,CAAC;oBACH,kFAAkF;oBAClF,yEAAyE;oBACzE,mEAAmE;oBACnE,KAAK,EAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBACxB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,yEAAyE;oBACzE,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC;wBAC5B,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,oBAAoB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;oBAC1F,CAAC;oBACD,IAAI,EAAE,CAAC;gBACT,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;IACT,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAC1B,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -15,5 +15,5 @@ import type { App, RunOptions } from './types.js';
|
|
|
15
15
|
* @param options - Optional runtime configuration (context, alt screen, cursor, mouse).
|
|
16
16
|
* @returns A promise that resolves when the application exits.
|
|
17
17
|
*/
|
|
18
|
-
export declare function run<Model, M>(app: App<Model, M>, options?: RunOptions): Promise<void>;
|
|
18
|
+
export declare function run<Model, M>(app: App<Model, M>, options?: RunOptions<M>): Promise<void>;
|
|
19
19
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAO,UAAU,EAAa,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAO,UAAU,EAAa,MAAM,YAAY,CAAC;AAwBlE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,GAAG,CAAC,KAAK,EAAE,CAAC,EAChC,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,EAClB,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,IAAI,CAAC,CAgQf"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { getDefaultContext } from '@flyingrobots/bijou';
|
|
2
|
-
import { isKeyMsg } from './types.js';
|
|
3
|
-
import { enterScreen, exitScreen,
|
|
1
|
+
import { getDefaultContext, createSurface, surfaceToString } from '@flyingrobots/bijou';
|
|
2
|
+
import { isKeyMsg, isPulseMsg, isResizeMsg } from './types.js';
|
|
3
|
+
import { clearAndHome, enterScreen, exitScreen, renderSurfaceFrame } from './screen.js';
|
|
4
4
|
import { createEventBus } from './eventbus.js';
|
|
5
|
+
import { createPipeline } from './pipeline/pipeline.js';
|
|
6
|
+
import { bcssMiddleware } from './pipeline/middleware/css.js';
|
|
7
|
+
import { motionMiddleware } from './pipeline/middleware/motion.js';
|
|
8
|
+
import { paintMiddleware } from './pipeline/middleware/paint.js';
|
|
9
|
+
import { installBCSSResolver } from './css/install.js';
|
|
10
|
+
import { normalizeViewOutput, wrapViewOutputAsLayoutRoot } from './view-output.js';
|
|
5
11
|
/**
|
|
6
12
|
* Disable mouse reporting sequences that terminals may send.
|
|
7
13
|
* Some terminals auto-enable mouse tracking in alt screen mode.
|
|
8
14
|
*/
|
|
9
|
-
const DISABLE_MOUSE = '\x1b[?1000l\x1b[?1002l\x1b[?
|
|
15
|
+
const DISABLE_MOUSE = '\x1b[?1000l\x1b[?1002l\x1b[?1006l';
|
|
10
16
|
/**
|
|
11
17
|
* Enable SGR mouse reporting.
|
|
12
18
|
* 1000 = basic press/release, 1002 = button-event tracking (drag),
|
|
@@ -31,13 +37,27 @@ const ENABLE_MOUSE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h';
|
|
|
31
37
|
*/
|
|
32
38
|
export async function run(app, options) {
|
|
33
39
|
const ctx = options?.ctx ?? getDefaultContext();
|
|
40
|
+
installRuntimeOverlay(ctx);
|
|
34
41
|
const useAltScreen = options?.altScreen ?? true;
|
|
35
42
|
const useHideCursor = options?.hideCursor ?? true;
|
|
36
43
|
const useMouse = options?.mouse ?? false;
|
|
44
|
+
installBCSSResolver(ctx, options?.css);
|
|
37
45
|
const [initModel, initCmds] = app.init();
|
|
38
46
|
// Non-interactive: render once and return
|
|
39
47
|
if (ctx.mode !== 'interactive') {
|
|
40
|
-
|
|
48
|
+
const viewOutput = app.view(initModel);
|
|
49
|
+
let output;
|
|
50
|
+
if (typeof viewOutput === 'string') {
|
|
51
|
+
output = viewOutput;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const normalized = normalizeViewOutput(viewOutput, {
|
|
55
|
+
width: sanitizeDimension(ctx.runtime.columns),
|
|
56
|
+
height: sanitizeDimension(ctx.runtime.rows),
|
|
57
|
+
});
|
|
58
|
+
output = surfaceToString(normalized.surface, ctx.style);
|
|
59
|
+
}
|
|
60
|
+
ctx.io.write(output);
|
|
41
61
|
return;
|
|
42
62
|
}
|
|
43
63
|
// Interactive mode
|
|
@@ -45,6 +65,10 @@ export async function run(app, options) {
|
|
|
45
65
|
let running = true;
|
|
46
66
|
let lastCtrlC = 0;
|
|
47
67
|
let resolveQuit = null;
|
|
68
|
+
let currentDt = 0.016; // Default to 60fps for first frame
|
|
69
|
+
let fatalError = null;
|
|
70
|
+
// Double Buffering: track what is currently on screen
|
|
71
|
+
let currentSurface = createSurface(sanitizeDimension(ctx.runtime.columns), sanitizeDimension(ctx.runtime.rows));
|
|
48
72
|
const bus = createEventBus({
|
|
49
73
|
onCommandRejected(error) {
|
|
50
74
|
const message = error instanceof Error
|
|
@@ -53,8 +77,46 @@ export async function run(app, options) {
|
|
|
53
77
|
writeErrorLine(ctx.io, `[EventBus] Command rejected: ${message}\n`);
|
|
54
78
|
},
|
|
55
79
|
});
|
|
80
|
+
// Setup Programmable Rendering Pipeline
|
|
81
|
+
const pipeline = createPipeline();
|
|
82
|
+
// 1. Layout Logic Stage
|
|
83
|
+
pipeline.use('Layout', (state, next) => {
|
|
84
|
+
const viewOutput = app.view(state.model);
|
|
85
|
+
state.layoutRoot = wrapViewOutputAsLayoutRoot(viewOutput, {
|
|
86
|
+
width: sanitizeDimension(ctx.runtime.columns),
|
|
87
|
+
height: sanitizeDimension(ctx.runtime.rows),
|
|
88
|
+
});
|
|
89
|
+
next();
|
|
90
|
+
});
|
|
91
|
+
// 2. Motion Interpolation Stage
|
|
92
|
+
pipeline.use('Layout', motionMiddleware());
|
|
93
|
+
if (options?.css) {
|
|
94
|
+
pipeline.use('Layout', bcssMiddleware(options.css));
|
|
95
|
+
}
|
|
96
|
+
// 3. Paint Stage
|
|
97
|
+
pipeline.use('Paint', paintMiddleware());
|
|
98
|
+
// Add default Diff stage (double-buffering)
|
|
99
|
+
pipeline.use('Diff', (state, next) => {
|
|
100
|
+
renderSurfaceFrame(state.ctx.io, state.currentSurface, state.targetSurface, state.ctx.style);
|
|
101
|
+
next();
|
|
102
|
+
});
|
|
103
|
+
// Add default Output stage (sync current surface)
|
|
104
|
+
pipeline.use('Output', (state, next) => {
|
|
105
|
+
currentSurface = state.targetSurface.clone();
|
|
106
|
+
next();
|
|
107
|
+
});
|
|
108
|
+
options?.configurePipeline?.(pipeline);
|
|
109
|
+
// Register user middleware
|
|
110
|
+
if (options?.middlewares) {
|
|
111
|
+
for (const mw of options.middlewares) {
|
|
112
|
+
bus.use(mw);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
56
115
|
/** Tear down the run loop and signal the quit promise. */
|
|
57
|
-
function shutdown() {
|
|
116
|
+
function shutdown(error) {
|
|
117
|
+
if (error !== undefined && fatalError === null) {
|
|
118
|
+
fatalError = error;
|
|
119
|
+
}
|
|
58
120
|
if (!running)
|
|
59
121
|
return;
|
|
60
122
|
running = false;
|
|
@@ -72,11 +134,34 @@ export async function run(app, options) {
|
|
|
72
134
|
ctx.io.write(DISABLE_MOUSE);
|
|
73
135
|
}
|
|
74
136
|
// Render helper
|
|
137
|
+
let renderRequested = false;
|
|
75
138
|
/** Render the current model's view to the terminal. */
|
|
76
139
|
function render() {
|
|
77
|
-
if (!running)
|
|
140
|
+
if (!running || renderRequested)
|
|
78
141
|
return;
|
|
79
|
-
|
|
142
|
+
renderRequested = true;
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
try {
|
|
145
|
+
const targetSurface = createSurface(sanitizeDimension(ctx.runtime.columns), sanitizeDimension(ctx.runtime.rows));
|
|
146
|
+
const renderState = {
|
|
147
|
+
model,
|
|
148
|
+
ctx,
|
|
149
|
+
dt: currentDt,
|
|
150
|
+
currentSurface,
|
|
151
|
+
targetSurface,
|
|
152
|
+
layoutMap: new Map(),
|
|
153
|
+
data: {},
|
|
154
|
+
};
|
|
155
|
+
pipeline.execute(renderState);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
writeErrorLine(ctx.io, `[Runtime Error] ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
159
|
+
shutdown(error);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
renderRequested = false;
|
|
163
|
+
}
|
|
164
|
+
}, 0);
|
|
80
165
|
}
|
|
81
166
|
// Execute commands through the bus
|
|
82
167
|
/** Submit TEA commands to the event bus for async execution. */
|
|
@@ -89,10 +174,22 @@ export async function run(app, options) {
|
|
|
89
174
|
bus.connectIO(ctx.io, { mouse: useMouse });
|
|
90
175
|
// Handle quit signals from commands
|
|
91
176
|
bus.onQuit(shutdown);
|
|
177
|
+
// Start heartbeat for animations
|
|
178
|
+
bus.startPulse(ctx.runtime.refreshRate);
|
|
92
179
|
// Single subscription drives the entire update cycle
|
|
93
180
|
bus.on((msg) => {
|
|
94
181
|
if (!running)
|
|
95
182
|
return;
|
|
183
|
+
// Track time delta from pulse
|
|
184
|
+
if (isPulseMsg(msg)) {
|
|
185
|
+
currentDt = msg.dt;
|
|
186
|
+
}
|
|
187
|
+
if (isResizeMsg(msg)) {
|
|
188
|
+
ctx.runtime.columns = sanitizeDimension(msg.columns);
|
|
189
|
+
ctx.runtime.rows = sanitizeDimension(msg.rows);
|
|
190
|
+
currentSurface = createSurface(ctx.runtime.columns, ctx.runtime.rows);
|
|
191
|
+
clearAndHome(ctx.io);
|
|
192
|
+
}
|
|
96
193
|
// Double Ctrl+C force-quit
|
|
97
194
|
if (isKeyMsg(msg) && msg.key === 'c' && msg.ctrl) {
|
|
98
195
|
const now = Date.now();
|
|
@@ -116,6 +213,9 @@ export async function run(app, options) {
|
|
|
116
213
|
};
|
|
117
214
|
const [resizedModel, resizeCmds] = app.update(initialResize, model);
|
|
118
215
|
model = resizedModel;
|
|
216
|
+
// After a potential resize in update, ensure currentSurface matches size
|
|
217
|
+
// to avoid diffing mismatched grids.
|
|
218
|
+
currentSurface = createSurface(sanitizeDimension(ctx.runtime.columns), sanitizeDimension(ctx.runtime.rows));
|
|
119
219
|
// Initial render + startup commands
|
|
120
220
|
render();
|
|
121
221
|
executeCommands(initCmds);
|
|
@@ -126,7 +226,14 @@ export async function run(app, options) {
|
|
|
126
226
|
if (!running)
|
|
127
227
|
resolve();
|
|
128
228
|
});
|
|
229
|
+
// Ensure any pending render is flushed before exiting
|
|
230
|
+
if (renderRequested) {
|
|
231
|
+
await new Promise((resolve) => {
|
|
232
|
+
setTimeout(resolve, 0);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
129
235
|
// Cleanup — bus disposes all I/O connections
|
|
236
|
+
bus.stopPulse();
|
|
130
237
|
bus.dispose();
|
|
131
238
|
if (useMouse) {
|
|
132
239
|
ctx.io.write(DISABLE_MOUSE);
|
|
@@ -134,6 +241,9 @@ export async function run(app, options) {
|
|
|
134
241
|
if (useAltScreen || useHideCursor) {
|
|
135
242
|
exitScreen(ctx.io);
|
|
136
243
|
}
|
|
244
|
+
if (fatalError != null) {
|
|
245
|
+
throw fatalError instanceof Error ? fatalError : new Error(String(fatalError));
|
|
246
|
+
}
|
|
137
247
|
}
|
|
138
248
|
/** Clamp a terminal dimension to a non-negative integer. */
|
|
139
249
|
function sanitizeDimension(value) {
|
|
@@ -141,6 +251,41 @@ function sanitizeDimension(value) {
|
|
|
141
251
|
return 0;
|
|
142
252
|
return Math.max(0, Math.floor(value));
|
|
143
253
|
}
|
|
254
|
+
function installRuntimeOverlay(ctx) {
|
|
255
|
+
const baseRuntime = ctx.runtime;
|
|
256
|
+
const state = {
|
|
257
|
+
columns: sanitizeDimension(baseRuntime.columns),
|
|
258
|
+
rows: sanitizeDimension(baseRuntime.rows),
|
|
259
|
+
};
|
|
260
|
+
const runtime = {
|
|
261
|
+
env(key) {
|
|
262
|
+
return baseRuntime.env(key);
|
|
263
|
+
},
|
|
264
|
+
get stdoutIsTTY() {
|
|
265
|
+
return baseRuntime.stdoutIsTTY;
|
|
266
|
+
},
|
|
267
|
+
get stdinIsTTY() {
|
|
268
|
+
return baseRuntime.stdinIsTTY;
|
|
269
|
+
},
|
|
270
|
+
get columns() {
|
|
271
|
+
return state.columns;
|
|
272
|
+
},
|
|
273
|
+
set columns(value) {
|
|
274
|
+
state.columns = sanitizeDimension(value);
|
|
275
|
+
},
|
|
276
|
+
get rows() {
|
|
277
|
+
return state.rows;
|
|
278
|
+
},
|
|
279
|
+
set rows(value) {
|
|
280
|
+
state.rows = sanitizeDimension(value);
|
|
281
|
+
},
|
|
282
|
+
get refreshRate() {
|
|
283
|
+
return baseRuntime.refreshRate;
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
ctx.runtime = runtime;
|
|
287
|
+
return runtime;
|
|
288
|
+
}
|
|
144
289
|
/** Write an error message to stderr if available, otherwise stdout. */
|
|
145
290
|
function writeErrorLine(io, data) {
|
|
146
291
|
if (io.writeError != null) {
|