@tuidom/core 0.3.0 → 0.5.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/dist/common/colorUtils.d.ts +49 -0
- package/dist/common/colorUtils.js +101 -0
- package/dist/dom/overlayLayer.d.ts +7 -0
- package/dist/dom/overlayLayer.js +6 -10
- package/dist/dom/styles/styleTokens.d.ts +1 -0
- package/dist/dom/styles/styleTokens.js +5 -1
- package/dist/dom/styles/tuiStyle.d.ts +6 -0
- package/dist/dom/styles/tuiStyle.js +11 -3
- package/dist/dom/tuiElement.d.ts +59 -2
- package/dist/dom/tuiElement.js +128 -19
- package/dist/rendering/grid.d.ts +6 -0
- package/dist/rendering/grid.js +20 -10
- package/package.json +1 -1
|
@@ -3,17 +3,66 @@
|
|
|
3
3
|
* Must be negative so it's never confused with a packed RGB value (0x000000–0xFFFFFF).
|
|
4
4
|
*/
|
|
5
5
|
export declare const DEFAULT_COLOR = -1;
|
|
6
|
+
/**
|
|
7
|
+
* Полностью прозрачный цвет (`#RRGGBB00`, CSS `transparent`): при наложении
|
|
8
|
+
* ничего не меняет, а как собственный bg элемента — не красит. Сентинел, а не
|
|
9
|
+
* упаковка с альфой 0: старший байт 0 занят непрозрачными 24-битными числами.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TRANSPARENT_COLOR = -2;
|
|
6
12
|
/** Pack three 8-bit channels into a single 24-bit integer. */
|
|
7
13
|
export declare function packRgb(r: number, g: number, b: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* Упаковывает цвет с альфой (0..255). Непрозрачный (`a === 255`) даёт обычное
|
|
16
|
+
* 24-битное число — то же, что {@link packRgb}; `a === 0` — {@link TRANSPARENT_COLOR};
|
|
17
|
+
* промежуточная альфа кладётся в старший байт (`0xAARRGGBB`, беззнаковое
|
|
18
|
+
* значение > 0xFFFFFF). Так весь существующий код с 24-битными литералами
|
|
19
|
+
* остаётся валидным без миграции, а полупрозрачность — отличима одним сравнением.
|
|
20
|
+
*/
|
|
21
|
+
export declare function packRgba(r: number, g: number, b: number, a: number): number;
|
|
8
22
|
/** Extract the red channel (bits 16–23). */
|
|
9
23
|
export declare function unpackR(color: number): number;
|
|
10
24
|
/** Extract the green channel (bits 8–15). */
|
|
11
25
|
export declare function unpackG(color: number): number;
|
|
12
26
|
/** Extract the blue channel (bits 0–7). */
|
|
13
27
|
export declare function unpackB(color: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Альфа цвета: 255 у 24-битных чисел и сентинелов (`DEFAULT_COLOR` — цвет
|
|
30
|
+
* терминала, он непрозрачен), 0 у {@link TRANSPARENT_COLOR}, иначе старший байт.
|
|
31
|
+
*/
|
|
32
|
+
export declare function unpackA(color: number): number;
|
|
33
|
+
/** true для полупрозрачного цвета (альфа 1..254) — того, что требует композитинга с подложкой. */
|
|
34
|
+
export declare function isTranslucent(color: number): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* true, если число — легальное значение цвета: 24-битный RGB, упаковка с
|
|
37
|
+
* альфой, {@link DEFAULT_COLOR} или {@link TRANSPARENT_COLOR}. Сентинелы
|
|
38
|
+
* каскада (`INHERITED_*`) и мусор — false.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isColorValue(color: number): boolean;
|
|
14
41
|
/**
|
|
15
42
|
* Накладывает полупрозрачный `fg` на непрозрачную подложку `bg` и возвращает
|
|
16
43
|
* непрозрачный результат: терминал альфы не умеет, поэтому композитинг
|
|
17
44
|
* выполняется заранее (`alpha` — доля 0..1).
|
|
18
45
|
*/
|
|
19
46
|
export declare function blendRgb(fg: number, bg: number, alpha: number): number;
|
|
47
|
+
/**
|
|
48
|
+
* Композитинг «`color` поверх `under`» (source-over): результат всегда
|
|
49
|
+
* непрозрачен или `DEFAULT_COLOR`. Непрозрачный `color` (и `DEFAULT_COLOR`)
|
|
50
|
+
* возвращается как есть — путь нулевой стоимости, одно сравнение;
|
|
51
|
+
* {@link TRANSPARENT_COLOR} оставляет подложку. Полупрозрачный цвет смешивается
|
|
52
|
+
* с `under` по альфе; если подложка — `DEFAULT_COLOR` (цвет терминала, нам
|
|
53
|
+
* неизвестен), смешивать не с чем — альфа отбрасывается, цвет ложится непрозрачным.
|
|
54
|
+
*/
|
|
55
|
+
export declare function compositeOver(color: number, under: number): number;
|
|
56
|
+
/**
|
|
57
|
+
* Разбирает CSS-подобный hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`
|
|
58
|
+
* (решётка необязательна, регистр любой) в упакованный цвет (см.
|
|
59
|
+
* {@link packRgba}). Некорректная строка — throw: цвет темы с опечаткой
|
|
60
|
+
* должен падать на разборе, а не красить в чёрный.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseHexColor(hex: string): number;
|
|
63
|
+
/**
|
|
64
|
+
* Обратное к {@link parseHexColor}: `#rrggbb` для непрозрачного, `#rrggbbaa`
|
|
65
|
+
* с альфой, `#00000000` для {@link TRANSPARENT_COLOR}. `DEFAULT_COLOR` hex не
|
|
66
|
+
* имеет — throw.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatHexColor(color: number): string;
|
|
@@ -3,10 +3,34 @@
|
|
|
3
3
|
* Must be negative so it's never confused with a packed RGB value (0x000000–0xFFFFFF).
|
|
4
4
|
*/
|
|
5
5
|
export const DEFAULT_COLOR = -1;
|
|
6
|
+
/**
|
|
7
|
+
* Полностью прозрачный цвет (`#RRGGBB00`, CSS `transparent`): при наложении
|
|
8
|
+
* ничего не меняет, а как собственный bg элемента — не красит. Сентинел, а не
|
|
9
|
+
* упаковка с альфой 0: старший байт 0 занят непрозрачными 24-битными числами.
|
|
10
|
+
*/
|
|
11
|
+
export const TRANSPARENT_COLOR = -2;
|
|
12
|
+
/** Максимальное упакованное значение с альфой (`0xFE_FFFFFF`); всё выше — не цвет. */
|
|
13
|
+
const MAX_PACKED_COLOR = 0xfeffffff;
|
|
14
|
+
/** Множитель старшего байта — альфа хранится через умножение, а не `<<`, чтобы не уйти в отрицательные. */
|
|
15
|
+
const ALPHA_UNIT = 0x1000000;
|
|
6
16
|
/** Pack three 8-bit channels into a single 24-bit integer. */
|
|
7
17
|
export function packRgb(r, g, b) {
|
|
8
18
|
return (r << 16) | (g << 8) | b;
|
|
9
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Упаковывает цвет с альфой (0..255). Непрозрачный (`a === 255`) даёт обычное
|
|
22
|
+
* 24-битное число — то же, что {@link packRgb}; `a === 0` — {@link TRANSPARENT_COLOR};
|
|
23
|
+
* промежуточная альфа кладётся в старший байт (`0xAARRGGBB`, беззнаковое
|
|
24
|
+
* значение > 0xFFFFFF). Так весь существующий код с 24-битными литералами
|
|
25
|
+
* остаётся валидным без миграции, а полупрозрачность — отличима одним сравнением.
|
|
26
|
+
*/
|
|
27
|
+
export function packRgba(r, g, b, a) {
|
|
28
|
+
if (a >= 0xff)
|
|
29
|
+
return packRgb(r, g, b);
|
|
30
|
+
if (a <= 0)
|
|
31
|
+
return TRANSPARENT_COLOR;
|
|
32
|
+
return a * ALPHA_UNIT + packRgb(r, g, b);
|
|
33
|
+
}
|
|
10
34
|
/** Extract the red channel (bits 16–23). */
|
|
11
35
|
export function unpackR(color) {
|
|
12
36
|
return (color >> 16) & 0xff;
|
|
@@ -19,6 +43,31 @@ export function unpackG(color) {
|
|
|
19
43
|
export function unpackB(color) {
|
|
20
44
|
return color & 0xff;
|
|
21
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Альфа цвета: 255 у 24-битных чисел и сентинелов (`DEFAULT_COLOR` — цвет
|
|
48
|
+
* терминала, он непрозрачен), 0 у {@link TRANSPARENT_COLOR}, иначе старший байт.
|
|
49
|
+
*/
|
|
50
|
+
export function unpackA(color) {
|
|
51
|
+
if (color === TRANSPARENT_COLOR)
|
|
52
|
+
return 0;
|
|
53
|
+
if (color <= 0xffffff)
|
|
54
|
+
return 0xff;
|
|
55
|
+
return color >>> 24;
|
|
56
|
+
}
|
|
57
|
+
/** true для полупрозрачного цвета (альфа 1..254) — того, что требует композитинга с подложкой. */
|
|
58
|
+
export function isTranslucent(color) {
|
|
59
|
+
return color > 0xffffff;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* true, если число — легальное значение цвета: 24-битный RGB, упаковка с
|
|
63
|
+
* альфой, {@link DEFAULT_COLOR} или {@link TRANSPARENT_COLOR}. Сентинелы
|
|
64
|
+
* каскада (`INHERITED_*`) и мусор — false.
|
|
65
|
+
*/
|
|
66
|
+
export function isColorValue(color) {
|
|
67
|
+
return (color === DEFAULT_COLOR ||
|
|
68
|
+
color === TRANSPARENT_COLOR ||
|
|
69
|
+
(Number.isInteger(color) && color >= 0 && color <= MAX_PACKED_COLOR));
|
|
70
|
+
}
|
|
22
71
|
/**
|
|
23
72
|
* Накладывает полупрозрачный `fg` на непрозрачную подложку `bg` и возвращает
|
|
24
73
|
* непрозрачный результат: терминал альфы не умеет, поэтому композитинг
|
|
@@ -28,3 +77,55 @@ export function blendRgb(fg, bg, alpha) {
|
|
|
28
77
|
const mix = (f, b) => Math.round(f * alpha + b * (1 - alpha));
|
|
29
78
|
return packRgb(mix(unpackR(fg), unpackR(bg)), mix(unpackG(fg), unpackG(bg)), mix(unpackB(fg), unpackB(bg)));
|
|
30
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Композитинг «`color` поверх `under`» (source-over): результат всегда
|
|
82
|
+
* непрозрачен или `DEFAULT_COLOR`. Непрозрачный `color` (и `DEFAULT_COLOR`)
|
|
83
|
+
* возвращается как есть — путь нулевой стоимости, одно сравнение;
|
|
84
|
+
* {@link TRANSPARENT_COLOR} оставляет подложку. Полупрозрачный цвет смешивается
|
|
85
|
+
* с `under` по альфе; если подложка — `DEFAULT_COLOR` (цвет терминала, нам
|
|
86
|
+
* неизвестен), смешивать не с чем — альфа отбрасывается, цвет ложится непрозрачным.
|
|
87
|
+
*/
|
|
88
|
+
export function compositeOver(color, under) {
|
|
89
|
+
if (color <= 0xffffff)
|
|
90
|
+
return color === TRANSPARENT_COLOR ? under : color;
|
|
91
|
+
const rgb = color & 0xffffff;
|
|
92
|
+
if (under < 0)
|
|
93
|
+
return rgb;
|
|
94
|
+
return blendRgb(rgb, under, (color >>> 24) / 0xff);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Разбирает CSS-подобный hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`
|
|
98
|
+
* (решётка необязательна, регистр любой) в упакованный цвет (см.
|
|
99
|
+
* {@link packRgba}). Некорректная строка — throw: цвет темы с опечаткой
|
|
100
|
+
* должен падать на разборе, а не красить в чёрный.
|
|
101
|
+
*/
|
|
102
|
+
export function parseHexColor(hex) {
|
|
103
|
+
const digits = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
104
|
+
const short = digits.length === 3 || digits.length === 4;
|
|
105
|
+
if (!(short || digits.length === 6 || digits.length === 8) || !/^[0-9a-fA-F]+$/.test(digits)) {
|
|
106
|
+
throw new Error(`parseHexColor: некорректный hex-цвет "${hex}"`);
|
|
107
|
+
}
|
|
108
|
+
const channel = (i) => {
|
|
109
|
+
if (short) {
|
|
110
|
+
const d = parseInt(digits[i], 16);
|
|
111
|
+
return d * 16 + d;
|
|
112
|
+
}
|
|
113
|
+
return parseInt(digits.slice(i * 2, i * 2 + 2), 16);
|
|
114
|
+
};
|
|
115
|
+
const hasAlpha = digits.length === 4 || digits.length === 8;
|
|
116
|
+
return packRgba(channel(0), channel(1), channel(2), hasAlpha ? channel(3) : 0xff);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Обратное к {@link parseHexColor}: `#rrggbb` для непрозрачного, `#rrggbbaa`
|
|
120
|
+
* с альфой, `#00000000` для {@link TRANSPARENT_COLOR}. `DEFAULT_COLOR` hex не
|
|
121
|
+
* имеет — throw.
|
|
122
|
+
*/
|
|
123
|
+
export function formatHexColor(color) {
|
|
124
|
+
if (color === DEFAULT_COLOR)
|
|
125
|
+
throw new Error("formatHexColor: DEFAULT_COLOR не имеет hex-представления");
|
|
126
|
+
if (color === TRANSPARENT_COLOR)
|
|
127
|
+
return "#00000000";
|
|
128
|
+
const hex2 = (n) => n.toString(16).padStart(2, "0");
|
|
129
|
+
const rgb = `#${hex2(unpackR(color))}${hex2(unpackG(color))}${hex2(unpackB(color))}`;
|
|
130
|
+
return isTranslucent(color) ? `${rgb}${hex2(unpackA(color))}` : rgb;
|
|
131
|
+
}
|
|
@@ -41,6 +41,13 @@ export interface OverlaySessionOptions {
|
|
|
41
41
|
* (владелец обработает Escape сам — например закроет только верхнее подменю).
|
|
42
42
|
*/
|
|
43
43
|
shouldCloseOnEscape?: () => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Рисовать тень под оверлеем (`TUIElement.shadow`, цвет `widget.shadow`) —
|
|
46
|
+
* TUI-аналог box-shadow попапов VS Code. Выключено по умолчанию у всех
|
|
47
|
+
* сессий, включая меню tuidom: включает хост. Не задано — остаётся
|
|
48
|
+
* `element.shadow` как выставил владелец.
|
|
49
|
+
*/
|
|
50
|
+
shadow?: boolean;
|
|
44
51
|
}
|
|
45
52
|
export interface OverlaySessionHandle {
|
|
46
53
|
readonly element: TUIElement;
|
package/dist/dom/overlayLayer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxConstraints,
|
|
1
|
+
import { BoxConstraints, Point, Size } from "../common/geometryPromitives.js";
|
|
2
2
|
import { RenderContext, TUIElement } from "./tuiElement.js";
|
|
3
3
|
export class OverlayLayer extends TUIElement {
|
|
4
4
|
items = [];
|
|
@@ -42,6 +42,8 @@ export class OverlayLayer extends TUIElement {
|
|
|
42
42
|
createSession(element, position, options) {
|
|
43
43
|
this.disposeSessionByElement(element);
|
|
44
44
|
const initialVisible = options.visible ?? false;
|
|
45
|
+
if (options.shadow !== undefined)
|
|
46
|
+
element.shadow = options.shadow;
|
|
45
47
|
this.addItem(element, position, false);
|
|
46
48
|
const session = {
|
|
47
49
|
element,
|
|
@@ -201,15 +203,9 @@ export class OverlayLayer extends TUIElement {
|
|
|
201
203
|
continue;
|
|
202
204
|
// Позиция — из localPosition (её выставил layoutChild), не из
|
|
203
205
|
// item.position: после layout авторитетна геометрия элемента (Н5).
|
|
204
|
-
// Клип по границам ребёнка —
|
|
205
|
-
// не выходит за layoutSize, хит-зона совпадает с видимым.
|
|
206
|
-
|
|
207
|
-
const childOffset = new Offset(child.localPosition.dx, child.localPosition.dy);
|
|
208
|
-
const clip = new Rect(child.globalPosition, child.layoutSize);
|
|
209
|
-
const childContext = context.withOffset(childOffset).withClip(clip);
|
|
210
|
-
if (childContext.clipRect.isEmpty)
|
|
211
|
-
continue;
|
|
212
|
-
child.render(childContext);
|
|
206
|
+
// Клип по границам ребёнка и тень за ним — общий renderChild (Н2):
|
|
207
|
+
// нарисованное не выходит за layoutSize, хит-зона совпадает с видимым.
|
|
208
|
+
this.renderChild(context, item.element);
|
|
213
209
|
}
|
|
214
210
|
}
|
|
215
211
|
openSession(session) {
|
|
@@ -72,6 +72,7 @@ export declare const STYLE_TOKEN_DEFAULTS: {
|
|
|
72
72
|
"editorSuggestWidget.selectedForeground": number;
|
|
73
73
|
"editorSuggestWidget.iconForeground": number;
|
|
74
74
|
"editorSuggestWidget.detailForeground": number;
|
|
75
|
+
"widget.shadow": number;
|
|
75
76
|
"editorWidget.foreground": number;
|
|
76
77
|
"editorWidget.background": number;
|
|
77
78
|
"editorWidget.border": number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_COLOR, packRgb } from "../../common/colorUtils.js";
|
|
1
|
+
import { DEFAULT_COLOR, packRgb, packRgba } from "../../common/colorUtils.js";
|
|
2
2
|
/**
|
|
3
3
|
* Дефолтные значения цветовых токенов tuidom — единственное место, где у
|
|
4
4
|
* виджетов tuidom/ui есть RGB-литералы. Правило: токен, на который ссылается
|
|
@@ -85,6 +85,10 @@ export const STYLE_TOKEN_DEFAULTS = {
|
|
|
85
85
|
"editorSuggestWidget.selectedForeground": packRgb(255, 255, 255),
|
|
86
86
|
"editorSuggestWidget.iconForeground": packRgb(130, 170, 255),
|
|
87
87
|
"editorSuggestWidget.detailForeground": packRgb(120, 120, 130),
|
|
88
|
+
// ── Оверлеи ──
|
|
89
|
+
// Тень попапа (`TUIElement.shadow`): полупрозрачный чёрный, композитится с
|
|
90
|
+
// тем, что под ним. Значение — дефолт VS Code для тёмных тем (`#00000059`).
|
|
91
|
+
"widget.shadow": packRgba(0, 0, 0, 0x59),
|
|
88
92
|
// ── Виджеты редактора (диалоги, find) ──
|
|
89
93
|
"editorWidget.foreground": packRgb(204, 204, 204),
|
|
90
94
|
"editorWidget.background": packRgb(37, 37, 38),
|
|
@@ -72,4 +72,10 @@ export declare function mergeStyleVariants(style: TUIStyle, isActive: (selector:
|
|
|
72
72
|
fg: StyleColor | undefined;
|
|
73
73
|
bg: StyleColor | undefined;
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Резолв базовых fg/bg без when-вариантов и var-scope (утилита для тестов и
|
|
77
|
+
* простых потребителей; ядро идёт через `TUIElement.performStyleResolution`,
|
|
78
|
+
* та же модель): цвет с альфой композитится с унаследованным bg, fg — с
|
|
79
|
+
* итоговым bg, наружу уходят непрозрачные значения.
|
|
80
|
+
*/
|
|
75
81
|
export declare function resolveStyle(style: TUIStyle, inherited: ResolvedTUIStyle): ResolvedTUIStyle;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_COLOR } from "../../common/colorUtils.js";
|
|
1
|
+
import { compositeOver, DEFAULT_COLOR } from "../../common/colorUtils.js";
|
|
2
2
|
import { ROOT_VAR_SCOPE } from "./styleTokens.js";
|
|
3
3
|
// ─── Inherited Color Sentinels ───
|
|
4
4
|
// Sentinel values that resolve to the inherited fg/bg from the parent.
|
|
@@ -114,8 +114,16 @@ export function mergeStyleVariants(style, isActive) {
|
|
|
114
114
|
}
|
|
115
115
|
return { fg, bg };
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Резолв базовых fg/bg без when-вариантов и var-scope (утилита для тестов и
|
|
119
|
+
* простых потребителей; ядро идёт через `TUIElement.performStyleResolution`,
|
|
120
|
+
* та же модель): цвет с альфой композитится с унаследованным bg, fg — с
|
|
121
|
+
* итоговым bg, наружу уходят непрозрачные значения.
|
|
122
|
+
*/
|
|
117
123
|
export function resolveStyle(style, inherited) {
|
|
118
|
-
const
|
|
119
|
-
const bg =
|
|
124
|
+
const ownBg = style.bg !== undefined ? resolveStyleColor(style.bg, inherited.fg, inherited.bg) : inherited.bg;
|
|
125
|
+
const bg = compositeOver(ownBg, inherited.bg);
|
|
126
|
+
const ownFg = style.fg !== undefined ? resolveStyleColor(style.fg, inherited.fg, inherited.bg) : inherited.fg;
|
|
127
|
+
const fg = compositeOver(ownFg, bg);
|
|
120
128
|
return { fg, bg };
|
|
121
129
|
}
|
package/dist/dom/tuiElement.d.ts
CHANGED
|
@@ -143,6 +143,8 @@ export declare class TUIElement {
|
|
|
143
143
|
private subtreeStyleDirty;
|
|
144
144
|
private appliedFgValue;
|
|
145
145
|
private appliedBgValue;
|
|
146
|
+
/** Красит ли элемент собственный фон: bg задан своим стилем и не TRANSPARENT_COLOR. */
|
|
147
|
+
private paintsOwnBackgroundValue;
|
|
146
148
|
private styleStatesSet;
|
|
147
149
|
private styleVarsValue;
|
|
148
150
|
private varScopeRef;
|
|
@@ -182,6 +184,25 @@ export declare class TUIElement {
|
|
|
182
184
|
get hidden(): boolean;
|
|
183
185
|
set hidden(value: boolean);
|
|
184
186
|
private hiddenValue;
|
|
187
|
+
/**
|
|
188
|
+
* Тень оверлея — TUI-аналог `box-shadow` VS Code: колонка справа и строка
|
|
189
|
+
* снизу от элемента (сдвиг 1×1) затемняются цветом `widget.shadow`
|
|
190
|
+
* (с альфой, композитится с тем, что уже нарисовано). Рисует не сам
|
|
191
|
+
* элемент, а его родитель после него ({@link renderChildren},
|
|
192
|
+
* `OverlayLayer.render`): клип ребёнка не выпускает его за собственный
|
|
193
|
+
* rect (Н2), а тень лежит снаружи. Damage-обход учитывает её через
|
|
194
|
+
* {@link paintOutset}. Отдаёт форму оверлею там, где тема рисует рамку
|
|
195
|
+
* цветом фона (Catppuccin: `menu.border` = фон меню).
|
|
196
|
+
*
|
|
197
|
+
* Рисуют её канонические циклы — {@link renderChildren} и
|
|
198
|
+
* `OverlayLayer.render` (оба через {@link renderChild}). Контейнеры со своим
|
|
199
|
+
* циклом (`SizedBoxElement`, `ScrollViewport`, строки списка) ребёнка
|
|
200
|
+
* растягивают на весь свой rect — тени там некуда лечь, она осталась бы за
|
|
201
|
+
* клипом самого контейнера.
|
|
202
|
+
*/
|
|
203
|
+
get shadow(): boolean;
|
|
204
|
+
set shadow(value: boolean);
|
|
205
|
+
private shadowValue;
|
|
185
206
|
/** Прикрепляет ребёнка в конец списка (снимая с прежнего родителя). */
|
|
186
207
|
protected appendChild(child: TUIElement): void;
|
|
187
208
|
/** Прикрепляет ребёнка на позицию index (снимая с прежнего родителя). */
|
|
@@ -283,8 +304,9 @@ export declare class TUIElement {
|
|
|
283
304
|
* предков и дефолтов tuidom (STYLE_TOKEN_DEFAULTS). Обычное место — корень:
|
|
284
305
|
* хост транслирует сюда палитру темы одним вызовом (hot-swap = повторный
|
|
285
306
|
* вызов). Таблица заменяется целиком, null — снимает. Значения — только
|
|
286
|
-
* конкретные числа (packed RGB
|
|
287
|
-
*
|
|
307
|
+
* конкретные числа (packed RGB, в том числе с альфой — `packRgba`/
|
|
308
|
+
* `parseHexColor`, | DEFAULT_COLOR | TRANSPARENT_COLOR); сентинелы
|
|
309
|
+
* INHERITED_* нелегальны.
|
|
288
310
|
*/
|
|
289
311
|
setStyleVars(vars: Readonly<Record<string, number>> | null): void;
|
|
290
312
|
/**
|
|
@@ -360,6 +382,11 @@ export declare class TUIElement {
|
|
|
360
382
|
* запустило бы lazy-layout с мусорными constraints).
|
|
361
383
|
*/
|
|
362
384
|
collectDamage(sink: DamageList, parentOrigin: Point): void;
|
|
385
|
+
/**
|
|
386
|
+
* На сколько ячеек вправо и вниз родитель рисует за границей элемента
|
|
387
|
+
* (тень, см. {@link shadow}). Damage-rect элемента расширяется на столько же.
|
|
388
|
+
*/
|
|
389
|
+
protected get paintOutset(): number;
|
|
363
390
|
/** Обход детей damage-сбора — seam для контейнеров с нестандартной структурой. */
|
|
364
391
|
protected collectChildrenDamage(sink: DamageList, origin: Point): void;
|
|
365
392
|
/**
|
|
@@ -503,6 +530,10 @@ export declare class TUIElement {
|
|
|
503
530
|
* флагов стиля: собственный фон — это новая поверхность, и атрибуты того,
|
|
504
531
|
* что нарисовано под ней раньше в этом кадре (подчёркивание диагностики под
|
|
505
532
|
* попапом, жирный токен под диалогом), на ней проступать не должны.
|
|
533
|
+
*
|
|
534
|
+
* Полупрозрачный собственный bg уже скомпозичен каскадом с унаследованным
|
|
535
|
+
* (см. {@link performStyleResolution}); `TRANSPARENT_COLOR` (`#RRGGBB00`)
|
|
536
|
+
* считается «фона нет» — как CSS `transparent`, элемент ничего не красит.
|
|
506
537
|
*/
|
|
507
538
|
protected paintOwnBackground(context: RenderContext): void;
|
|
508
539
|
/** true, если фон задан собственным стилем (см. {@link paintOwnBackground}). */
|
|
@@ -522,6 +553,32 @@ export declare class TUIElement {
|
|
|
522
553
|
* самый цикл, который раньше был скопирован в десяток контейнеров.
|
|
523
554
|
*/
|
|
524
555
|
protected renderChildren(context: RenderContext): void;
|
|
556
|
+
/**
|
|
557
|
+
* Отрисовка одного ребёнка в контексте родителя: сдвиг на localPosition,
|
|
558
|
+
* клип по границам ребёнка, затем его тень (см. {@link shadow}) — уже в
|
|
559
|
+
* контексте родителя, потому что она лежит за клипом ребёнка. Тень не
|
|
560
|
+
* зависит от того, попал ли сам ребёнок в клип: damage-rect может задеть
|
|
561
|
+
* только полосу тени (перерисовался виджет под ней), и тогда ребёнок
|
|
562
|
+
* пропускается, а тень обязана лечь заново.
|
|
563
|
+
*/
|
|
564
|
+
protected renderChild(context: RenderContext, child: TUIElement): void;
|
|
565
|
+
/**
|
|
566
|
+
* Тень ребёнка (см. {@link shadow}) в контексте РОДИТЕЛЯ: колонка справа
|
|
567
|
+
* (x = right, строки top+1…bottom) и строка снизу (y = bottom, колонки
|
|
568
|
+
* left+1…right) — классический сдвиг 1×1. Ячейка затемняется цветом
|
|
569
|
+
* `widget.shadow` из var-scope ребёнка: bg композитит грид, fg — здесь
|
|
570
|
+
* (грид кладёт fg с альфой на bg, а не на прежний fg); терминальный
|
|
571
|
+
* DEFAULT_COLOR у fg не трогаем — смешивать не с чем.
|
|
572
|
+
*
|
|
573
|
+
* Широкий глиф (CJK, эмодзи) наполовину не красится: голова тянет цвета в
|
|
574
|
+
* продолжение (`Grid.updateCell`), поэтому глиф под кромкой тени
|
|
575
|
+
* затемняется целиком — тень на этой строке шире на колонку. Продолжение
|
|
576
|
+
* в полосе пропускается: его голова либо сама в полосе (уже покрашена —
|
|
577
|
+
* иначе двойная тень), либо внутри ребёнка (его глиф у правой кромки).
|
|
578
|
+
* Единственное исключение — голова в пропущенном нижнем левом углу: такой
|
|
579
|
+
* глиф красится целиком через голову.
|
|
580
|
+
*/
|
|
581
|
+
protected paintChildShadow(context: RenderContext, child: TUIElement): void;
|
|
525
582
|
/**
|
|
526
583
|
* Финальный шаблон хит-теста — НЕ переопределять (кастомизация — через
|
|
527
584
|
* {@link hitTestChildren}/{@link hitTestSelf}): скрытое не кликается,
|
package/dist/dom/tuiElement.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compositeOver, isColorValue, TRANSPARENT_COLOR } from "../common/colorUtils.js";
|
|
2
2
|
import { DisplayLine } from "../common/displayLine.js";
|
|
3
3
|
import { BoxConstraints, Offset, Point, Rect, Size } from "../common/geometryPromitives.js";
|
|
4
4
|
import { StyleFlags } from "../common/styleFlags.js";
|
|
@@ -217,6 +217,8 @@ export class TUIElement {
|
|
|
217
217
|
// вопрос «задан ли цвет собственным стилем» (заливка фона, инспектор).
|
|
218
218
|
appliedFgValue;
|
|
219
219
|
appliedBgValue;
|
|
220
|
+
/** Красит ли элемент собственный фон: bg задан своим стилем и не TRANSPARENT_COLOR. */
|
|
221
|
+
paintsOwnBackgroundValue = false;
|
|
220
222
|
// Активные состояния (hover/focus ведёт ядро, прочие — виджеты). Lazy:
|
|
221
223
|
// у подавляющего большинства элементов состояний нет.
|
|
222
224
|
styleStatesSet = null;
|
|
@@ -310,6 +312,32 @@ export class TUIElement {
|
|
|
310
312
|
this.markDirty();
|
|
311
313
|
}
|
|
312
314
|
hiddenValue = false;
|
|
315
|
+
/**
|
|
316
|
+
* Тень оверлея — TUI-аналог `box-shadow` VS Code: колонка справа и строка
|
|
317
|
+
* снизу от элемента (сдвиг 1×1) затемняются цветом `widget.shadow`
|
|
318
|
+
* (с альфой, композитится с тем, что уже нарисовано). Рисует не сам
|
|
319
|
+
* элемент, а его родитель после него ({@link renderChildren},
|
|
320
|
+
* `OverlayLayer.render`): клип ребёнка не выпускает его за собственный
|
|
321
|
+
* rect (Н2), а тень лежит снаружи. Damage-обход учитывает её через
|
|
322
|
+
* {@link paintOutset}. Отдаёт форму оверлею там, где тема рисует рамку
|
|
323
|
+
* цветом фона (Catppuccin: `menu.border` = фон меню).
|
|
324
|
+
*
|
|
325
|
+
* Рисуют её канонические циклы — {@link renderChildren} и
|
|
326
|
+
* `OverlayLayer.render` (оба через {@link renderChild}). Контейнеры со своим
|
|
327
|
+
* циклом (`SizedBoxElement`, `ScrollViewport`, строки списка) ребёнка
|
|
328
|
+
* растягивают на весь свой rect — тени там некуда лечь, она осталась бы за
|
|
329
|
+
* клипом самого контейнера.
|
|
330
|
+
*/
|
|
331
|
+
get shadow() {
|
|
332
|
+
return this.shadowValue;
|
|
333
|
+
}
|
|
334
|
+
set shadow(value) {
|
|
335
|
+
if (this.shadowValue === value)
|
|
336
|
+
return;
|
|
337
|
+
this.shadowValue = value;
|
|
338
|
+
this.markDirty();
|
|
339
|
+
}
|
|
340
|
+
shadowValue = false;
|
|
313
341
|
/** Прикрепляет ребёнка в конец списка (снимая с прежнего родителя). */
|
|
314
342
|
appendChild(child) {
|
|
315
343
|
this.insertChild(this.childrenList.length, child);
|
|
@@ -571,13 +599,19 @@ export class TUIElement {
|
|
|
571
599
|
const vars = this.styleVarsValue !== null ? extendVarScope(context.vars, this.styleVarsValue) : context.vars;
|
|
572
600
|
this.varScopeRef = vars;
|
|
573
601
|
const describe = () => this.describeForStyleError();
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
602
|
+
// Цвет с альфой композитится уже здесь, с унаследованным bg: в
|
|
603
|
+
// resolvedStyle и детям уходят непрозрачные значения, иначе заливка
|
|
604
|
+
// фона и текст с тем же bg наложились бы на ячейку дважды.
|
|
605
|
+
const ownBg = applied.bg !== undefined
|
|
578
606
|
? resolveStyleColor(applied.bg, context.fg, context.bg, vars, describe)
|
|
579
607
|
: context.bg;
|
|
608
|
+
const bg = compositeOver(ownBg, context.bg);
|
|
609
|
+
const ownFg = applied.fg !== undefined
|
|
610
|
+
? resolveStyleColor(applied.fg, context.fg, context.bg, vars, describe)
|
|
611
|
+
: context.fg;
|
|
612
|
+
const fg = compositeOver(ownFg, bg);
|
|
580
613
|
this.resolvedStyleValue = { fg, bg };
|
|
614
|
+
this.paintsOwnBackgroundValue = applied.bg !== undefined && ownBg !== TRANSPARENT_COLOR;
|
|
581
615
|
this.childStyleContext = this.buildChildStyleContext(context);
|
|
582
616
|
}
|
|
583
617
|
this.isStyleDirty = false;
|
|
@@ -636,15 +670,16 @@ export class TUIElement {
|
|
|
636
670
|
* предков и дефолтов tuidom (STYLE_TOKEN_DEFAULTS). Обычное место — корень:
|
|
637
671
|
* хост транслирует сюда палитру темы одним вызовом (hot-swap = повторный
|
|
638
672
|
* вызов). Таблица заменяется целиком, null — снимает. Значения — только
|
|
639
|
-
* конкретные числа (packed RGB
|
|
640
|
-
*
|
|
673
|
+
* конкретные числа (packed RGB, в том числе с альфой — `packRgba`/
|
|
674
|
+
* `parseHexColor`, | DEFAULT_COLOR | TRANSPARENT_COLOR); сентинелы
|
|
675
|
+
* INHERITED_* нелегальны.
|
|
641
676
|
*/
|
|
642
677
|
setStyleVars(vars) {
|
|
643
678
|
if (vars === this.styleVarsValue)
|
|
644
679
|
return;
|
|
645
680
|
if (vars !== null) {
|
|
646
681
|
for (const key of Object.keys(vars)) {
|
|
647
|
-
if (vars[key]
|
|
682
|
+
if (!isColorValue(vars[key])) {
|
|
648
683
|
throw new Error(`${this.describeForStyleError()}.setStyleVars: токен "${key}" содержит сентинел/некорректное значение ${vars[key]} — таблицы принимают только конкретные цвета`);
|
|
649
684
|
}
|
|
650
685
|
}
|
|
@@ -804,7 +839,11 @@ export class TUIElement {
|
|
|
804
839
|
this.hasPaintDirtyDescendant = false;
|
|
805
840
|
return;
|
|
806
841
|
}
|
|
807
|
-
|
|
842
|
+
// Rect для damage — с outset'ом: тень родитель рисует за границей
|
|
843
|
+
// allocatedSize, и её ячейки тоже должны перерисоваться при переезде,
|
|
844
|
+
// скрытии и paint-dirty.
|
|
845
|
+
const outset = this.paintOutset;
|
|
846
|
+
const rect = new Rect(new Point(parentOrigin.x + this.localPosition.dx, parentOrigin.y + this.localPosition.dy), new Size(this.allocatedSize.width + outset, this.allocatedSize.height + outset));
|
|
808
847
|
const old = this.lastPaintedRect;
|
|
809
848
|
// Ещё не рисовался — тоже moved. Optional chain здесь не подходит: после
|
|
810
849
|
// первого `old?.x` тип уже сужен, и остальные `old?.` стали бы лишними.
|
|
@@ -830,6 +869,13 @@ export class TUIElement {
|
|
|
830
869
|
if (descend)
|
|
831
870
|
this.collectChildrenDamage(sink, rect.origin);
|
|
832
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* На сколько ячеек вправо и вниз родитель рисует за границей элемента
|
|
874
|
+
* (тень, см. {@link shadow}). Damage-rect элемента расширяется на столько же.
|
|
875
|
+
*/
|
|
876
|
+
get paintOutset() {
|
|
877
|
+
return this.shadowValue ? 1 : 0;
|
|
878
|
+
}
|
|
833
879
|
/** Обход детей damage-сбора — seam для контейнеров с нестандартной структурой. */
|
|
834
880
|
collectChildrenDamage(sink, origin) {
|
|
835
881
|
for (const child of this.childrenList)
|
|
@@ -1101,9 +1147,13 @@ export class TUIElement {
|
|
|
1101
1147
|
* флагов стиля: собственный фон — это новая поверхность, и атрибуты того,
|
|
1102
1148
|
* что нарисовано под ней раньше в этом кадре (подчёркивание диагностики под
|
|
1103
1149
|
* попапом, жирный токен под диалогом), на ней проступать не должны.
|
|
1150
|
+
*
|
|
1151
|
+
* Полупрозрачный собственный bg уже скомпозичен каскадом с унаследованным
|
|
1152
|
+
* (см. {@link performStyleResolution}); `TRANSPARENT_COLOR` (`#RRGGBB00`)
|
|
1153
|
+
* считается «фона нет» — как CSS `transparent`, элемент ничего не красит.
|
|
1104
1154
|
*/
|
|
1105
1155
|
paintOwnBackground(context) {
|
|
1106
|
-
if (this.
|
|
1156
|
+
if (!this.paintsOwnBackgroundValue)
|
|
1107
1157
|
return;
|
|
1108
1158
|
const { fg, bg } = this.resolvedStyleValue;
|
|
1109
1159
|
const { width, height } = this.layoutSize;
|
|
@@ -1115,7 +1165,7 @@ export class TUIElement {
|
|
|
1115
1165
|
}
|
|
1116
1166
|
/** true, если фон задан собственным стилем (см. {@link paintOwnBackground}). */
|
|
1117
1167
|
get hasOwnBackground() {
|
|
1118
|
-
return this.
|
|
1168
|
+
return this.paintsOwnBackgroundValue;
|
|
1119
1169
|
}
|
|
1120
1170
|
/**
|
|
1121
1171
|
* Сырые цвета после when-merge, до резолва токенов/сентинелов: что элемент
|
|
@@ -1134,16 +1184,75 @@ export class TUIElement {
|
|
|
1134
1184
|
for (const child of this.getChildren()) {
|
|
1135
1185
|
if (child.hidden)
|
|
1136
1186
|
continue;
|
|
1137
|
-
|
|
1138
|
-
const clip = new Rect(child.globalPosition, child.layoutSize);
|
|
1139
|
-
const childContext = context.withOffset(offset).withClip(clip);
|
|
1140
|
-
// Пустой клип — ребёнок целиком вне отрисовываемой области: пропуск
|
|
1141
|
-
// всего поддерева, включая side-эффекты его render.
|
|
1142
|
-
if (childContext.clipRect.isEmpty)
|
|
1143
|
-
continue;
|
|
1144
|
-
child.render(childContext);
|
|
1187
|
+
this.renderChild(context, child);
|
|
1145
1188
|
}
|
|
1146
1189
|
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Отрисовка одного ребёнка в контексте родителя: сдвиг на localPosition,
|
|
1192
|
+
* клип по границам ребёнка, затем его тень (см. {@link shadow}) — уже в
|
|
1193
|
+
* контексте родителя, потому что она лежит за клипом ребёнка. Тень не
|
|
1194
|
+
* зависит от того, попал ли сам ребёнок в клип: damage-rect может задеть
|
|
1195
|
+
* только полосу тени (перерисовался виджет под ней), и тогда ребёнок
|
|
1196
|
+
* пропускается, а тень обязана лечь заново.
|
|
1197
|
+
*/
|
|
1198
|
+
renderChild(context, child) {
|
|
1199
|
+
const offset = new Offset(child.localPosition.dx, child.localPosition.dy);
|
|
1200
|
+
const clip = new Rect(child.globalPosition, child.layoutSize);
|
|
1201
|
+
const childContext = context.withOffset(offset).withClip(clip);
|
|
1202
|
+
// Пустой клип — ребёнок целиком вне отрисовываемой области: пропуск
|
|
1203
|
+
// всего поддерева, включая side-эффекты его render.
|
|
1204
|
+
if (!childContext.clipRect.isEmpty)
|
|
1205
|
+
child.render(childContext);
|
|
1206
|
+
if (child.shadow)
|
|
1207
|
+
this.paintChildShadow(context, child);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Тень ребёнка (см. {@link shadow}) в контексте РОДИТЕЛЯ: колонка справа
|
|
1211
|
+
* (x = right, строки top+1…bottom) и строка снизу (y = bottom, колонки
|
|
1212
|
+
* left+1…right) — классический сдвиг 1×1. Ячейка затемняется цветом
|
|
1213
|
+
* `widget.shadow` из var-scope ребёнка: bg композитит грид, fg — здесь
|
|
1214
|
+
* (грид кладёт fg с альфой на bg, а не на прежний fg); терминальный
|
|
1215
|
+
* DEFAULT_COLOR у fg не трогаем — смешивать не с чем.
|
|
1216
|
+
*
|
|
1217
|
+
* Широкий глиф (CJK, эмодзи) наполовину не красится: голова тянет цвета в
|
|
1218
|
+
* продолжение (`Grid.updateCell`), поэтому глиф под кромкой тени
|
|
1219
|
+
* затемняется целиком — тень на этой строке шире на колонку. Продолжение
|
|
1220
|
+
* в полосе пропускается: его голова либо сама в полосе (уже покрашена —
|
|
1221
|
+
* иначе двойная тень), либо внутри ребёнка (его глиф у правой кромки).
|
|
1222
|
+
* Единственное исключение — голова в пропущенном нижнем левом углу: такой
|
|
1223
|
+
* глиф красится целиком через голову.
|
|
1224
|
+
*/
|
|
1225
|
+
paintChildShadow(context, child) {
|
|
1226
|
+
const color = child.styleVar("widget.shadow");
|
|
1227
|
+
if (color === TRANSPARENT_COLOR)
|
|
1228
|
+
return;
|
|
1229
|
+
const left = child.localPosition.dx;
|
|
1230
|
+
const top = child.localPosition.dy;
|
|
1231
|
+
const right = left + child.layoutSize.width;
|
|
1232
|
+
const bottom = top + child.layoutSize.height;
|
|
1233
|
+
const shadeCell = (x, y, cell) => {
|
|
1234
|
+
context.setCell(x, y, { bg: color, fg: cell.fg < 0 ? cell.fg : compositeOver(color, cell.fg) });
|
|
1235
|
+
};
|
|
1236
|
+
const shade = (x, y, wholeGlyph) => {
|
|
1237
|
+
const cell = context.getCell(x, y);
|
|
1238
|
+
if (cell === null)
|
|
1239
|
+
return;
|
|
1240
|
+
if (cell.width !== 0) {
|
|
1241
|
+
shadeCell(x, y, cell);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (!wholeGlyph)
|
|
1245
|
+
return;
|
|
1246
|
+
const head = context.getCell(x - 1, y);
|
|
1247
|
+
if (head === null)
|
|
1248
|
+
return;
|
|
1249
|
+
shadeCell(x - 1, y, head);
|
|
1250
|
+
};
|
|
1251
|
+
for (let y = top + 1; y <= bottom; y++)
|
|
1252
|
+
shade(right, y, false);
|
|
1253
|
+
for (let x = left + 1; x < right; x++)
|
|
1254
|
+
shade(x, bottom, x === left + 1);
|
|
1255
|
+
}
|
|
1147
1256
|
// ─── Hit-testing ───
|
|
1148
1257
|
//
|
|
1149
1258
|
// Правило системы (Н6): рендер и Tab обходят детей ВПЕРЁД, хит-тест — тем
|
package/dist/rendering/grid.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface CellPatch {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* 2D grid of terminal cells backed by a flat array for cache-friendly access.
|
|
18
|
+
*
|
|
19
|
+
* Ячейки хранят только непрозрачные цвета (или DEFAULT_COLOR): цвет с альфой,
|
|
20
|
+
* пришедший в {@link setCell}/{@link updateCell}, композитится с текущим
|
|
21
|
+
* содержимым ячейки на месте (см. `compositeOver`). Так порядок отрисовки —
|
|
22
|
+
* родитель, дети, оверлеи, патчи подсветок — становится порядком наложения
|
|
23
|
+
* слоёв, как в браузере, а бэкенды/снапшоты/SVG видят уже готовые цвета.
|
|
18
24
|
*/
|
|
19
25
|
export declare class Grid {
|
|
20
26
|
readonly size: Size;
|
package/dist/rendering/grid.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { DEFAULT_COLOR } from "../common/colorUtils.js";
|
|
1
|
+
import { compositeOver, DEFAULT_COLOR } from "../common/colorUtils.js";
|
|
2
2
|
import { Point, Rect, Size } from "../common/geometryPromitives.js";
|
|
3
3
|
import { StyleFlags } from "../common/styleFlags.js";
|
|
4
4
|
import { Cell } from "./cell.js";
|
|
5
5
|
/**
|
|
6
6
|
* 2D grid of terminal cells backed by a flat array for cache-friendly access.
|
|
7
|
+
*
|
|
8
|
+
* Ячейки хранят только непрозрачные цвета (или DEFAULT_COLOR): цвет с альфой,
|
|
9
|
+
* пришедший в {@link setCell}/{@link updateCell}, композитится с текущим
|
|
10
|
+
* содержимым ячейки на месте (см. `compositeOver`). Так порядок отрисовки —
|
|
11
|
+
* родитель, дети, оверлеи, патчи подсветок — становится порядком наложения
|
|
12
|
+
* слоёв, как в браузере, а бэкенды/снапшоты/SVG видят уже готовые цвета.
|
|
7
13
|
*/
|
|
8
14
|
export class Grid {
|
|
9
15
|
size;
|
|
@@ -55,8 +61,8 @@ export class Grid {
|
|
|
55
61
|
/* v8 ignore stop */
|
|
56
62
|
}
|
|
57
63
|
cell.char = char;
|
|
58
|
-
cell.
|
|
59
|
-
cell.
|
|
64
|
+
cell.bg = compositeOver(bg, cell.bg);
|
|
65
|
+
cell.fg = compositeOver(fg, cell.bg);
|
|
60
66
|
cell.style = style;
|
|
61
67
|
cell.width = width;
|
|
62
68
|
// For wide chars, set up the continuation cell
|
|
@@ -73,8 +79,8 @@ export class Grid {
|
|
|
73
79
|
/* v8 ignore stop */
|
|
74
80
|
}
|
|
75
81
|
cont.char = "";
|
|
76
|
-
cont.fg = fg;
|
|
77
|
-
cont.bg = bg;
|
|
82
|
+
cont.fg = cell.fg;
|
|
83
|
+
cont.bg = cell.bg;
|
|
78
84
|
cont.style = style;
|
|
79
85
|
cont.width = 0;
|
|
80
86
|
}
|
|
@@ -108,10 +114,13 @@ export class Grid {
|
|
|
108
114
|
}
|
|
109
115
|
if (patch.char !== undefined)
|
|
110
116
|
cell.char = patch.char;
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
// Композитинг в порядке отрисовки: полупрозрачный bg ложится на то, что
|
|
118
|
+
// уже лежит в ячейке в этом кадре, полупрозрачный fg — на итоговый bg.
|
|
119
|
+
// Непрозрачные цвета проходят как раньше (compositeOver — одно сравнение).
|
|
113
120
|
if (patch.bg !== undefined)
|
|
114
|
-
cell.bg = patch.bg;
|
|
121
|
+
cell.bg = compositeOver(patch.bg, cell.bg);
|
|
122
|
+
if (patch.fg !== undefined)
|
|
123
|
+
cell.fg = compositeOver(patch.fg, cell.bg);
|
|
115
124
|
if (patch.style !== undefined)
|
|
116
125
|
cell.style = patch.style;
|
|
117
126
|
if (patch.width !== undefined)
|
|
@@ -132,10 +141,11 @@ export class Grid {
|
|
|
132
141
|
}
|
|
133
142
|
cont.char = "";
|
|
134
143
|
cont.width = 0;
|
|
144
|
+
// Продолжение — та же ячейка: берёт уже скомпозиченные цвета головы.
|
|
135
145
|
if (patch.fg !== undefined)
|
|
136
|
-
cont.fg =
|
|
146
|
+
cont.fg = cell.fg;
|
|
137
147
|
if (patch.bg !== undefined)
|
|
138
|
-
cont.bg =
|
|
148
|
+
cont.bg = cell.bg;
|
|
139
149
|
if (patch.style !== undefined)
|
|
140
150
|
cont.style = patch.style;
|
|
141
151
|
}
|
package/package.json
CHANGED