@pixi-ui-editor/runtime-pixi 0.8.0 → 0.9.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/controls.d.ts +17 -1
- package/dist/controls.d.ts.map +1 -1
- package/dist/controls.js +30 -1
- package/dist/controls.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +36 -0
- package/dist/scene.js.map +1 -1
- package/dist/views/ItemTableNodeView.d.ts +57 -0
- package/dist/views/ItemTableNodeView.d.ts.map +1 -0
- package/dist/views/ItemTableNodeView.js +100 -0
- package/dist/views/ItemTableNodeView.js.map +1 -0
- package/dist/views/NodeView.d.ts +14 -0
- package/dist/views/NodeView.d.ts.map +1 -1
- package/dist/views/NodeView.js +18 -1
- package/dist/views/NodeView.js.map +1 -1
- package/dist/views/PaginationNodeView.d.ts +38 -2
- package/dist/views/PaginationNodeView.d.ts.map +1 -1
- package/dist/views/PaginationNodeView.js +85 -28
- package/dist/views/PaginationNodeView.js.map +1 -1
- package/dist/views/createNodeView.d.ts.map +1 -1
- package/dist/views/createNodeView.js +4 -0
- package/dist/views/createNodeView.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { resolveItemTableVisibleCount } from "@pixi-ui-editor/schema";
|
|
2
|
+
import { NodeView } from "./NodeView.js";
|
|
3
|
+
/**
|
|
4
|
+
* Узел фиксированной вместимости: держит авторский список узлов (`items`, ссылки по адресу — их
|
|
5
|
+
* разрешает `scene.ts`, тем же приёмом, что цели spine-коннекторов) и показывает только первые
|
|
6
|
+
* `visibleCount` из них — остальные скрыты всегда, а не «на этой странице». Автор раскладывает
|
|
7
|
+
* элементы под максимум, а сколько их реально помещается, задаётся на профиль: в разной ориентации
|
|
8
|
+
* экрана на экран влезает разное число строк.
|
|
9
|
+
*
|
|
10
|
+
* Список именно авторский, а не дети: элемент обычно уже лежит там, где его выравнивает layout-группа.
|
|
11
|
+
*
|
|
12
|
+
* Узел намеренно ничего больше не делает. Он не страницы (`PageGroupNodeView` показывает ровно
|
|
13
|
+
* одного ребёнка и растягивает его на себя), не раскладка (позицию элементы сохраняют свою) и ничего не
|
|
14
|
+
* знает ни про какие данные: чем наполнить видимые элементы и когда, решает игровой код через
|
|
15
|
+
* generic API из `controls.ts` (`getItemTableViewVisibleItems`, `getItemTableViewItem`,
|
|
16
|
+
* `setItemTableViewVisibleCount`, `setItemTableViewItemsVisible`). Связка «переключили страницу →
|
|
17
|
+
* перезаполнили элементы» тоже принадлежит игре: `PaginationNodeView.onPageChange` и эта таблица
|
|
18
|
+
* ничего друг о друге не знают.
|
|
19
|
+
*
|
|
20
|
+
* Видимость складывается с авторской: элемент, скрытый в документе, не станет видимым из-за
|
|
21
|
+
* вместимости, а скрытый вместимостью держится флагом `NodeView.hiddenByOwner` (см. `applyVisibility`).
|
|
22
|
+
*/
|
|
23
|
+
export class ItemTableNodeView extends NodeView {
|
|
24
|
+
itemViews = [];
|
|
25
|
+
authoredVisibleCount = 0;
|
|
26
|
+
/** Транзиентное переопределение от игрового кода; `undefined` — авторское значение профиля. */
|
|
27
|
+
runtimeVisibleCount;
|
|
28
|
+
/** Транзиентный «скрыть/показать всё» поверх вместимости; `undefined` — вместимость решает сама. */
|
|
29
|
+
forcedItemsVisible;
|
|
30
|
+
/**
|
|
31
|
+
* Разрешённые views авторского списка `items`, позиционно выровненные с ним (`scene.ts`). Ссылка,
|
|
32
|
+
* которая ни во что не разрешилась, сохраняет свою дырку `undefined`, чтобы остальные элементы не
|
|
33
|
+
* сдвинулись; сама висячая ссылка — ошибка валидации документа, а не забота этого вида.
|
|
34
|
+
*/
|
|
35
|
+
setItems(views) {
|
|
36
|
+
// Выбывшие из списка возвращаются под управление документа: иначе узел, убранный из таблицы,
|
|
37
|
+
// остался бы скрытым её прежней вместимостью навсегда.
|
|
38
|
+
const next = new Set(views);
|
|
39
|
+
for (const previous of this.itemViews) {
|
|
40
|
+
if (previous instanceof NodeView && !next.has(previous)) {
|
|
41
|
+
previous.hiddenByOwner = false;
|
|
42
|
+
previous.visible = previous.documentVisible;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
this.itemViews = [...views];
|
|
46
|
+
this.applyVisibility();
|
|
47
|
+
}
|
|
48
|
+
/** Элементы в авторском порядке; нерезолвенные ссылки пропущены. */
|
|
49
|
+
get items() {
|
|
50
|
+
return this.itemViews.filter((item) => item !== undefined);
|
|
51
|
+
}
|
|
52
|
+
get visibleCount() {
|
|
53
|
+
return this.runtimeVisibleCount ?? this.authoredVisibleCount;
|
|
54
|
+
}
|
|
55
|
+
/** Транзиентно меняет вместимость; `undefined` возвращает авторское значение профиля. */
|
|
56
|
+
set visibleCount(value) {
|
|
57
|
+
this.runtimeVisibleCount = value === undefined ? undefined : Math.max(0, Math.round(value));
|
|
58
|
+
this.applyVisibility();
|
|
59
|
+
}
|
|
60
|
+
/** Элементы, показанные прямо сейчас, в авторском порядке. */
|
|
61
|
+
get visibleItems() {
|
|
62
|
+
return this.items.filter((item) => item.visible);
|
|
63
|
+
}
|
|
64
|
+
/** Индекс — позиция в авторском списке, а не среди показанных: адресация не «плавает». */
|
|
65
|
+
itemAt(index) {
|
|
66
|
+
return this.itemViews[index];
|
|
67
|
+
}
|
|
68
|
+
/** Показывает или прячет все элементы разом; `undefined` возвращает управление вместимости. */
|
|
69
|
+
setItemsVisible(visible) {
|
|
70
|
+
this.forcedItemsVisible = visible;
|
|
71
|
+
this.applyVisibility();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Проход по элементам: влезающие включены, остальные выключены. Помимо самого `visible` ставится
|
|
75
|
+
* `hiddenByOwner`, потому что `visible` узла пересчитывается из документа на каждом обновлении —
|
|
76
|
+
* в том числе из колбэка layout-движка, который для элемента внутри layout-группы срабатывает
|
|
77
|
+
* позже прохода таблицы и без флага вернул бы скрытый элемент обратно.
|
|
78
|
+
*/
|
|
79
|
+
applyVisibility() {
|
|
80
|
+
const count = this.visibleCount;
|
|
81
|
+
this.itemViews.forEach((item, index) => {
|
|
82
|
+
if (item === undefined)
|
|
83
|
+
return;
|
|
84
|
+
const enabled = this.forcedItemsVisible ?? index < count;
|
|
85
|
+
if (item instanceof NodeView) {
|
|
86
|
+
item.hiddenByOwner = !enabled;
|
|
87
|
+
item.visible = enabled && item.documentVisible;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
item.visible = enabled;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
syncContent(node, _transform, profile) {
|
|
94
|
+
if (node.type !== "item-table")
|
|
95
|
+
return;
|
|
96
|
+
this.authoredVisibleCount = resolveItemTableVisibleCount(node, profile);
|
|
97
|
+
this.applyVisibility();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=ItemTableNodeView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ItemTableNodeView.js","sourceRoot":"","sources":["../../src/views/ItemTableNodeView.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveItemTableVisibleCount, type LayoutProfileId, type UINode } from \"@pixi-ui-editor/schema\";\r\nimport { Container } from \"pixi.js\";\r\nimport { NodeView } from \"./NodeView.js\";\r\n\r\n/**\r\n * Узел фиксированной вместимости: держит авторский список узлов (`items`, ссылки по адресу — их\r\n * разрешает `scene.ts`, тем же приёмом, что цели spine-коннекторов) и показывает только первые\r\n * `visibleCount` из них — остальные скрыты всегда, а не «на этой странице». Автор раскладывает\r\n * элементы под максимум, а сколько их реально помещается, задаётся на профиль: в разной ориентации\r\n * экрана на экран влезает разное число строк.\r\n *\r\n * Список именно авторский, а не дети: элемент обычно уже лежит там, где его выравнивает layout-группа.\r\n *\r\n * Узел намеренно ничего больше не делает. Он не страницы (`PageGroupNodeView` показывает ровно\r\n * одного ребёнка и растягивает его на себя), не раскладка (позицию элементы сохраняют свою) и ничего не\r\n * знает ни про какие данные: чем наполнить видимые элементы и когда, решает игровой код через\r\n * generic API из `controls.ts` (`getItemTableViewVisibleItems`, `getItemTableViewItem`,\r\n * `setItemTableViewVisibleCount`, `setItemTableViewItemsVisible`). Связка «переключили страницу →\r\n * перезаполнили элементы» тоже принадлежит игре: `PaginationNodeView.onPageChange` и эта таблица\r\n * ничего друг о друге не знают.\r\n *\r\n * Видимость складывается с авторской: элемент, скрытый в документе, не станет видимым из-за\r\n * вместимости, а скрытый вместимостью держится флагом `NodeView.hiddenByOwner` (см. `applyVisibility`).\r\n */\r\nexport class ItemTableNodeView extends NodeView {\r\n private itemViews: (Container | undefined)[] = [];\r\n private authoredVisibleCount = 0;\r\n /** Транзиентное переопределение от игрового кода; `undefined` — авторское значение профиля. */\r\n private runtimeVisibleCount?: number;\r\n /** Транзиентный «скрыть/показать всё» поверх вместимости; `undefined` — вместимость решает сама. */\r\n private forcedItemsVisible?: boolean;\r\n\r\n /**\r\n * Разрешённые views авторского списка `items`, позиционно выровненные с ним (`scene.ts`). Ссылка,\r\n * которая ни во что не разрешилась, сохраняет свою дырку `undefined`, чтобы остальные элементы не\r\n * сдвинулись; сама висячая ссылка — ошибка валидации документа, а не забота этого вида.\r\n */\r\n setItems(views: readonly (Container | undefined)[]): void {\r\n // Выбывшие из списка возвращаются под управление документа: иначе узел, убранный из таблицы,\r\n // остался бы скрытым её прежней вместимостью навсегда.\r\n const next = new Set(views);\r\n for (const previous of this.itemViews) {\r\n if (previous instanceof NodeView && !next.has(previous)) { previous.hiddenByOwner = false; previous.visible = previous.documentVisible; }\r\n }\r\n this.itemViews = [...views];\r\n this.applyVisibility();\r\n }\r\n\r\n /** Элементы в авторском порядке; нерезолвенные ссылки пропущены. */\r\n get items(): Container[] {\r\n return this.itemViews.filter((item): item is Container => item !== undefined);\r\n }\r\n\r\n get visibleCount(): number {\r\n return this.runtimeVisibleCount ?? this.authoredVisibleCount;\r\n }\r\n\r\n /** Транзиентно меняет вместимость; `undefined` возвращает авторское значение профиля. */\r\n set visibleCount(value: number | undefined) {\r\n this.runtimeVisibleCount = value === undefined ? undefined : Math.max(0, Math.round(value));\r\n this.applyVisibility();\r\n }\r\n\r\n /** Элементы, показанные прямо сейчас, в авторском порядке. */\r\n get visibleItems(): Container[] {\r\n return this.items.filter((item) => item.visible);\r\n }\r\n\r\n /** Индекс — позиция в авторском списке, а не среди показанных: адресация не «плавает». */\r\n itemAt(index: number): Container | undefined {\r\n return this.itemViews[index];\r\n }\r\n\r\n /** Показывает или прячет все элементы разом; `undefined` возвращает управление вместимости. */\r\n setItemsVisible(visible: boolean | undefined): void {\r\n this.forcedItemsVisible = visible;\r\n this.applyVisibility();\r\n }\r\n\r\n /**\r\n * Проход по элементам: влезающие включены, остальные выключены. Помимо самого `visible` ставится\r\n * `hiddenByOwner`, потому что `visible` узла пересчитывается из документа на каждом обновлении —\r\n * в том числе из колбэка layout-движка, который для элемента внутри layout-группы срабатывает\r\n * позже прохода таблицы и без флага вернул бы скрытый элемент обратно.\r\n */\r\n private applyVisibility(): void {\r\n const count = this.visibleCount;\r\n this.itemViews.forEach((item, index) => {\r\n if (item === undefined) return;\r\n const enabled = this.forcedItemsVisible ?? index < count;\r\n if (item instanceof NodeView) {\r\n item.hiddenByOwner = !enabled;\r\n item.visible = enabled && item.documentVisible;\r\n return;\r\n }\r\n item.visible = enabled;\r\n });\r\n }\r\n\r\n protected syncContent(node: UINode, _transform: UINode[\"transform\"], profile: LayoutProfileId): void {\r\n if (node.type !== \"item-table\") return;\r\n this.authoredVisibleCount = resolveItemTableVisibleCount(node, profile);\r\n this.applyVisibility();\r\n }\r\n}\r\n"]}
|
package/dist/views/NodeView.d.ts
CHANGED
|
@@ -22,6 +22,18 @@ export declare abstract class NodeView extends Container {
|
|
|
22
22
|
/** Authored/computed RectTransform-like rectangle; unlike Pixi bounds it also exists without content. */
|
|
23
23
|
protected layoutWidth: number;
|
|
24
24
|
protected layoutHeight: number;
|
|
25
|
+
/**
|
|
26
|
+
* Скрыт ли узел владельцем-вместимостью (`ItemTableNodeView`) поверх авторской видимости.
|
|
27
|
+
*
|
|
28
|
+
* Флаг живёт здесь, а не только в самом владельце, потому что `visible` заново выводится из
|
|
29
|
+
* документа на каждом обновлении — в том числе в `updateManaged`, который для элемента внутри
|
|
30
|
+
* layout-группы дёргает сам layout-движок своим колбэком `onLayout` (см. `LayoutItemContainer`),
|
|
31
|
+
* то есть уже после того, как владелец закончил свой проход. Одно присваивание `visible` снаружи
|
|
32
|
+
* пережило бы ровно до следующего пересчёта раскладки; сложение с этим флагом переживает любой.
|
|
33
|
+
*/
|
|
34
|
+
hiddenByOwner: boolean;
|
|
35
|
+
/** Visibility last resolved from the document, before `hiddenByOwner` is applied on top. */
|
|
36
|
+
private authoredVisible;
|
|
25
37
|
constructor(textures?: ReadonlyMap<string, Texture>);
|
|
26
38
|
protected textureFor(assetId: string | undefined): Texture | undefined;
|
|
27
39
|
/**
|
|
@@ -49,6 +61,8 @@ export declare abstract class NodeView extends Container {
|
|
|
49
61
|
/** Applies the rectangle owned by a direct parent's layout solver. */
|
|
50
62
|
updateManaged(node: UINode, profile: LayoutProfileId, size: LayoutSize): void;
|
|
51
63
|
get layoutRectangle(): LayoutSize;
|
|
64
|
+
/** Авторская видимость узла отдельно от `visible`, поверх которой применяется `hiddenByOwner`. */
|
|
65
|
+
get documentVisible(): boolean;
|
|
52
66
|
getSpine(): Spine | undefined;
|
|
53
67
|
/**
|
|
54
68
|
* Rasterizes canvas-based content (`TextNodeView`) at a higher pixel density than the renderer's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeView.d.ts","sourceRoot":"","sources":["../../src/views/NodeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAa,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAqD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAElG;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,SAAS,CAAC;AAE3D;;;;GAIG;AACH,8BAAsB,QAAS,SAAQ,SAAS;IAC9C,SAAS,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC9B,4FAA4F;IAC5F,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,yGAAyG;IACzG,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,YAAY,SAAK;
|
|
1
|
+
{"version":3,"file":"NodeView.d.ts","sourceRoot":"","sources":["../../src/views/NodeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAa,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAqD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAElG;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,SAAS,CAAC;AAE3D;;;;GAIG;AACH,8BAAsB,QAAS,SAAQ,SAAS;IAC9C,SAAS,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC9B,4FAA4F;IAC5F,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,yGAAyG;IACzG,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,YAAY,SAAK;IAC3B;;;;;;;;OAQG;IACH,aAAa,UAAS;IACtB,4FAA4F;IAC5F,OAAO,CAAC,eAAe,CAAQ;IAE/B,YAAY,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EAGlD;IAED,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAErE;IAED;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAEvC;IAED,mGAAmG;IACnG,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAI7C;IAED,kGAAkG;IAClG,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAE7G,OAAO,CAAC,sBAAsB;IAwB9B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAG5E;IAED,mGAAmG;IACnG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,UAAO,EAAE,OAAO,GAAE,eAA2B,GAAG,IAAI,CAEhH;IAED,sEAAsE;IACtE,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAG5E;IAED,IAAI,eAAe,IAAI,UAAU,CAEhC;IAED,kGAAkG;IAClG,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,QAAQ,IAAI,KAAK,GAAG,SAAS,CAE5B;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAG;CACnD"}
|
package/dist/views/NodeView.js
CHANGED
|
@@ -15,6 +15,18 @@ export class NodeView extends Container {
|
|
|
15
15
|
/** Authored/computed RectTransform-like rectangle; unlike Pixi bounds it also exists without content. */
|
|
16
16
|
layoutWidth = 0;
|
|
17
17
|
layoutHeight = 0;
|
|
18
|
+
/**
|
|
19
|
+
* Скрыт ли узел владельцем-вместимостью (`ItemTableNodeView`) поверх авторской видимости.
|
|
20
|
+
*
|
|
21
|
+
* Флаг живёт здесь, а не только в самом владельце, потому что `visible` заново выводится из
|
|
22
|
+
* документа на каждом обновлении — в том числе в `updateManaged`, который для элемента внутри
|
|
23
|
+
* layout-группы дёргает сам layout-движок своим колбэком `onLayout` (см. `LayoutItemContainer`),
|
|
24
|
+
* то есть уже после того, как владелец закончил свой проход. Одно присваивание `visible` снаружи
|
|
25
|
+
* пережило бы ровно до следующего пересчёта раскладки; сложение с этим флагом переживает любой.
|
|
26
|
+
*/
|
|
27
|
+
hiddenByOwner = false;
|
|
28
|
+
/** Visibility last resolved from the document, before `hiddenByOwner` is applied on top. */
|
|
29
|
+
authoredVisible = true;
|
|
18
30
|
constructor(textures) {
|
|
19
31
|
super();
|
|
20
32
|
this.textures = textures;
|
|
@@ -59,7 +71,8 @@ export class NodeView extends Container {
|
|
|
59
71
|
this.position.set(managedByLayout ? pivotX : transform.x, managedByLayout ? pivotY : transform.y);
|
|
60
72
|
this.rotation = transform.rotation;
|
|
61
73
|
this.scale.set(transform.scaleX, transform.scaleY);
|
|
62
|
-
this.
|
|
74
|
+
this.authoredVisible = visible;
|
|
75
|
+
this.visible = visible && !this.hiddenByOwner;
|
|
63
76
|
this.layoutWidth = transform.width;
|
|
64
77
|
this.layoutHeight = transform.height;
|
|
65
78
|
// Тот же прямоугольник, что видит пользователь: grab-зона не зависит от содержимого ноды.
|
|
@@ -84,6 +97,10 @@ export class NodeView extends Container {
|
|
|
84
97
|
get layoutRectangle() {
|
|
85
98
|
return { width: this.layoutWidth, height: this.layoutHeight };
|
|
86
99
|
}
|
|
100
|
+
/** Авторская видимость узла отдельно от `visible`, поверх которой применяется `hiddenByOwner`. */
|
|
101
|
+
get documentVisible() {
|
|
102
|
+
return this.authoredVisible;
|
|
103
|
+
}
|
|
87
104
|
getSpine() {
|
|
88
105
|
return this.content instanceof Spine ? this.content : undefined;
|
|
89
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeView.js","sourceRoot":"","sources":["../../src/views/NodeView.ts"],"names":[],"mappings":"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","sourcesContent":["import { Spine } from \"@esotericsoftware/spine-pixi-v8\";\r\nimport { type LayoutProfileId, type UINode } from \"@pixi-ui-editor/schema\";\r\nimport { Container, Rectangle, Texture, type PointData } from \"pixi.js\";\r\nimport { resolveAnchoredTransform, resolveProfileTransform, type LayoutSize } from \"../layout.js\";\r\n\r\n/**\r\n * Whether a built scene is an inert authoring surface or a live one.\r\n * The editor canvas builds `authoring` scenes so controls never swallow selection and drag\r\n * gestures; Preview and the consuming app build `runtime` scenes with real pointer handling.\r\n */\r\nexport type SceneInteractionMode = \"authoring\" | \"runtime\";\r\n\r\n/**\r\n * Base display object for every schema node type. The whole layout contract — profile overrides,\r\n * anchors, pivot, position, scale, visibility — lives here once; subclasses only synchronize their\r\n * type-specific content against the resolved layout rectangle.\r\n */\r\nexport abstract class NodeView extends Container {\r\n protected content?: Container;\r\n /** A mutable scene texture map: assets may be loaded after this stable view was created. */\r\n protected readonly textures: ReadonlyMap<string, Texture> | undefined;\r\n /** Node's own grab rectangle in local space, kept equal to its resolved layout rectangle. */\r\n private readonly grabRect = new Rectangle();\r\n /** Authored/computed RectTransform-like rectangle; unlike Pixi bounds it also exists without content. */\r\n protected layoutWidth = 0;\r\n protected layoutHeight = 0;\r\n\r\n constructor(textures?: ReadonlyMap<string, Texture>) {\r\n super();\r\n this.textures = textures;\r\n }\r\n\r\n protected textureFor(assetId: string | undefined): Texture | undefined {\r\n return assetId === undefined ? undefined : this.textures?.get(assetId);\r\n }\r\n\r\n /**\r\n * Makes every node selectable and draggable by its layout rectangle, whatever it renders.\r\n *\r\n * Lives here rather than in a subclass because a bare `Container` has no `containsPoint` of its\r\n * own: without this, Pixi could only find a node through whatever content the subclass happens to\r\n * draw, so nodes that draw nothing (container) or keep their content out of hit testing (button,\r\n * whose `@pixi/ui` view is inert while authoring) would silently stop being selectable.\r\n *\r\n * Deliberately `containsPoint` and NOT `hitArea`: Pixi's `hitPruneFn` drops a container together\r\n * with its whole subtree once the point falls outside its `hitArea`, which would make any child\r\n * reaching past its parent's rectangle visible but unclickable. `containsPoint` never clips\r\n * children — they are hit-tested first and independently, so a child always stays grabbable.\r\n */\r\n containsPoint(point: PointData): boolean {\r\n return this.grabRect.contains(point.x, point.y);\r\n }\r\n\r\n /** Заменяет содержимое ноды: прежнее снимается со сцены и уничтожается, чтобы не копить сироты. */\r\n protected setContent(content: Container): void {\r\n if (this.content !== undefined) { this.removeChild(this.content); this.content.destroy(); }\r\n this.content = content;\r\n this.addChild(content);\r\n }\r\n\r\n /** Syncs type-specific content to the resolved layout rectangle; layout math itself is shared. */\r\n protected abstract syncContent(node: UINode, transform: UINode[\"transform\"], profile: LayoutProfileId): void;\r\n\r\n private applyResolvedTransform(node: UINode, transform: UINode[\"transform\"], visible: boolean, managedByLayout: boolean, profile: LayoutProfileId): void {\r\n this.syncContent(node, transform, profile);\r\n\r\n const pivotX = (transform.pivotX ?? 0) * transform.width;\r\n const pivotY = (transform.pivotY ?? 0) * transform.height;\r\n this.pivot.set(pivotX, pivotY);\r\n // x/y are the pivot offset from the resolved anchor. Thus a centred anchor and pivot at\r\n // zero offsets place the visual centre exactly at the parent's centre.\r\n // A managed child is positioned by its technical Yoga item. Its own NodeView keeps only the\r\n // pivot offset inside that computed rectangle; authored x/y and anchors do not participate.\r\n this.position.set(managedByLayout ? pivotX : transform.x, managedByLayout ? pivotY : transform.y);\r\n this.rotation = transform.rotation;\r\n this.scale.set(transform.scaleX, transform.scaleY);\r\n this.visible = visible;\r\n this.layoutWidth = transform.width;\r\n this.layoutHeight = transform.height;\r\n // Тот же прямоугольник, что видит пользователь: grab-зона не зависит от содержимого ноды.\r\n this.grabRect.x = 0;\r\n this.grabRect.y = 0;\r\n this.grabRect.width = transform.width;\r\n this.grabRect.height = transform.height;\r\n }\r\n\r\n update(node: UINode, profile: LayoutProfileId, parentSize?: LayoutSize): void {\r\n const resolved = resolveProfileTransform(node, profile);\r\n this.applyResolvedTransform(node, resolveAnchoredTransform(resolved.transform, parentSize), resolved.visible, false, profile);\r\n }\r\n\r\n /** Applies a transient editor rectangle without deriving it from content-dependent Pixi bounds. */\r\n preview(node: UINode, transform: UINode[\"transform\"], visible = true, profile: LayoutProfileId = \"desktop\"): void {\r\n this.applyResolvedTransform(node, transform, visible, false, profile);\r\n }\r\n\r\n /** Applies the rectangle owned by a direct parent's layout solver. */\r\n updateManaged(node: UINode, profile: LayoutProfileId, size: LayoutSize): void {\r\n const resolved = resolveProfileTransform(node, profile);\r\n this.applyResolvedTransform(node, { ...resolved.transform, x: 0, y: 0, width: size.width, height: size.height }, resolved.visible, true, profile);\r\n }\r\n\r\n get layoutRectangle(): LayoutSize {\r\n return { width: this.layoutWidth, height: this.layoutHeight };\r\n }\r\n\r\n getSpine(): Spine | undefined {\r\n return this.content instanceof Spine ? this.content : undefined;\r\n }\r\n\r\n /**\r\n * Rasterizes canvas-based content (`TextNodeView`) at a higher pixel density than the renderer's\r\n * own backing resolution, so an authoring camera zoomed in doesn't magnify an already-rasterized\r\n * bitmap past its source resolution and read as blurred. A no-op base implementation: sprite/\r\n * graphics/bitmap-text content is either GPU-native (always crisp under zoom) or, for bitmap-text,\r\n * has its resolution fixed by the installed `BitmapFont` and can't be changed per instance.\r\n */\r\n setContentResolution(_resolution: number): void {}\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"NodeView.js","sourceRoot":"","sources":["../../src/views/NodeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,SAAS,EAA2B,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAmB,MAAM,cAAc,CAAC;AASlG;;;;GAIG;AACH,MAAM,OAAgB,QAAS,SAAQ,SAAS;IACpC,OAAO,CAAa;IAC9B,4FAA4F;IACzE,QAAQ,CAA2C;IACtE,6FAA6F;IAC5E,QAAQ,GAAG,IAAI,SAAS,EAAE,CAAC;IAC5C,yGAAyG;IAC/F,WAAW,GAAG,CAAC,CAAC;IAChB,YAAY,GAAG,CAAC,CAAC;IAC3B;;;;;;;;OAQG;IACH,aAAa,GAAG,KAAK,CAAC;IACtB,4FAA4F;IACpF,eAAe,GAAG,IAAI,CAAC;IAE/B,YAAY,QAAuC;QACjD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAES,UAAU,CAAC,OAA2B;QAC9C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACzE,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,KAAgB;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,mGAAmG;IACzF,UAAU,CAAC,OAAkB;QACrC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAAC,CAAC;QAC3F,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAKO,sBAAsB,CAAC,IAAY,EAAE,SAA8B,EAAE,OAAgB,EAAE,eAAwB,EAAE,OAAwB;QAC/I,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAE3C,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC;QACzD,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC;QAC1D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,yFAAyF;QACzF,uEAAuE;QACvE,4FAA4F;QAC5F,4FAA4F;QAC5F,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClG,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;QAC9C,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC;QACrC,0FAA0F;QAC1F,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC1C,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,OAAwB,EAAE,UAAuB;QACpE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,wBAAwB,CAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAChI,CAAC;IAED,mGAAmG;IACnG,OAAO,CAAC,IAAY,EAAE,SAA8B,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,GAAoB,SAAS;QACxG,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,sEAAsE;IACtE,aAAa,CAAC,IAAY,EAAE,OAAwB,EAAE,IAAgB;QACpE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpJ,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;IAChE,CAAC;IAED,kGAAkG;IAClG,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAClE,CAAC;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,WAAmB,IAAS,CAAC;CACnD","sourcesContent":["import { Spine } from \"@esotericsoftware/spine-pixi-v8\";\r\nimport { type LayoutProfileId, type UINode } from \"@pixi-ui-editor/schema\";\r\nimport { Container, Rectangle, Texture, type PointData } from \"pixi.js\";\r\nimport { resolveAnchoredTransform, resolveProfileTransform, type LayoutSize } from \"../layout.js\";\r\n\r\n/**\r\n * Whether a built scene is an inert authoring surface or a live one.\r\n * The editor canvas builds `authoring` scenes so controls never swallow selection and drag\r\n * gestures; Preview and the consuming app build `runtime` scenes with real pointer handling.\r\n */\r\nexport type SceneInteractionMode = \"authoring\" | \"runtime\";\r\n\r\n/**\r\n * Base display object for every schema node type. The whole layout contract — profile overrides,\r\n * anchors, pivot, position, scale, visibility — lives here once; subclasses only synchronize their\r\n * type-specific content against the resolved layout rectangle.\r\n */\r\nexport abstract class NodeView extends Container {\r\n protected content?: Container;\r\n /** A mutable scene texture map: assets may be loaded after this stable view was created. */\r\n protected readonly textures: ReadonlyMap<string, Texture> | undefined;\r\n /** Node's own grab rectangle in local space, kept equal to its resolved layout rectangle. */\r\n private readonly grabRect = new Rectangle();\r\n /** Authored/computed RectTransform-like rectangle; unlike Pixi bounds it also exists without content. */\r\n protected layoutWidth = 0;\r\n protected layoutHeight = 0;\r\n /**\r\n * Скрыт ли узел владельцем-вместимостью (`ItemTableNodeView`) поверх авторской видимости.\r\n *\r\n * Флаг живёт здесь, а не только в самом владельце, потому что `visible` заново выводится из\r\n * документа на каждом обновлении — в том числе в `updateManaged`, который для элемента внутри\r\n * layout-группы дёргает сам layout-движок своим колбэком `onLayout` (см. `LayoutItemContainer`),\r\n * то есть уже после того, как владелец закончил свой проход. Одно присваивание `visible` снаружи\r\n * пережило бы ровно до следующего пересчёта раскладки; сложение с этим флагом переживает любой.\r\n */\r\n hiddenByOwner = false;\r\n /** Visibility last resolved from the document, before `hiddenByOwner` is applied on top. */\r\n private authoredVisible = true;\r\n\r\n constructor(textures?: ReadonlyMap<string, Texture>) {\r\n super();\r\n this.textures = textures;\r\n }\r\n\r\n protected textureFor(assetId: string | undefined): Texture | undefined {\r\n return assetId === undefined ? undefined : this.textures?.get(assetId);\r\n }\r\n\r\n /**\r\n * Makes every node selectable and draggable by its layout rectangle, whatever it renders.\r\n *\r\n * Lives here rather than in a subclass because a bare `Container` has no `containsPoint` of its\r\n * own: without this, Pixi could only find a node through whatever content the subclass happens to\r\n * draw, so nodes that draw nothing (container) or keep their content out of hit testing (button,\r\n * whose `@pixi/ui` view is inert while authoring) would silently stop being selectable.\r\n *\r\n * Deliberately `containsPoint` and NOT `hitArea`: Pixi's `hitPruneFn` drops a container together\r\n * with its whole subtree once the point falls outside its `hitArea`, which would make any child\r\n * reaching past its parent's rectangle visible but unclickable. `containsPoint` never clips\r\n * children — they are hit-tested first and independently, so a child always stays grabbable.\r\n */\r\n containsPoint(point: PointData): boolean {\r\n return this.grabRect.contains(point.x, point.y);\r\n }\r\n\r\n /** Заменяет содержимое ноды: прежнее снимается со сцены и уничтожается, чтобы не копить сироты. */\r\n protected setContent(content: Container): void {\r\n if (this.content !== undefined) { this.removeChild(this.content); this.content.destroy(); }\r\n this.content = content;\r\n this.addChild(content);\r\n }\r\n\r\n /** Syncs type-specific content to the resolved layout rectangle; layout math itself is shared. */\r\n protected abstract syncContent(node: UINode, transform: UINode[\"transform\"], profile: LayoutProfileId): void;\r\n\r\n private applyResolvedTransform(node: UINode, transform: UINode[\"transform\"], visible: boolean, managedByLayout: boolean, profile: LayoutProfileId): void {\r\n this.syncContent(node, transform, profile);\r\n\r\n const pivotX = (transform.pivotX ?? 0) * transform.width;\r\n const pivotY = (transform.pivotY ?? 0) * transform.height;\r\n this.pivot.set(pivotX, pivotY);\r\n // x/y are the pivot offset from the resolved anchor. Thus a centred anchor and pivot at\r\n // zero offsets place the visual centre exactly at the parent's centre.\r\n // A managed child is positioned by its technical Yoga item. Its own NodeView keeps only the\r\n // pivot offset inside that computed rectangle; authored x/y and anchors do not participate.\r\n this.position.set(managedByLayout ? pivotX : transform.x, managedByLayout ? pivotY : transform.y);\r\n this.rotation = transform.rotation;\r\n this.scale.set(transform.scaleX, transform.scaleY);\r\n this.authoredVisible = visible;\r\n this.visible = visible && !this.hiddenByOwner;\r\n this.layoutWidth = transform.width;\r\n this.layoutHeight = transform.height;\r\n // Тот же прямоугольник, что видит пользователь: grab-зона не зависит от содержимого ноды.\r\n this.grabRect.x = 0;\r\n this.grabRect.y = 0;\r\n this.grabRect.width = transform.width;\r\n this.grabRect.height = transform.height;\r\n }\r\n\r\n update(node: UINode, profile: LayoutProfileId, parentSize?: LayoutSize): void {\r\n const resolved = resolveProfileTransform(node, profile);\r\n this.applyResolvedTransform(node, resolveAnchoredTransform(resolved.transform, parentSize), resolved.visible, false, profile);\r\n }\r\n\r\n /** Applies a transient editor rectangle without deriving it from content-dependent Pixi bounds. */\r\n preview(node: UINode, transform: UINode[\"transform\"], visible = true, profile: LayoutProfileId = \"desktop\"): void {\r\n this.applyResolvedTransform(node, transform, visible, false, profile);\r\n }\r\n\r\n /** Applies the rectangle owned by a direct parent's layout solver. */\r\n updateManaged(node: UINode, profile: LayoutProfileId, size: LayoutSize): void {\r\n const resolved = resolveProfileTransform(node, profile);\r\n this.applyResolvedTransform(node, { ...resolved.transform, x: 0, y: 0, width: size.width, height: size.height }, resolved.visible, true, profile);\r\n }\r\n\r\n get layoutRectangle(): LayoutSize {\r\n return { width: this.layoutWidth, height: this.layoutHeight };\r\n }\r\n\r\n /** Авторская видимость узла отдельно от `visible`, поверх которой применяется `hiddenByOwner`. */\r\n get documentVisible(): boolean {\r\n return this.authoredVisible;\r\n }\r\n\r\n getSpine(): Spine | undefined {\r\n return this.content instanceof Spine ? this.content : undefined;\r\n }\r\n\r\n /**\r\n * Rasterizes canvas-based content (`TextNodeView`) at a higher pixel density than the renderer's\r\n * own backing resolution, so an authoring camera zoomed in doesn't magnify an already-rasterized\r\n * bitmap past its source resolution and read as blurred. A no-op base implementation: sprite/\r\n * graphics/bitmap-text content is either GPU-native (always crisp under zoom) or, for bitmap-text,\r\n * has its resolution fixed by the installed `BitmapFont` and can't be changed per instance.\r\n */\r\n setContentResolution(_resolution: number): void {}\r\n}\r\n"]}
|
|
@@ -3,6 +3,17 @@ import { Texture } from "pixi.js";
|
|
|
3
3
|
import { Signal } from "typed-signals";
|
|
4
4
|
import { NodeView, type SceneInteractionMode } from "./NodeView.js";
|
|
5
5
|
export type { PaginationNode };
|
|
6
|
+
/**
|
|
7
|
+
* Кнопка-стрелка пагинатора: ровно та часть `ButtonNodeView`/`StagedButtonNodeView`, которая нужна
|
|
8
|
+
* виджету, — нажатие и «доступна ли». Тип объявлен структурно, чтобы этот файл не зависел от обоих
|
|
9
|
+
* классов кнопок и не решал, какой из них «настоящий».
|
|
10
|
+
*/
|
|
11
|
+
export type PaginationArrow = {
|
|
12
|
+
onPress: {
|
|
13
|
+
connect(handler: () => void): unknown;
|
|
14
|
+
};
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
};
|
|
6
17
|
/**
|
|
7
18
|
* Standalone page indicator: N procedurally generated items arranged by `@pixi/ui`'s `List` (the same
|
|
8
19
|
* approach `ScrollViewNodeView`/`scrollView.ts` use for arranging children) — this view never
|
|
@@ -18,12 +29,22 @@ export type { PaginationNode };
|
|
|
18
29
|
* item count/footprint is a runtime fact about this control, not scene topology — it only rebuilds this
|
|
19
30
|
* view's own item children, never the owning `NodeView` or the wider scene tree. The active pill itself
|
|
20
31
|
* is not structural: switching pages only recreates the (one) background view that needs to move.
|
|
32
|
+
*
|
|
33
|
+
* With `visiblePageCount` set, the indicator becomes a fixed strip of that many slots with the active
|
|
34
|
+
* page always in the middle one (4 pages, page 3 active → 2-3-4). A slot pointing outside the real
|
|
35
|
+
* page range keeps its footprint and renders nothing, so the active page never shifts sideways.
|
|
36
|
+
*
|
|
37
|
+
* The prev/next arrows are ordinary authored button nodes placed wherever the layout needs them; the
|
|
38
|
+
* widget only takes their views (`scene.ts`'s `wireNodeReferences`, the same address → view resolution
|
|
39
|
+
* Spine connector targets use) and steps the page on press. It never draws an arrow itself.
|
|
21
40
|
*/
|
|
22
41
|
export declare class PaginationNodeView extends NodeView {
|
|
23
42
|
private readonly list;
|
|
24
43
|
private readonly interaction;
|
|
25
44
|
private readonly fonts;
|
|
26
45
|
private items;
|
|
46
|
+
private visibleSlots;
|
|
47
|
+
private arrows?;
|
|
27
48
|
private styleMode;
|
|
28
49
|
private activeTexture;
|
|
29
50
|
private inactiveTexture;
|
|
@@ -40,15 +61,30 @@ export declare class PaginationNodeView extends NodeView {
|
|
|
40
61
|
readonly onPageChange: Signal<(page: number) => void>;
|
|
41
62
|
constructor(node: PaginationNode, textures: ReadonlyMap<string, Texture> | undefined, interaction: SceneInteractionMode, fonts: ReadonlyMap<string, string> | undefined);
|
|
42
63
|
private createBackgroundView;
|
|
64
|
+
/** How many indicator slots are drawn: the fixed authored window, or one slot per real page. */
|
|
65
|
+
private get slotCount();
|
|
66
|
+
/** The page a slot points at. Windowed slots slide with the current page, which keeps its middle seat. */
|
|
67
|
+
private pageForSlot;
|
|
43
68
|
private createItem;
|
|
44
|
-
/** Recreates every item for the current `styleMode`/`
|
|
69
|
+
/** Recreates every item for the current `styleMode`/`slotCount`/`dotWidth`/`dotHeight`. */
|
|
45
70
|
private buildItems;
|
|
46
71
|
/** Authoring canvas stays inert like every other control; a clickable item is only live at runtime. */
|
|
47
72
|
private wireItem;
|
|
48
73
|
private applyCurrentPage;
|
|
74
|
+
/**
|
|
75
|
+
* Подключает авторские кнопки-стрелки: нажатие шагает на страницу назад/вперёд и эмитит
|
|
76
|
+
* `onPageChange`, как клик по самому пункту. Кнопки остаются обычными нодами сцены — виджет только
|
|
77
|
+
* подписывается на них и гасит ту, за которой страниц больше нет.
|
|
78
|
+
*/
|
|
79
|
+
setArrows(arrows: {
|
|
80
|
+
previous?: PaginationArrow;
|
|
81
|
+
next?: PaginationArrow;
|
|
82
|
+
}): void;
|
|
83
|
+
/** На краях диапазона соответствующая стрелка недоступна — она никуда не ведёт. */
|
|
84
|
+
private applyArrowState;
|
|
49
85
|
protected syncContent(node: UINode): void;
|
|
50
86
|
get pageCount(): number;
|
|
51
|
-
/**
|
|
87
|
+
/** Used by `setPaginationViewPageCount` once game code knows the real content length. */
|
|
52
88
|
set pageCount(value: number);
|
|
53
89
|
get page(): number;
|
|
54
90
|
/** External sync from game code (or editor preview): highlights an item without emitting `onPageChange`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaginationNodeView.d.ts","sourceRoot":"","sources":["../../src/views/PaginationNodeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAA4B,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAExH,OAAO,EAA4C,OAAO,EAAyB,MAAM,SAAS,CAAC;AACnG,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEpE,YAAY,EAAE,cAAc,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"PaginationNodeView.d.ts","sourceRoot":"","sources":["../../src/views/PaginationNodeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAA4B,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAExH,OAAO,EAA4C,OAAO,EAAyB,MAAM,SAAS,CAAC;AACnG,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEpE,YAAY,EAAE,cAAc,EAAE,CAAC;AAuB/B;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE;QAAE,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AA+CvG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;IAChE,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,MAAM,CAAC,CAAyD;IACxE,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,6BAA6B,CAAsB;IAC3D,OAAO,CAAC,qBAAqB,CAA4B;IACzD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAU;IAEhC,0GAA0G;IAC1G,QAAQ,CAAC,YAAY,gBAAqB,MAAM,KAAK,IAAI,EAAI;IAE7D,YAAY,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAoBtK;IAED,OAAO,CAAC,oBAAoB;IAM5B,gGAAgG;IAChG,OAAO,KAAK,SAAS,GAEpB;IAED,0GAA0G;IAC1G,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,UAAU;IAmBlB,2FAA2F;IAC3F,OAAO,CAAC,UAAU;IAYlB,uGAAuG;IACvG,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAmBxB;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,eAAe,CAAC;QAAC,IAAI,CAAC,EAAE,eAAe,CAAA;KAAE,GAAG,IAAI,CAK9E;IAED,mFAAmF;IACnF,OAAO,CAAC,eAAe;IAMvB,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA2BxC;IAED,IAAI,SAAS,IAAI,MAAM,CAAgC;IAEvD,yFAAyF;IACzF,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,EAQ1B;IAED,IAAI,IAAI,IAAI,MAAM,CAA6B;IAE/C,4GAA4G;IAC5G,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAKrB;IAED,iHAAiH;IACjH,OAAO,CAAC,OAAO;CAKhB"}
|
|
@@ -81,12 +81,22 @@ class NumberItem extends Container {
|
|
|
81
81
|
* item count/footprint is a runtime fact about this control, not scene topology — it only rebuilds this
|
|
82
82
|
* view's own item children, never the owning `NodeView` or the wider scene tree. The active pill itself
|
|
83
83
|
* is not structural: switching pages only recreates the (one) background view that needs to move.
|
|
84
|
+
*
|
|
85
|
+
* With `visiblePageCount` set, the indicator becomes a fixed strip of that many slots with the active
|
|
86
|
+
* page always in the middle one (4 pages, page 3 active → 2-3-4). A slot pointing outside the real
|
|
87
|
+
* page range keeps its footprint and renders nothing, so the active page never shifts sideways.
|
|
88
|
+
*
|
|
89
|
+
* The prev/next arrows are ordinary authored button nodes placed wherever the layout needs them; the
|
|
90
|
+
* widget only takes their views (`scene.ts`'s `wireNodeReferences`, the same address → view resolution
|
|
91
|
+
* Spine connector targets use) and steps the page on press. It never draws an arrow itself.
|
|
84
92
|
*/
|
|
85
93
|
export class PaginationNodeView extends NodeView {
|
|
86
94
|
list;
|
|
87
95
|
interaction;
|
|
88
96
|
fonts;
|
|
89
97
|
items = [];
|
|
98
|
+
visibleSlots;
|
|
99
|
+
arrows;
|
|
90
100
|
styleMode;
|
|
91
101
|
activeTexture;
|
|
92
102
|
inactiveTexture;
|
|
@@ -113,7 +123,8 @@ export class PaginationNodeView extends NodeView {
|
|
|
113
123
|
this.numberActiveBackgroundTexture = node.numberActiveBackgroundAssetId === undefined ? undefined : this.textureFor(node.numberActiveBackgroundAssetId);
|
|
114
124
|
this.numberActiveNineSlice = node.numberActiveNineSlice;
|
|
115
125
|
this.pageCountState = Math.max(1, Math.round(node.pageCount));
|
|
116
|
-
this.
|
|
126
|
+
this.visibleSlots = node.visiblePageCount;
|
|
127
|
+
this.currentPage = clampPage(node.defaultPage, this.pageCount);
|
|
117
128
|
this.dotWidth = node.dotSize.width;
|
|
118
129
|
this.dotHeight = node.dotSize.height;
|
|
119
130
|
this.clickableState = node.clickable;
|
|
@@ -126,57 +137,97 @@ export class PaginationNodeView extends NodeView {
|
|
|
126
137
|
? new Sprite(texture)
|
|
127
138
|
: new NineSliceSprite({ texture, leftWidth: this.numberActiveNineSlice.left, topHeight: this.numberActiveNineSlice.top, rightWidth: this.numberActiveNineSlice.right, bottomHeight: this.numberActiveNineSlice.bottom });
|
|
128
139
|
}
|
|
129
|
-
|
|
140
|
+
/** How many indicator slots are drawn: the fixed authored window, or one slot per real page. */
|
|
141
|
+
get slotCount() {
|
|
142
|
+
return this.visibleSlots === undefined ? this.pageCount : Math.max(1, Math.round(this.visibleSlots));
|
|
143
|
+
}
|
|
144
|
+
/** The page a slot points at. Windowed slots slide with the current page, which keeps its middle seat. */
|
|
145
|
+
pageForSlot(slot) {
|
|
146
|
+
return this.visibleSlots === undefined ? slot : this.currentPage - Math.floor(this.slotCount / 2) + slot;
|
|
147
|
+
}
|
|
148
|
+
createItem(slot) {
|
|
149
|
+
const page = this.pageForSlot(slot);
|
|
150
|
+
const exists = page >= 0 && page < this.pageCount;
|
|
151
|
+
const isActive = exists && page === this.currentPage;
|
|
130
152
|
if (this.styleMode === "dots") {
|
|
131
|
-
const dot = new Sprite(
|
|
153
|
+
const dot = new Sprite(isActive ? this.activeTexture : this.inactiveTexture);
|
|
132
154
|
dot.setSize(this.dotWidth, this.dotHeight);
|
|
155
|
+
// An out-of-range slot renders nothing but keeps its size, so `List` still reserves its place.
|
|
156
|
+
dot.visible = exists;
|
|
133
157
|
return dot;
|
|
134
158
|
}
|
|
135
|
-
const isActive = index === this.currentPage;
|
|
136
159
|
const baseFill = this.numberStyle?.fill ?? DEFAULT_NUMBER_STYLE.fill;
|
|
137
160
|
const fill = isActive ? this.numberActiveColor ?? baseFill : baseFill;
|
|
138
|
-
const item = new NumberItem(new Text({ text: String(
|
|
161
|
+
const item = new NumberItem(new Text({ text: exists ? String(page + 1) : "", style: numberTextStyle(this.numberStyle, fill, this.fonts) }));
|
|
139
162
|
item.resize(this.dotWidth, this.dotHeight);
|
|
140
163
|
if (isActive && this.numberActiveBackgroundTexture !== undefined)
|
|
141
164
|
item.setBackground(this.createBackgroundView(this.numberActiveBackgroundTexture));
|
|
142
165
|
return item;
|
|
143
166
|
}
|
|
144
|
-
/** Recreates every item for the current `styleMode`/`
|
|
167
|
+
/** Recreates every item for the current `styleMode`/`slotCount`/`dotWidth`/`dotHeight`. */
|
|
145
168
|
buildItems() {
|
|
146
169
|
this.list.removeChildren();
|
|
147
170
|
this.items = [];
|
|
148
|
-
for (let
|
|
149
|
-
const item = this.createItem(
|
|
150
|
-
this.wireItem(item,
|
|
171
|
+
for (let slot = 0; slot < this.slotCount; slot++) {
|
|
172
|
+
const item = this.createItem(slot);
|
|
173
|
+
this.wireItem(item, slot);
|
|
151
174
|
this.items.push(item);
|
|
152
175
|
this.list.addChild(item);
|
|
153
176
|
}
|
|
177
|
+
this.applyArrowState();
|
|
154
178
|
}
|
|
155
179
|
/** Authoring canvas stays inert like every other control; a clickable item is only live at runtime. */
|
|
156
|
-
wireItem(item,
|
|
180
|
+
wireItem(item, slot) {
|
|
157
181
|
if (this.interaction === "authoring" || !this.clickableState) {
|
|
158
182
|
item.eventMode = "none";
|
|
159
183
|
return;
|
|
160
184
|
}
|
|
161
185
|
item.eventMode = "static";
|
|
162
186
|
item.cursor = "pointer";
|
|
163
|
-
|
|
187
|
+
// Resolved on click, not captured: a windowed slot points at a different page after every move.
|
|
188
|
+
item.on("pointertap", () => {
|
|
189
|
+
const page = this.pageForSlot(slot);
|
|
190
|
+
if (page >= 0 && page < this.pageCount)
|
|
191
|
+
this.setPage(page);
|
|
192
|
+
});
|
|
164
193
|
}
|
|
165
194
|
applyCurrentPage() {
|
|
166
|
-
if (this.styleMode === "dots") {
|
|
167
|
-
this.items.forEach((item, index) => { if (item instanceof Sprite)
|
|
168
|
-
item.texture = index === this.currentPage ? this.activeTexture : this.inactiveTexture; });
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
195
|
const baseFill = this.numberStyle?.fill ?? DEFAULT_NUMBER_STYLE.fill;
|
|
172
|
-
this.items.forEach((item,
|
|
173
|
-
|
|
196
|
+
this.items.forEach((item, slot) => {
|
|
197
|
+
const page = this.pageForSlot(slot);
|
|
198
|
+
const exists = page >= 0 && page < this.pageCount;
|
|
199
|
+
const isActive = exists && page === this.currentPage;
|
|
200
|
+
if (item instanceof NumberItem) {
|
|
201
|
+
item.numberText.text = exists ? String(page + 1) : "";
|
|
202
|
+
item.numberText.style = numberTextStyle(this.numberStyle, isActive ? this.numberActiveColor ?? baseFill : baseFill, this.fonts);
|
|
203
|
+
item.setBackground(isActive && this.numberActiveBackgroundTexture !== undefined ? this.createBackgroundView(this.numberActiveBackgroundTexture) : undefined);
|
|
174
204
|
return;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
item.
|
|
178
|
-
item.
|
|
205
|
+
}
|
|
206
|
+
item.texture = isActive ? this.activeTexture : this.inactiveTexture;
|
|
207
|
+
item.setSize(this.dotWidth, this.dotHeight);
|
|
208
|
+
item.visible = exists;
|
|
179
209
|
});
|
|
210
|
+
this.applyArrowState();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Подключает авторские кнопки-стрелки: нажатие шагает на страницу назад/вперёд и эмитит
|
|
214
|
+
* `onPageChange`, как клик по самому пункту. Кнопки остаются обычными нодами сцены — виджет только
|
|
215
|
+
* подписывается на них и гасит ту, за которой страниц больше нет.
|
|
216
|
+
*/
|
|
217
|
+
setArrows(arrows) {
|
|
218
|
+
this.arrows = arrows;
|
|
219
|
+
arrows.previous?.onPress.connect(() => this.setPage(this.currentPage - 1));
|
|
220
|
+
arrows.next?.onPress.connect(() => this.setPage(this.currentPage + 1));
|
|
221
|
+
this.applyArrowState();
|
|
222
|
+
}
|
|
223
|
+
/** На краях диапазона соответствующая стрелка недоступна — она никуда не ведёт. */
|
|
224
|
+
applyArrowState() {
|
|
225
|
+
if (this.arrows === undefined)
|
|
226
|
+
return;
|
|
227
|
+
if (this.arrows.previous !== undefined)
|
|
228
|
+
this.arrows.previous.enabled = this.currentPage > 0;
|
|
229
|
+
if (this.arrows.next !== undefined)
|
|
230
|
+
this.arrows.next.enabled = this.currentPage < this.pageCount - 1;
|
|
180
231
|
}
|
|
181
232
|
syncContent(node) {
|
|
182
233
|
if (node.type !== "pagination")
|
|
@@ -185,7 +236,10 @@ export class PaginationNodeView extends NodeView {
|
|
|
185
236
|
const inactive = node.inactiveAssetId === undefined ? Texture.WHITE : this.textureFor(node.inactiveAssetId) ?? Texture.WHITE;
|
|
186
237
|
const pageCount = Math.max(1, Math.round(node.pageCount));
|
|
187
238
|
this.clickableState = node.clickable;
|
|
188
|
-
const
|
|
239
|
+
const previousSlotCount = this.slotCount;
|
|
240
|
+
this.visibleSlots = node.visiblePageCount;
|
|
241
|
+
this.pageCountState = pageCount;
|
|
242
|
+
const structureChanged = node.style !== this.styleMode || this.slotCount !== previousSlotCount || node.dotSize.width !== this.dotWidth || node.dotSize.height !== this.dotHeight;
|
|
189
243
|
this.styleMode = node.style;
|
|
190
244
|
this.activeTexture = active;
|
|
191
245
|
this.inactiveTexture = inactive;
|
|
@@ -193,10 +247,9 @@ export class PaginationNodeView extends NodeView {
|
|
|
193
247
|
this.numberActiveColor = node.numberActiveColor;
|
|
194
248
|
this.numberActiveBackgroundTexture = node.numberActiveBackgroundAssetId === undefined ? undefined : this.textureFor(node.numberActiveBackgroundAssetId);
|
|
195
249
|
this.numberActiveNineSlice = node.numberActiveNineSlice;
|
|
196
|
-
this.pageCountState = pageCount;
|
|
197
250
|
this.dotWidth = node.dotSize.width;
|
|
198
251
|
this.dotHeight = node.dotSize.height;
|
|
199
|
-
this.currentPage = clampPage(this.currentPage, pageCount);
|
|
252
|
+
this.currentPage = clampPage(this.currentPage, this.pageCount);
|
|
200
253
|
if (this.list.type !== node.direction)
|
|
201
254
|
this.list.type = node.direction;
|
|
202
255
|
if (this.list.elementsMargin !== node.spacing)
|
|
@@ -207,14 +260,18 @@ export class PaginationNodeView extends NodeView {
|
|
|
207
260
|
this.applyCurrentPage();
|
|
208
261
|
}
|
|
209
262
|
get pageCount() { return this.pageCountState; }
|
|
210
|
-
/**
|
|
263
|
+
/** Used by `setPaginationViewPageCount` once game code knows the real content length. */
|
|
211
264
|
set pageCount(value) {
|
|
212
265
|
const next = Math.max(1, Math.round(value));
|
|
213
266
|
if (next === this.pageCountState)
|
|
214
267
|
return;
|
|
268
|
+
const previousSlotCount = this.slotCount;
|
|
215
269
|
this.pageCountState = next;
|
|
216
|
-
this.currentPage = clampPage(this.currentPage,
|
|
217
|
-
this.
|
|
270
|
+
this.currentPage = clampPage(this.currentPage, this.pageCount);
|
|
271
|
+
if (this.slotCount === previousSlotCount)
|
|
272
|
+
this.applyCurrentPage();
|
|
273
|
+
else
|
|
274
|
+
this.buildItems();
|
|
218
275
|
}
|
|
219
276
|
get page() { return this.currentPage; }
|
|
220
277
|
/** External sync from game code (or editor preview): highlights an item without emitting `onPageChange`. */
|