lecodes-cli 0.1.5 → 0.1.7
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/index.js +300 -149
- package/package.json +5 -3
- package/runtime/sdk/animate/animate.ts +238 -0
- package/runtime/sdk/animate/bezier.ts +138 -0
- package/runtime/sdk/animate/easings.ts +126 -0
- package/runtime/sdk/core/color.ts +66 -0
- package/runtime/sdk/core/events.ts +38 -0
- package/runtime/sdk/core/registry.ts +23 -0
- package/runtime/sdk/g2/Camera2D.ts +73 -0
- package/runtime/sdk/g2/Node2D.ts +104 -0
- package/runtime/sdk/g2/Scene2D.ts +73 -0
- package/runtime/sdk/g2/Sprite.ts +164 -0
- package/runtime/sdk/g2/Texture2D.ts +37 -0
- package/runtime/sdk/g2/Tilemap.ts +41 -0
- package/runtime/sdk/g2/loop.ts +22 -0
- package/runtime/sdk/gl/Body.ts +101 -0
- package/runtime/sdk/gl/Camera.ts +40 -0
- package/runtime/sdk/gl/Geometry.ts +218 -0
- package/runtime/sdk/gl/Light.ts +37 -0
- package/runtime/sdk/gl/Material.ts +124 -0
- package/runtime/sdk/gl/Mesh.ts +123 -0
- package/runtime/sdk/gl/Node.ts +307 -0
- package/runtime/sdk/gl/Noise.ts +30 -0
- package/runtime/sdk/gl/Particles.ts +185 -0
- package/runtime/sdk/gl/Plane.ts +53 -0
- package/runtime/sdk/gl/Ray.ts +16 -0
- package/runtime/sdk/gl/Scene.ts +179 -0
- package/runtime/sdk/gl/Texture.ts +35 -0
- package/runtime/sdk/gl/controls.ts +95 -0
- package/runtime/sdk/gl/state.ts +6 -0
- package/runtime/sdk/gl/touch.ts +56 -0
- package/runtime/sdk/inject.ts +72 -0
- package/runtime/sdk/math/Mathf.ts +118 -0
- package/runtime/sdk/math/mat4.ts +278 -0
- package/runtime/sdk/math/quat.ts +232 -0
- package/runtime/sdk/math/vec.ts +255 -0
- package/runtime/sdk/plugins/qr.ts +36 -0
- package/runtime/sdk/runtime/device.ts +33 -0
- package/runtime/sdk/runtime/fetch.ts +71 -0
- package/runtime/sdk/runtime/files.ts +23 -0
- package/runtime/sdk/runtime/input.ts +12 -0
- package/runtime/sdk/runtime/media.ts +111 -0
- package/runtime/sdk/runtime/misc.ts +12 -0
- package/runtime/sdk/runtime/net.ts +36 -0
- package/runtime/sdk/runtime/storage.ts +13 -0
- package/runtime/sdk/runtime/touch.ts +44 -0
- package/runtime/sdk/ui/UI.ts +19 -0
- package/runtime/sdk/ui/UIButton.ts +84 -0
- package/runtime/sdk/ui/UIContainer.ts +100 -0
- package/runtime/sdk/ui/UIImage.ts +68 -0
- package/runtime/sdk/ui/UIInput.ts +78 -0
- package/runtime/sdk/ui/UINode.ts +342 -0
- package/runtime/sdk/ui/UIScreen.ts +122 -0
- package/runtime/sdk/ui/UIScrollable.ts +70 -0
- package/runtime/sdk/ui/UISpacer.ts +14 -0
- package/runtime/sdk/ui/UIText.ts +40 -0
- package/runtime/sdk/ui/UIVideo.ts +38 -0
- package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
- package/runtime/sdk/ui/UIWidget.ts +97 -0
- package/runtime/sdk/ui/fonts.ts +10 -0
- package/runtime/sdk/ui/router.ts +73 -0
- package/runtime/gl-matrix/common.js +0 -54
- package/runtime/gl-matrix/header.js +0 -12
- package/runtime/gl-matrix/index.js +0 -11
- package/runtime/gl-matrix/mat2.js +0 -425
- package/runtime/gl-matrix/mat2d.js +0 -476
- package/runtime/gl-matrix/mat3.js +0 -765
- package/runtime/gl-matrix/mat4.js +0 -1947
- package/runtime/gl-matrix/quat.js +0 -737
- package/runtime/gl-matrix/quat2.js +0 -822
- package/runtime/gl-matrix/vec2.js +0 -620
- package/runtime/gl-matrix/vec3.js +0 -791
- package/runtime/gl-matrix/vec4.js +0 -652
- package/runtime/worker/animate.js +0 -304
- package/runtime/worker/chunk-4ZN32GYS.js +0 -34
- package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
- package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
- package/runtime/worker/creator.js +0 -930
- package/runtime/worker/globals.js +0 -208
- package/runtime/worker/ui.js +0 -763
- package/runtime/worker/utils.js +0 -16
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { TouchStartEvent } from "../runtime/touch"
|
|
2
|
+
import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type ScrollStyle, type Style, type UINodeChild, } from "./UINode"
|
|
3
|
+
|
|
4
|
+
export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & ScrollStyle & BaseStyle & ScrollStyle & { refreshControlColor?: Color }
|
|
5
|
+
|
|
6
|
+
export interface UIScreen <Scrollable extends boolean = boolean> {
|
|
7
|
+
readonly type: "screen",
|
|
8
|
+
scrollable: Scrollable,
|
|
9
|
+
style: Style<this, UIScreenStyle>,
|
|
10
|
+
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
11
|
+
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
12
|
+
|
|
13
|
+
open(): void,
|
|
14
|
+
close(): void
|
|
15
|
+
|
|
16
|
+
append(...nodes: UINodeChild[]): this
|
|
17
|
+
insert(index: number, ...nodes: UINodeChild[]): this
|
|
18
|
+
remove(...nodes: UINodeChild[]): this
|
|
19
|
+
setContent(nodes: UINodeChild[]): this
|
|
20
|
+
|
|
21
|
+
readonly children: UINodeChild[],
|
|
22
|
+
|
|
23
|
+
makeScrollable(): UIScreen<true>,
|
|
24
|
+
onScroll: Scrollable extends true? (callback: (scrollPosition: number) => void) => void: never,
|
|
25
|
+
onOverscroll: Scrollable extends true? (callback: (scrollPosition: number) => void) => void: never,
|
|
26
|
+
onBackPressed(callback: () => void): this
|
|
27
|
+
onRefresh: Scrollable extends true? ((callback: () => void | Promise<void>) => this): never,
|
|
28
|
+
|
|
29
|
+
onOpen(callback: () => void): this
|
|
30
|
+
onClose(callback: () => void): this
|
|
31
|
+
|
|
32
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
33
|
+
|
|
34
|
+
onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ScreenElement extends ContainerElement<"screen"> {
|
|
38
|
+
scrollable = false as const
|
|
39
|
+
open(): void {
|
|
40
|
+
_creatorUI.openScreen(this)
|
|
41
|
+
}
|
|
42
|
+
close(): void {
|
|
43
|
+
_creatorUI.closeScreen()
|
|
44
|
+
}
|
|
45
|
+
protected ol: (() => void)[] = []
|
|
46
|
+
onOpen(callback: () => void): this {
|
|
47
|
+
this.ol.push(callback)
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
protected cl: (() => void)[] = []
|
|
51
|
+
onClose(callback: () => void): this {
|
|
52
|
+
this.cl.push(callback)
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
readonly touchStartListeners: any[] = []
|
|
56
|
+
_interactive = false
|
|
57
|
+
onTouchStart (callback: any): this {
|
|
58
|
+
this._interactive = true
|
|
59
|
+
this.touchStartListeners.push(callback)
|
|
60
|
+
return this
|
|
61
|
+
}
|
|
62
|
+
_emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
|
|
63
|
+
const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
|
|
64
|
+
touchEvent.target = this
|
|
65
|
+
for (let callback of this.touchStartListeners) {
|
|
66
|
+
callback(touchEvent)
|
|
67
|
+
if ((touchEvent as any)._trackHandler) {
|
|
68
|
+
return (touchEvent as any)._trackHandler
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
makeScrollable(): UIScreen<true> {
|
|
74
|
+
this.scrollable = true as any
|
|
75
|
+
|
|
76
|
+
Object.assign(this, {
|
|
77
|
+
sl: [] as any[],
|
|
78
|
+
osl: [] as any[],
|
|
79
|
+
onScroll(callback: any) {
|
|
80
|
+
this.sl.push(callback)
|
|
81
|
+
return this
|
|
82
|
+
},
|
|
83
|
+
onOverscroll(callback: any) {
|
|
84
|
+
this.osl.push(callback)
|
|
85
|
+
return this
|
|
86
|
+
},
|
|
87
|
+
onRefresh(callback: any) {
|
|
88
|
+
(this as any)._refreshCallback = callback
|
|
89
|
+
return this
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return this as any as UIScreen<true>
|
|
94
|
+
}
|
|
95
|
+
_backButtonCallback?: () => void
|
|
96
|
+
onBackPressed(callback: any): this {
|
|
97
|
+
this._backButtonCallback = callback
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
declare _refreshCallback: any
|
|
101
|
+
declare onRefresh: never
|
|
102
|
+
declare onScroll: never
|
|
103
|
+
declare onOverscroll: never
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function UIScreen(): UIScreen<false>;
|
|
107
|
+
export function UIScreen(children: UINodeChild[]): UIScreen<false>;
|
|
108
|
+
export function UIScreen(style: UIScreenStyle): UIScreen<false>;
|
|
109
|
+
export function UIScreen(style: UIScreenStyle, children: UINodeChild[]): UIScreen<false>;
|
|
110
|
+
export function UIScreen(...args: [] | [UINodeChild[] | UIScreenStyle] | [UIScreenStyle, children: UINodeChild[]]): UIScreen<false> {
|
|
111
|
+
if (args.length === 0) {
|
|
112
|
+
return new ScreenElement("screen", {}, [])
|
|
113
|
+
} else if (args.length === 1) {
|
|
114
|
+
if (Array.isArray(args[0])) {
|
|
115
|
+
return new ScreenElement("screen", {}, args[0])
|
|
116
|
+
} else {
|
|
117
|
+
return new ScreenElement("screen", args[0], [])
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
return new ScreenElement("screen", args[0], args[1])
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { UIContainerStyle } from "./UIContainer"
|
|
2
|
+
import { type Style, type AnimateStyle, type UINodeChild, type OnLayoutCallback, ContainerElement } from "./UINode"
|
|
3
|
+
|
|
4
|
+
export type ScrollStyle = { showScrollbar?: boolean, overscrollMode?: "none" | "absorb" | "default" }
|
|
5
|
+
|
|
6
|
+
export type UIScrollableStyle = UIContainerStyle & ScrollStyle & { scrollDirection?: "horizontal" | "vertical" }
|
|
7
|
+
|
|
8
|
+
export interface UIScrollable {
|
|
9
|
+
readonly type: "scrollable",
|
|
10
|
+
style: Style<this, UIScrollableStyle>,
|
|
11
|
+
animateTo: AnimateStyle<this, UIScrollableStyle>,
|
|
12
|
+
animateFrom: AnimateStyle<this, UIScrollableStyle>,
|
|
13
|
+
|
|
14
|
+
append(...nodes: UINodeChild[]): UIScrollable
|
|
15
|
+
insert(index: number, ...nodes: UINodeChild[]): UIScrollable
|
|
16
|
+
remove(...nodes: UINodeChild[]): UIScrollable
|
|
17
|
+
setContent(nodes: UINodeChild[]): this
|
|
18
|
+
|
|
19
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
20
|
+
|
|
21
|
+
onScroll(callback: (scrollPosition: number) => void): UIScrollable,
|
|
22
|
+
onScrollRelease(callback: () => void): UIScrollable,
|
|
23
|
+
onOverscroll(callback: (delta: number) => void): UIScrollable
|
|
24
|
+
|
|
25
|
+
readonly children: UINodeChild[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class ScrollableElement extends ContainerElement<"scrollable"> {
|
|
29
|
+
|
|
30
|
+
readonly osl: any[] = []
|
|
31
|
+
readonly sl: any[] = []
|
|
32
|
+
readonly srl: any[] = []
|
|
33
|
+
|
|
34
|
+
get scrollListeners() {
|
|
35
|
+
return this.sl
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onScroll (callback: any): this {
|
|
39
|
+
this.sl.push(callback)
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onScrollRelease (callback: any): this {
|
|
44
|
+
this.srl.push(callback)
|
|
45
|
+
return this
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onOverscroll (callback: any): this {
|
|
49
|
+
this.osl.push(callback)
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function UIScrollable(): UIScrollable
|
|
55
|
+
export function UIScrollable(children: UINodeChild[]): UIScrollable
|
|
56
|
+
export function UIScrollable(style: UIScrollableStyle): UIScrollable
|
|
57
|
+
export function UIScrollable(style: UIScrollableStyle, children: UINodeChild[]): UIScrollable
|
|
58
|
+
export function UIScrollable(...args: [] | [UINodeChild[] | UIScrollableStyle] | [UIScrollableStyle, children: UINodeChild[]]): UIScrollable {
|
|
59
|
+
if (args.length === 0) {
|
|
60
|
+
return new ScrollableElement("scrollable", {}, [])
|
|
61
|
+
} else if (args.length === 1) {
|
|
62
|
+
if (Array.isArray(args[0])) {
|
|
63
|
+
return new ScrollableElement("scrollable", {}, args[0])
|
|
64
|
+
} else {
|
|
65
|
+
return new ScrollableElement("scrollable", args[0], [])
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
return new ScrollableElement("scrollable", args[0], args[1])
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Element, type ElementStyle, type Style, type StyleFn } from "./UINode"
|
|
2
|
+
|
|
3
|
+
export type UISpacerStyle = ElementStyle
|
|
4
|
+
|
|
5
|
+
export interface UISpacer {
|
|
6
|
+
readonly type: "spacer",
|
|
7
|
+
style: Style<this, UISpacerStyle>,
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
export function UISpacer(): UISpacer
|
|
11
|
+
export function UISpacer(style: UISpacerStyle): UISpacer
|
|
12
|
+
export function UISpacer(style?: UISpacerStyle): UISpacer {
|
|
13
|
+
return new Element("spacer", style ?? {})
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type TextStyle } from "./UINode"
|
|
2
|
+
|
|
3
|
+
export type UITextStyle = ElementStyle & TextStyle
|
|
4
|
+
|
|
5
|
+
export interface UIText {
|
|
6
|
+
readonly type: "text",
|
|
7
|
+
text: string,
|
|
8
|
+
style: Style<this, UITextStyle>,
|
|
9
|
+
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
|
|
10
|
+
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
|
|
11
|
+
|
|
12
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TextElement extends Element<"text"> {
|
|
16
|
+
private _text: string
|
|
17
|
+
constructor(style: any, text: string) {
|
|
18
|
+
super("text", style)
|
|
19
|
+
this._text = text
|
|
20
|
+
}
|
|
21
|
+
get text(): string {
|
|
22
|
+
return this._text
|
|
23
|
+
}
|
|
24
|
+
set text(value) {
|
|
25
|
+
this._text = value
|
|
26
|
+
if (this._id !== 0) {
|
|
27
|
+
_creatorUI.updateNode(this._id, "text", value)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function UIText(text: string): UIText;
|
|
33
|
+
export function UIText(style: UITextStyle, text: string): UIText;
|
|
34
|
+
export function UIText(...args: [string] | [UITextStyle, string]): UIText {
|
|
35
|
+
if (args.length === 1) {
|
|
36
|
+
return new TextElement({}, args[0])
|
|
37
|
+
} else {
|
|
38
|
+
return new TextElement(args[0], args[1])
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { VideoPlayer } from "../runtime/media"
|
|
2
|
+
import { Element, type AnimateStyle, type AppearStyle, type BaseStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type StyleFn } from "./UINode"
|
|
3
|
+
|
|
4
|
+
export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
|
|
5
|
+
|
|
6
|
+
export interface UIVideo {
|
|
7
|
+
readonly type: "video",
|
|
8
|
+
player: VideoPlayer
|
|
9
|
+
style: Style<this, UIVideoStyle>
|
|
10
|
+
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
11
|
+
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
12
|
+
|
|
13
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class VideoElement extends Element<"video"> {
|
|
17
|
+
private _playerId: number
|
|
18
|
+
private _player: VideoPlayer
|
|
19
|
+
constructor(style: any, player: VideoPlayer) {
|
|
20
|
+
super("video", style)
|
|
21
|
+
this._player = player
|
|
22
|
+
this._playerId = (player as any)._id
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get player() {
|
|
26
|
+
return this._player
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function UIVideo(player: VideoPlayer): UIVideo
|
|
31
|
+
export function UIVideo(style: UIVideoStyle, player: VideoPlayer): UIVideo
|
|
32
|
+
export function UIVideo(...args: [VideoPlayer] | [UIVideoStyle, VideoPlayer]): UIVideo {
|
|
33
|
+
if (args.length === 1) {
|
|
34
|
+
return new VideoElement({}, args[0])
|
|
35
|
+
} else {
|
|
36
|
+
return new VideoElement(args[0], args[1])
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Element, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type PaddingStyle, type Style, type UINodeChild } from "./UINode"
|
|
2
|
+
|
|
3
|
+
export type UIVirtualizedListStyle = DrawableStyle & ElementStyle
|
|
4
|
+
|
|
5
|
+
export interface VListConfig<T> {
|
|
6
|
+
/** Стабильный уникальный ключ айтема */
|
|
7
|
+
keyOf: (item: T) => string
|
|
8
|
+
/** Фабрика поддерева. Вызывается при входе айтема в окно; поддерево живёт до выхода из окна */
|
|
9
|
+
render: (item: T) => UINodeChild
|
|
10
|
+
/**
|
|
11
|
+
* Оценка высоты (px) до первого измерения. Число — или функция от айтема,
|
|
12
|
+
* например для чата: msg => 40 + Math.ceil(msg.text.length / 35) * 20
|
|
13
|
+
* Вычисляется один раз при добавлении айтема, в нативку уходит готовое число.
|
|
14
|
+
*/
|
|
15
|
+
estimatedHeight: number | ((item: T) => number)
|
|
16
|
+
/** px вокруг вьюпорта, которые держим смонтированными. Default: высота вьюпорта */
|
|
17
|
+
overscan?: number
|
|
18
|
+
/** Чат-режим: первый layout проскроллен в конец, append при положении внизу автоскроллит */
|
|
19
|
+
inverted?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UIVirtualizedList<T = unknown> {
|
|
23
|
+
readonly type: "vlist"
|
|
24
|
+
style: Style<this, UIVirtualizedListStyle>
|
|
25
|
+
|
|
26
|
+
/** Полная замена данных. Нативка сама размонтирует исчезнувшие ключи через _syncWindow */
|
|
27
|
+
setData(items: T[]): this
|
|
28
|
+
append(...items: T[]): this
|
|
29
|
+
/** Вставка в начало БЕЗ прыжка скролла (компенсация на стороне нативки) */
|
|
30
|
+
prepend(...items: T[]): this
|
|
31
|
+
/** Обновить айтемы с теми же ключами: re-render, если смонтированы. Высота в кэше сохраняется до повторного измерения */
|
|
32
|
+
update(...items: T[]): this
|
|
33
|
+
removeByKey(...keys: string[]): this
|
|
34
|
+
|
|
35
|
+
readonly itemCount: number
|
|
36
|
+
getItem(key: string): T | undefined
|
|
37
|
+
|
|
38
|
+
scrollTo(offset: number, animated?: boolean): void
|
|
39
|
+
scrollToKey(key: string, animated?: boolean): void
|
|
40
|
+
scrollToEnd(animated?: boolean): void
|
|
41
|
+
|
|
42
|
+
onScroll(callback: (scrollPosition: number) => void): this
|
|
43
|
+
/** Порог в px от нижнего края контента. Срабатывает один раз до ухода от края (защёлка в нативке) */
|
|
44
|
+
onEndReached(thresholdPx: number, callback: () => void): this
|
|
45
|
+
/** Порог от верхнего края — подгрузка истории в чате */
|
|
46
|
+
onStartReached(thresholdPx: number, callback: () => void): this
|
|
47
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedList<T> {
|
|
51
|
+
|
|
52
|
+
// --- читается нативкой в createUINode (copyProperty / copyNodePtr) ---
|
|
53
|
+
_overscan: number // -1 = "высота вьюпорта", резолвится в нативке
|
|
54
|
+
_inverted: boolean
|
|
55
|
+
_keys: string[] = [] // начальное состояние до маунта ноды
|
|
56
|
+
_estimates: number[] = [] // параллельно _keys
|
|
57
|
+
_endThreshold = 0
|
|
58
|
+
_startThreshold = 0
|
|
59
|
+
|
|
60
|
+
// --- списки слушателей, нативка забирает через copyPropertyListPtr ---
|
|
61
|
+
protected sl: ((scrollPosition: number) => void)[] = [] // scroll
|
|
62
|
+
protected erl: (() => void)[] = [] // endReached
|
|
63
|
+
protected strl: (() => void)[] = [] // startReached
|
|
64
|
+
|
|
65
|
+
// --- приватное состояние JS-стороны ---
|
|
66
|
+
private items = new Map<string, T>()
|
|
67
|
+
private mounted = new Map<string, UINodeChild>()
|
|
68
|
+
private keyOf: (item: T) => string
|
|
69
|
+
private renderItem: (item: T) => UINodeChild
|
|
70
|
+
private estimate: (item: T) => number
|
|
71
|
+
|
|
72
|
+
constructor(config: VListConfig<T>) {
|
|
73
|
+
super("vlist", {})
|
|
74
|
+
this.keyOf = config.keyOf
|
|
75
|
+
this.renderItem = config.render
|
|
76
|
+
this.estimate = typeof config.estimatedHeight === "function"
|
|
77
|
+
? config.estimatedHeight
|
|
78
|
+
: () => config.estimatedHeight as number
|
|
79
|
+
this._overscan = config.overscan ?? -1
|
|
80
|
+
this._inverted = config.inverted ?? false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private get isAttached(): boolean {
|
|
84
|
+
return !!this._id && this._id !== 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private register(items: T[]): [string[], number[]] {
|
|
88
|
+
const keys: string[] = []
|
|
89
|
+
const estimates: number[] = []
|
|
90
|
+
for (const item of items) {
|
|
91
|
+
const k = this.keyOf(item)
|
|
92
|
+
this.items.set(k, item)
|
|
93
|
+
keys.push(k)
|
|
94
|
+
estimates.push(this.estimate(item))
|
|
95
|
+
}
|
|
96
|
+
return [keys, estimates]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setData(items: T[]): this {
|
|
100
|
+
const prevItems = new Map(this.items)
|
|
101
|
+
const prevMounted = [...this.mounted.keys()]
|
|
102
|
+
this.items.clear()
|
|
103
|
+
const [keys, estimates] = this.register(items)
|
|
104
|
+
this._keys = keys
|
|
105
|
+
this._estimates = estimates
|
|
106
|
+
if (this.isAttached) {
|
|
107
|
+
// нативка диффает по ключам: исчезнувшие смонтированные ключи придут в _syncWindow как unmount
|
|
108
|
+
_creatorUI.vlistSetKeys(this, keys, estimates)
|
|
109
|
+
// setKeys диффает ТОЛЬКО по ключам — пережившие replace смонтированные айтемы сохраняют
|
|
110
|
+
// старое поддерево. Если объект данных изменился, перерисовываем их (как update), иначе
|
|
111
|
+
// строки остаются со старым содержимым. На вебе экран в истории держит айтемы
|
|
112
|
+
// смонтированными, поэтому без этого повторный setData их не обновлял.
|
|
113
|
+
for (const k of prevMounted) {
|
|
114
|
+
if (this.mounted.has(k) && this.items.get(k) !== prevItems.get(k)) {
|
|
115
|
+
_creatorUI.vlistInvalidate(this, k)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
append(...items: T[]): this {
|
|
123
|
+
const [keys, estimates] = this.register(items)
|
|
124
|
+
this._keys.push(...keys)
|
|
125
|
+
this._estimates.push(...estimates)
|
|
126
|
+
if (this.isAttached) {
|
|
127
|
+
// inverted + позиция внизу → нативка автоскроллит к концу
|
|
128
|
+
_creatorUI.vlistInsertKeys(this, -1, keys, estimates)
|
|
129
|
+
}
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
prepend(...items: T[]): this {
|
|
134
|
+
const [keys, estimates] = this.register(items)
|
|
135
|
+
this._keys.unshift(...keys)
|
|
136
|
+
this._estimates.unshift(...estimates)
|
|
137
|
+
if (this.isAttached) {
|
|
138
|
+
// нативка компенсирует scrollY на сумму estimates — позиция не прыгает
|
|
139
|
+
_creatorUI.vlistInsertKeys(this, 0, keys, estimates)
|
|
140
|
+
}
|
|
141
|
+
return this
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
update(...items: T[]): this {
|
|
145
|
+
for (const item of items) {
|
|
146
|
+
const k = this.keyOf(item)
|
|
147
|
+
if (!this.items.has(k)) continue
|
|
148
|
+
this.items.set(k, item)
|
|
149
|
+
if (this.isAttached && this.mounted.has(k)) {
|
|
150
|
+
// unmount+mount, кэш высоты сохраняется до повторного измерения.
|
|
151
|
+
// Несмонтированный айтем перерендерится сам при входе в окно
|
|
152
|
+
_creatorUI.vlistInvalidate(this, k)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return this
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
removeByKey(...keys: string[]): this {
|
|
159
|
+
for (const k of keys) {
|
|
160
|
+
if (!this.items.delete(k)) continue
|
|
161
|
+
const i = this._keys.indexOf(k)
|
|
162
|
+
if (i >= 0) { this._keys.splice(i, 1); this._estimates.splice(i, 1) }
|
|
163
|
+
}
|
|
164
|
+
if (this.isAttached) {
|
|
165
|
+
_creatorUI.vlistRemoveKeys(this, keys)
|
|
166
|
+
}
|
|
167
|
+
return this
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get itemCount(): number { return this.items.size }
|
|
171
|
+
getItem(key: string): T | undefined { return this.items.get(key) }
|
|
172
|
+
|
|
173
|
+
// --- команды: офсеты и позиции знает только нативка (префикс-суммы) ---
|
|
174
|
+
scrollTo(offset: number, animated = true): void {
|
|
175
|
+
_creatorUI.command(this, "scrollTo", offset, animated)
|
|
176
|
+
}
|
|
177
|
+
scrollToKey(key: string, animated = true): void {
|
|
178
|
+
_creatorUI.command(this, "scrollToKey", key, animated)
|
|
179
|
+
}
|
|
180
|
+
scrollToEnd(animated = true): void {
|
|
181
|
+
_creatorUI.command(this, "scrollToEnd", animated)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
onScroll(callback: (scrollPosition: number) => void): this {
|
|
185
|
+
this.sl.push(callback)
|
|
186
|
+
return this
|
|
187
|
+
}
|
|
188
|
+
onEndReached(thresholdPx: number, callback: () => void): this {
|
|
189
|
+
this._endThreshold = thresholdPx
|
|
190
|
+
this.erl.push(callback)
|
|
191
|
+
return this
|
|
192
|
+
}
|
|
193
|
+
onStartReached(thresholdPx: number, callback: () => void): this {
|
|
194
|
+
this._startThreshold = thresholdPx
|
|
195
|
+
this.strl.push(callback)
|
|
196
|
+
return this
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Единственный колбэк из нативки (через jsPtr, как у button).
|
|
201
|
+
* Вызывается ТОЛЬКО когда окно пересекло границу — не на каждый кадр скролла.
|
|
202
|
+
* Порядок: сначала unmount (нативка уже снесла поддеревья и обнулила _id,
|
|
203
|
+
* здесь только чистим JS-ссылки), потом mount.
|
|
204
|
+
*/
|
|
205
|
+
_syncWindow(unmountKeys: string[], mountKeys: string[]): void {
|
|
206
|
+
for (const k of unmountKeys) {
|
|
207
|
+
this.mounted.delete(k)
|
|
208
|
+
}
|
|
209
|
+
for (const k of mountKeys) {
|
|
210
|
+
const item = this.items.get(k)
|
|
211
|
+
if (item === undefined) continue
|
|
212
|
+
const node = this.renderItem(item)
|
|
213
|
+
this.mounted.set(k, node)
|
|
214
|
+
// createUINode в detached yoga root + измерение (width, NaN) + кэш высоты
|
|
215
|
+
_creatorUI.vlistMount(this, k, node)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function UIVirtualizedList<T>(config: VListConfig<T>): UIVirtualizedList<T> {
|
|
221
|
+
return new VListElement<T>(config)
|
|
222
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { TouchStartEvent } from "../runtime/touch"
|
|
2
|
+
import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type PositionStyle, type Style, type UINodeChild } from "./UINode"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
|
|
6
|
+
// A layer behind the widget that intercepts clicks. "transparent" still intercepts;
|
|
7
|
+
// null removes the layer. A tap on it fires onOverlayTap.
|
|
8
|
+
overlayColor?: Color | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export interface UIWidget {
|
|
13
|
+
readonly type: "widget",
|
|
14
|
+
style: Style<this, UIWidgetStyle>,
|
|
15
|
+
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
|
|
16
|
+
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
|
|
17
|
+
|
|
18
|
+
show(): void,
|
|
19
|
+
hide(): void,
|
|
20
|
+
|
|
21
|
+
readonly isShow: boolean,
|
|
22
|
+
|
|
23
|
+
append(...nodes: UINodeChild[]): UIWidget
|
|
24
|
+
insert(index: number, ...nodes: UINodeChild[]): UIWidget
|
|
25
|
+
remove(...nodes: UINodeChild[]): UIWidget
|
|
26
|
+
setContent(nodes: UINodeChild[]): this
|
|
27
|
+
|
|
28
|
+
readonly children: UINodeChild[],
|
|
29
|
+
|
|
30
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
31
|
+
onTouchStart(callback: (ev: TouchStartEvent<UIWidget>) => void): this,
|
|
32
|
+
onOverlayTap(callback: () => void): this,
|
|
33
|
+
onBackPressed(callback: () => void): this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export class WidgetElement extends ContainerElement<"widget"> {
|
|
38
|
+
scrollable = false as const
|
|
39
|
+
show(): void {
|
|
40
|
+
_creatorUI.showWidget(this)
|
|
41
|
+
}
|
|
42
|
+
hide(): void {
|
|
43
|
+
_creatorUI.hideWidget(this)
|
|
44
|
+
}
|
|
45
|
+
get isShow(): boolean {
|
|
46
|
+
return this._id !== 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_interactive = false
|
|
50
|
+
readonly touchStartListeners: any[] = []
|
|
51
|
+
onTouchStart (callback: any): this {
|
|
52
|
+
this._interactive = true
|
|
53
|
+
this.touchStartListeners.push(callback)
|
|
54
|
+
return this
|
|
55
|
+
}
|
|
56
|
+
_emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
|
|
57
|
+
const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
|
|
58
|
+
touchEvent.target = this
|
|
59
|
+
for (let callback of this.touchStartListeners) {
|
|
60
|
+
callback(touchEvent)
|
|
61
|
+
if ((touchEvent as any)._trackHandler) {
|
|
62
|
+
return (touchEvent as any)._trackHandler
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
readonly otl: (() => void)[] = []
|
|
68
|
+
onOverlayTap(callback: () => void): this {
|
|
69
|
+
this.otl.push(callback)
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_backButtonCallback?: () => void
|
|
74
|
+
onBackPressed(callback: any): this {
|
|
75
|
+
this._backButtonCallback = callback
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export function UIWidget(): UIWidget;
|
|
82
|
+
export function UIWidget(children: UINodeChild[]): UIWidget;
|
|
83
|
+
export function UIWidget(style: UIWidgetStyle): UIWidget;
|
|
84
|
+
export function UIWidget(style: UIWidgetStyle, children: UINodeChild[]): UIWidget;
|
|
85
|
+
export function UIWidget(...args: [] | [UINodeChild[] | UIWidgetStyle] | [UIWidgetStyle, children: UINodeChild[]]): UIWidget {
|
|
86
|
+
if (args.length === 0) {
|
|
87
|
+
return new WidgetElement("widget", {}, [])
|
|
88
|
+
} else if (args.length === 1) {
|
|
89
|
+
if (Array.isArray(args[0])) {
|
|
90
|
+
return new WidgetElement("widget", {}, args[0])
|
|
91
|
+
} else {
|
|
92
|
+
return new WidgetElement("widget", args[0], [])
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
return new WidgetElement("widget", args[0], args[1])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type FontOptions = {
|
|
2
|
+
weight?: number,
|
|
3
|
+
style?: "normal" | "italic"
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export async function registerFont (fontFamily: string, url: string, options: FontOptions = {}): Promise<void> {
|
|
7
|
+
return new Promise<void>((res, rej) => {
|
|
8
|
+
_creatorUI.registerFont(fontFamily, url, options, res, rej)
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { UIScreen } from "./UIScreen";
|
|
2
|
+
|
|
3
|
+
type RouterOptions = {
|
|
4
|
+
showDefaultBackButton?: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type Router = {
|
|
8
|
+
get current(): UIScreen<boolean>
|
|
9
|
+
init<T extends boolean>(homePage: UIScreen<T>, opts?: RouterOptions): void
|
|
10
|
+
push<T extends boolean>(screen: UIScreen<T>): void
|
|
11
|
+
replace<T extends boolean>(screen: UIScreen<T>, options?: ReplaceOptions): void
|
|
12
|
+
pop(to?: number): void
|
|
13
|
+
hide(): void
|
|
14
|
+
restore(): void
|
|
15
|
+
|
|
16
|
+
addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
|
|
17
|
+
removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let currentScreen: UIScreen<any> | null = null
|
|
21
|
+
let onChange = <T extends boolean>(screen: UIScreen<T>) => {
|
|
22
|
+
currentScreen = screen;
|
|
23
|
+
for (let callback of (Router as any)._changeListeners) {
|
|
24
|
+
callback(screen)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ReplaceOptions = {
|
|
29
|
+
transition: "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
|
|
30
|
+
"zoom" | "zoom-out" | "zoom-in" | "fade" | "none"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Router: Router = /*#__PURE__*/ Object.freeze({
|
|
34
|
+
|
|
35
|
+
init(homePage: UIScreen<any>, opts: RouterOptions = {}): void {
|
|
36
|
+
_creatorUI.routerOpen(homePage, onChange, opts.showDefaultBackButton ?? false)
|
|
37
|
+
currentScreen = homePage
|
|
38
|
+
},
|
|
39
|
+
push(screen: UIScreen<any>): void {
|
|
40
|
+
_creatorUI.routerPush(screen)
|
|
41
|
+
},
|
|
42
|
+
replace(screen: UIScreen<any>, opts?: ReplaceOptions): void {
|
|
43
|
+
_creatorUI.routerReplace(screen, opts?.transition ?? "fade")
|
|
44
|
+
},
|
|
45
|
+
pop(to?: number): void {
|
|
46
|
+
_creatorUI.routerPop(to ?? -1)
|
|
47
|
+
},
|
|
48
|
+
hide() {
|
|
49
|
+
_creatorUI.routerHide()
|
|
50
|
+
},
|
|
51
|
+
restore() {
|
|
52
|
+
_creatorUI.routerRestore()
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
get current(): UIScreen<any> {
|
|
56
|
+
return currentScreen!
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
_changeListeners: [] as any[],
|
|
60
|
+
addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
|
|
61
|
+
if (channel === "change") {
|
|
62
|
+
this._changeListeners.push(callback)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
|
|
66
|
+
if (channel === "change") {
|
|
67
|
+
let index = this._changeListeners.indexOf(callback)
|
|
68
|
+
if (index >= 0) {
|
|
69
|
+
this._changeListeners.splice(index, 1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})
|