@ives_xxz/framework 1.4.2 → 1.4.4
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/FW.d.ts +58 -0
- package/define/FWSystemDefine.ts +7 -0
- package/entry/FWEntry.ts +6 -0
- package/manager/FWAssetManager.ts +15 -8
- package/manager/FWPromiseManager.ts +244 -0
- package/manager/FWPromiseManager.ts.meta +10 -0
- package/package.json +1 -1
- package/render/FWAssembler.ts +11 -0
- package/render/FWAssembler.ts.meta +10 -0
- package/render/FWRenderAssembler.ts +620 -0
- package/render/FWRenderAssembler.ts.meta +10 -0
- package/render.meta +13 -0
package/FW.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ declare namespace FW {
|
|
|
34
34
|
engineMgr: EngineManager;
|
|
35
35
|
taskMgr: TaskManager;
|
|
36
36
|
hotUpdateMgr: HotUpdateManager;
|
|
37
|
+
promiseMgr: PromiseManager;
|
|
37
38
|
scene: Scene;
|
|
38
39
|
bundleName: string;
|
|
39
40
|
getComponent<T>(serviceIdentifier: FW.ServiceIdentifier<T>): T;
|
|
@@ -48,6 +49,17 @@ declare namespace FW {
|
|
|
48
49
|
onDestroy(): void;
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
type PromiseManager = {
|
|
53
|
+
public execute<T = any>(
|
|
54
|
+
executor: (
|
|
55
|
+
resolve: (value: T | PromiseLike<T>) => void,
|
|
56
|
+
reject: (reason?: any) => void,
|
|
57
|
+
signal: AbortSignal,
|
|
58
|
+
) => void = PromiseExcutor,
|
|
59
|
+
options: PromiseExecuteOptions = {},
|
|
60
|
+
): PromiseProxy<T>;
|
|
61
|
+
};
|
|
62
|
+
|
|
51
63
|
type Registry = {
|
|
52
64
|
/** 注册 */
|
|
53
65
|
register(): void;
|
|
@@ -1899,6 +1911,52 @@ declare namespace FW {
|
|
|
1899
1911
|
message?: string;
|
|
1900
1912
|
};
|
|
1901
1913
|
|
|
1914
|
+
// 颜色分段配置接口
|
|
1915
|
+
type AssemblerLabelColorSegment = {
|
|
1916
|
+
color: cc.Color;
|
|
1917
|
+
position: number;
|
|
1918
|
+
};
|
|
1919
|
+
|
|
1920
|
+
type AssemblerLabelColorConfig = {
|
|
1921
|
+
color: cc.Color;
|
|
1922
|
+
percentages: number;
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1925
|
+
type PromiseProxy<T = Promise> = {
|
|
1926
|
+
id: number;
|
|
1927
|
+
promise: Promise<T>;
|
|
1928
|
+
status: any;
|
|
1929
|
+
abortController: AbortController;
|
|
1930
|
+
abort?: (reason?: any) => void;
|
|
1931
|
+
addAbortEventListener?: (
|
|
1932
|
+
listener: (this: AbortSignal, ev: Event) => any,
|
|
1933
|
+
options?: boolean | AddEventListenerOptions,
|
|
1934
|
+
) => void;
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
type PromiseExcutor<T> = (
|
|
1938
|
+
resolve: (value: T | PromiseLike<T>) => void,
|
|
1939
|
+
reject: (reason?: any) => void,
|
|
1940
|
+
signal: AbortSignal,
|
|
1941
|
+
) => void;
|
|
1942
|
+
|
|
1943
|
+
type PromiseExecuteOptions = {
|
|
1944
|
+
timeout?: number;
|
|
1945
|
+
retryCount?: number;
|
|
1946
|
+
retryInterval?: number;
|
|
1947
|
+
retryCondition?: (error: any, retryCount: number) => boolean;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
type Promise = (resolve: (value: any) => void, reject: (reason?: any) => void) => void;
|
|
1951
|
+
|
|
1952
|
+
type PromiseResult<T = any> = {
|
|
1953
|
+
success: T[];
|
|
1954
|
+
failed: { id: number; reason: any }[];
|
|
1955
|
+
cancelled: number[];
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
type PromiseStatus = 'pending' | 'fulfilled' | 'rejected' | 'cancelled';
|
|
1959
|
+
|
|
1902
1960
|
declare function timeScale(scale: number);
|
|
1903
1961
|
declare let Entry: Entry;
|
|
1904
1962
|
}
|
package/define/FWSystemDefine.ts
CHANGED
|
@@ -130,4 +130,11 @@ export namespace FWSystemDefine {
|
|
|
130
130
|
SPRITE = 'sprite',
|
|
131
131
|
SKELETON = 'skeleton',
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
export enum FWPromiseStatus {
|
|
135
|
+
PENDING = 'pending',
|
|
136
|
+
FULFILLED = 'fulfilled',
|
|
137
|
+
REJECTED = 'rejected',
|
|
138
|
+
CANCELLED = 'cancelled',
|
|
139
|
+
}
|
|
133
140
|
}
|
package/entry/FWEntry.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { FWEventDefine } from '../define/FWEventDefine';
|
|
|
18
18
|
import FWTaskManager from '../manager/FWTaskManager';
|
|
19
19
|
import FWEngineManager from '../manager/FWEngineManager';
|
|
20
20
|
import FWHotUpdateManager from '../manager/FWHotUpdateManager';
|
|
21
|
+
import FWPromiseManager from '../manager/FWPromiseManager';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* 入口脚本
|
|
@@ -84,6 +85,10 @@ export default class FWEntry implements FW.Entry {
|
|
|
84
85
|
* 热更新管理器
|
|
85
86
|
*/
|
|
86
87
|
hotUpdateMgr: FW.HotUpdateManager;
|
|
88
|
+
/**
|
|
89
|
+
* promise管理器
|
|
90
|
+
*/
|
|
91
|
+
promiseMgr: FW.PromiseManager;
|
|
87
92
|
/**
|
|
88
93
|
* 当前Scene
|
|
89
94
|
*/
|
|
@@ -113,6 +118,7 @@ export default class FWEntry implements FW.Entry {
|
|
|
113
118
|
this.componentMgr = new FWComponentManager();
|
|
114
119
|
this.languageMgr = new FWLanguageManager();
|
|
115
120
|
this.hotUpdateMgr = new FWHotUpdateManager();
|
|
121
|
+
this.promiseMgr = new FWPromiseManager();
|
|
116
122
|
|
|
117
123
|
this.resMgr.initialize();
|
|
118
124
|
this.layerMgr.initialize();
|
|
@@ -77,6 +77,9 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
|
|
|
77
77
|
): Promise<T> {
|
|
78
78
|
return new Promise((resolve, reject) => {
|
|
79
79
|
cc.assetManager.loadRemote(url, { cacheEnabled: true, maxRetryCount: 0 }, (err, asset) => {
|
|
80
|
+
if (err || !asset) {
|
|
81
|
+
reject(err);
|
|
82
|
+
}
|
|
80
83
|
cb?.(asset as T);
|
|
81
84
|
resolve(asset as T);
|
|
82
85
|
});
|
|
@@ -128,11 +131,8 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
|
|
|
128
131
|
|
|
129
132
|
assetData.loaded = false;
|
|
130
133
|
|
|
131
|
-
return
|
|
132
|
-
(
|
|
133
|
-
resolve: (value: FW.AssetData | PromiseLike<FW.AssetData>) => void,
|
|
134
|
-
reject: (reason?: any) => void,
|
|
135
|
-
) => {
|
|
134
|
+
return FW.Entry.promiseMgr.execute(
|
|
135
|
+
(resolve, reject, signal) => {
|
|
136
136
|
const self = this;
|
|
137
137
|
bundle.load(
|
|
138
138
|
path,
|
|
@@ -142,8 +142,11 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
|
|
|
142
142
|
},
|
|
143
143
|
(err: Error, asset: cc.Asset) => {
|
|
144
144
|
if (err || !asset) {
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
signal.addEventListener('abort', () => {
|
|
146
|
+
FWLog.error(`加载资源失败:${asset},请检查!`);
|
|
147
|
+
reject(err);
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
assetData.loaded = true;
|
|
@@ -162,7 +165,11 @@ export class FWAssetManager extends FWManager implements FW.AssetManager {
|
|
|
162
165
|
},
|
|
163
166
|
);
|
|
164
167
|
},
|
|
165
|
-
|
|
168
|
+
{
|
|
169
|
+
timeout: 10,
|
|
170
|
+
retryCount: 3,
|
|
171
|
+
},
|
|
172
|
+
).promise;
|
|
166
173
|
}
|
|
167
174
|
/**
|
|
168
175
|
* 加载文件夹
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { FWSystemDefine } from '../define/FWSystemDefine';
|
|
2
|
+
import FWLog from '../log/FWLog';
|
|
3
|
+
import { FWManager } from './FWManager';
|
|
4
|
+
|
|
5
|
+
export default class FWPromiseManager extends FWManager implements FW.PromiseManager {
|
|
6
|
+
private promiseRegistry: Map<number, FW.PromiseProxy>;
|
|
7
|
+
private uniqueId: number = 0;
|
|
8
|
+
private timerSchedule: FW.TimerSchedule;
|
|
9
|
+
|
|
10
|
+
public initialize(): void {
|
|
11
|
+
this.promiseRegistry = new Map<number, FW.PromiseProxy>();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public onDestroy(): void {
|
|
15
|
+
this.cancelAll('Manager destroyed');
|
|
16
|
+
this.promiseRegistry.clear();
|
|
17
|
+
this.promiseRegistry = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 创建Promise执行器 */
|
|
21
|
+
public execute<T = any>(
|
|
22
|
+
executor: FW.PromiseExcutor<T>,
|
|
23
|
+
options: FW.PromiseExecuteOptions = {},
|
|
24
|
+
): FW.PromiseProxy<T> {
|
|
25
|
+
const id = this.uniqueId++;
|
|
26
|
+
const abortController = new AbortController();
|
|
27
|
+
let retryCount = 0;
|
|
28
|
+
let promise: Promise<T>;
|
|
29
|
+
const maxRetryTimes = options.retryCount || 0;
|
|
30
|
+
const retryInterval = options.retryInterval || 0;
|
|
31
|
+
const promiseProxy: FW.PromiseProxy<T> = {
|
|
32
|
+
id,
|
|
33
|
+
promise,
|
|
34
|
+
status: FWSystemDefine.FWPromiseStatus.PENDING,
|
|
35
|
+
abortController,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const createPromise = (): Promise<T> => {
|
|
39
|
+
return new Promise<T>((resolve, reject) => {
|
|
40
|
+
if (options.timeout && options.timeout > 0) {
|
|
41
|
+
this.timerSchedule?.unSchedule();
|
|
42
|
+
this.timerSchedule = FW.Entry.timeMgr.scheduleOnce(() => {
|
|
43
|
+
const timeoutError = new Error(`Promise ${id} timeout after ${options.timeout} s`);
|
|
44
|
+
if (
|
|
45
|
+
retryCount < maxRetryTimes &&
|
|
46
|
+
(!options.retryCondition || options.retryCondition(timeoutError, retryCount))
|
|
47
|
+
) {
|
|
48
|
+
retryCount++;
|
|
49
|
+
FWLog.debug(`Promise ${id} timeout, retrying (${retryCount}/${maxRetryTimes})`);
|
|
50
|
+
if (retryInterval > 0) {
|
|
51
|
+
FW.Entry.timeMgr.scheduleOnce(() => {
|
|
52
|
+
createPromise().then(resolve, reject);
|
|
53
|
+
}, retryInterval);
|
|
54
|
+
} else {
|
|
55
|
+
createPromise().then(resolve, reject);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
abortController.abort(timeoutError.message);
|
|
59
|
+
this.timerSchedule?.unSchedule();
|
|
60
|
+
}
|
|
61
|
+
}, options.timeout);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
this.timerSchedule?.unSchedule();
|
|
66
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
67
|
+
promiseProxy.status = FWSystemDefine.FWPromiseStatus.CANCELLED;
|
|
68
|
+
this.removePromise(id);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (abortController.signal.aborted) {
|
|
73
|
+
onAbort();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
abortController.signal.addEventListener('abort', onAbort);
|
|
78
|
+
|
|
79
|
+
const wrappedResolve = (value: T | PromiseLike<T>) => {
|
|
80
|
+
this.timerSchedule?.unSchedule();
|
|
81
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
82
|
+
promiseProxy.status = FWSystemDefine.FWPromiseStatus.FULFILLED;
|
|
83
|
+
abortController.signal.removeEventListener('abort', onAbort);
|
|
84
|
+
this.removePromise(id);
|
|
85
|
+
resolve(value);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const wrappedReject = (reason?: any) => {
|
|
90
|
+
this.timerSchedule?.unSchedule();
|
|
91
|
+
if (
|
|
92
|
+
retryCount < maxRetryTimes &&
|
|
93
|
+
(!options.retryCondition || options.retryCondition(reason, retryCount))
|
|
94
|
+
) {
|
|
95
|
+
retryCount++;
|
|
96
|
+
FWLog.debug(`Promise ${id} failed, retrying (${retryCount}/${maxRetryTimes}):`, reason);
|
|
97
|
+
if (retryInterval > 0) {
|
|
98
|
+
FW.Entry.timeMgr.scheduleOnce(() => {
|
|
99
|
+
createPromise().then(resolve, reject);
|
|
100
|
+
}, retryInterval);
|
|
101
|
+
} else {
|
|
102
|
+
createPromise().then(resolve, reject);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
106
|
+
promiseProxy.status = FWSystemDefine.FWPromiseStatus.REJECTED;
|
|
107
|
+
abortController.signal.removeEventListener('abort', onAbort);
|
|
108
|
+
this.removePromise(id);
|
|
109
|
+
reject(reason);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
try {
|
|
114
|
+
executor(wrappedResolve, wrappedReject, abortController.signal);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
wrappedReject(error);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
promise = createPromise();
|
|
122
|
+
promiseProxy.abort = (reason?: any) => {
|
|
123
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
124
|
+
FWLog.debug(reason || 'promise cancelled');
|
|
125
|
+
abortController.abort(reason);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
promiseProxy.addAbortEventListener = (
|
|
129
|
+
listener: (this: AbortSignal, ev: Event) => any,
|
|
130
|
+
options?: boolean | AddEventListenerOptions,
|
|
131
|
+
) => {
|
|
132
|
+
abortController.signal.addEventListener('abort', listener, options);
|
|
133
|
+
};
|
|
134
|
+
this.promiseRegistry.set(id, promiseProxy);
|
|
135
|
+
return promiseProxy;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 取消指定Promise */
|
|
139
|
+
public cancel(id: number, reason?: any): boolean {
|
|
140
|
+
const promiseProxy = this.promiseRegistry.get(id);
|
|
141
|
+
if (promiseProxy && promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
142
|
+
promiseProxy.abort(reason);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 批量取消Promise */
|
|
149
|
+
public cancelMultiple(ids: number[], reason?: any): number[] {
|
|
150
|
+
const cancelled: number[] = [];
|
|
151
|
+
ids.forEach((id) => {
|
|
152
|
+
if (this.cancel(id, reason)) {
|
|
153
|
+
cancelled.push(id);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return cancelled;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 取消所有Promise */
|
|
160
|
+
public cancelAll(reason?: any): number[] {
|
|
161
|
+
const cancelled: number[] = [];
|
|
162
|
+
this.promiseRegistry.forEach((promiseProxy, id) => {
|
|
163
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
164
|
+
promiseProxy.abort(reason);
|
|
165
|
+
cancelled.push(id);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return cancelled;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 批量执行Promise并等待所有完成 */
|
|
172
|
+
public async all<T = any>(
|
|
173
|
+
promises: FW.PromiseProxy<T>[],
|
|
174
|
+
options: FW.PromiseExecuteOptions = {},
|
|
175
|
+
): Promise<FW.PromiseResult<T>> {
|
|
176
|
+
const result: FW.PromiseResult<T> = {
|
|
177
|
+
success: [],
|
|
178
|
+
failed: [],
|
|
179
|
+
cancelled: [],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
for (const promiseProxy of promises) {
|
|
183
|
+
try {
|
|
184
|
+
const value = await promiseProxy.promise;
|
|
185
|
+
result.success.push(value);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (promiseProxy.status === FWSystemDefine.FWPromiseStatus.CANCELLED) {
|
|
188
|
+
result.cancelled.push(promiseProxy.id);
|
|
189
|
+
} else {
|
|
190
|
+
result.failed.push({
|
|
191
|
+
id: promiseProxy.id,
|
|
192
|
+
reason: error,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 获取Promise状态 */
|
|
202
|
+
public getStatus(id: number): FWSystemDefine.FWPromiseStatus | null {
|
|
203
|
+
const promiseProxy = this.promiseRegistry.get(id);
|
|
204
|
+
return promiseProxy ? promiseProxy.status : null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** 获取所有Promise状态 */
|
|
208
|
+
public getAllStatus(): Map<number, FWSystemDefine.FWPromiseStatus> {
|
|
209
|
+
const statusMap = new Map<number, FWSystemDefine.FWPromiseStatus>();
|
|
210
|
+
this.promiseRegistry.forEach((promiseProxy, id) => {
|
|
211
|
+
statusMap.set(id, promiseProxy.status);
|
|
212
|
+
});
|
|
213
|
+
return statusMap;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 移除Promise */
|
|
217
|
+
private removePromise(id: number): void {
|
|
218
|
+
this.promiseRegistry.delete(id);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** 获取正在执行的Promise数量 */
|
|
222
|
+
public getActiveCount(): number {
|
|
223
|
+
return Array.from(this.promiseRegistry.values()).filter(
|
|
224
|
+
(p) => p.status === FWSystemDefine.FWPromiseStatus.PENDING,
|
|
225
|
+
).length;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 清理已完成的Promise */
|
|
229
|
+
public clearCompletedPromise(): number {
|
|
230
|
+
const completedIds: number[] = [];
|
|
231
|
+
|
|
232
|
+
this.promiseRegistry.forEach((promiseProxy, id) => {
|
|
233
|
+
if (promiseProxy.status !== FWSystemDefine.FWPromiseStatus.PENDING) {
|
|
234
|
+
completedIds.push(id);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
completedIds.forEach((id) => {
|
|
239
|
+
this.promiseRegistry.delete(id);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return completedIds.length;
|
|
243
|
+
}
|
|
244
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Learn TypeScript:
|
|
2
|
+
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
|
|
3
|
+
// Learn Attribute:
|
|
4
|
+
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
|
|
5
|
+
// Learn life-cycle callbacks:
|
|
6
|
+
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
|
|
7
|
+
|
|
8
|
+
const { ccclass, property } = cc._decorator;
|
|
9
|
+
|
|
10
|
+
@ccclass
|
|
11
|
+
export default class FWAssembler extends cc['Assembler'] {}
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { FWLodash } from '../utils/FWLodash';
|
|
2
|
+
|
|
3
|
+
const { ccclass, property, executeInEditMode, menu } = cc._decorator;
|
|
4
|
+
|
|
5
|
+
@ccclass('AssemblerLabelColorConfig')
|
|
6
|
+
class AssemblerLabelColorConfig implements FW.AssemblerLabelColorConfig {
|
|
7
|
+
@property({ displayName: '颜色' })
|
|
8
|
+
color: cc.Color = cc.Color.WHITE;
|
|
9
|
+
|
|
10
|
+
@property({ type: cc.Float, min: 0, max: 1, displayName: '占比', slide: true })
|
|
11
|
+
percentages: number = 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@ccclass
|
|
15
|
+
@executeInEditMode
|
|
16
|
+
@menu('CustomComponent/FWRenderAssembler')
|
|
17
|
+
export default class FWRenderAssembler extends cc.Component {
|
|
18
|
+
@property({ displayName: '文本自定义渐变色' })
|
|
19
|
+
enableLabelCustomColors: boolean = false;
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
_gradientDirection: number = 0;
|
|
23
|
+
@property({
|
|
24
|
+
type: cc.Enum({ 从左到右: 0, 从上到下: 1 }),
|
|
25
|
+
displayName: '渐变方向',
|
|
26
|
+
visible() {
|
|
27
|
+
return this.enableLabelCustomColors;
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
get gradientDirection() {
|
|
31
|
+
return this._gradientDirection;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set gradientDirection(gradientDirection: number) {
|
|
35
|
+
this._gradientDirection = gradientDirection;
|
|
36
|
+
if (CC_EDITOR) {
|
|
37
|
+
this.adjustLabel();
|
|
38
|
+
this.onPropertyChanged();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
_config: AssemblerLabelColorConfig[] = [];
|
|
44
|
+
|
|
45
|
+
@property({
|
|
46
|
+
type: [AssemblerLabelColorConfig],
|
|
47
|
+
visible() {
|
|
48
|
+
return this.enableLabelCustomColors;
|
|
49
|
+
},
|
|
50
|
+
displayName: '颜色配置',
|
|
51
|
+
})
|
|
52
|
+
get config() {
|
|
53
|
+
return this._config;
|
|
54
|
+
}
|
|
55
|
+
set config(config: AssemblerLabelColorConfig[]) {
|
|
56
|
+
this._config = config;
|
|
57
|
+
if (CC_EDITOR) {
|
|
58
|
+
this.adjustLabel();
|
|
59
|
+
this.onPropertyChanged();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _horizontalAlign: number;
|
|
64
|
+
private _verticalAlign: number;
|
|
65
|
+
private _originalRenderData: any = null;
|
|
66
|
+
private _originalUpdateRenderData: Function = null;
|
|
67
|
+
private _originalFillBuffers: Function = null;
|
|
68
|
+
private _isPatched: boolean = false;
|
|
69
|
+
private _colors: cc.Color[] = [];
|
|
70
|
+
private _percentages: number[] = [];
|
|
71
|
+
private _isOriginalDataSaved: boolean = false;
|
|
72
|
+
private _cachedProperties: any;
|
|
73
|
+
|
|
74
|
+
protected start(): void {
|
|
75
|
+
this.adjustLabel();
|
|
76
|
+
this.config.forEach((v) => {
|
|
77
|
+
this._colors.push(v.color);
|
|
78
|
+
this._percentages.push(v.percentages);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!CC_EDITOR) {
|
|
82
|
+
cc.director.on(cc.Director.EVENT_AFTER_DRAW, this.onUpdateAssembler, this);
|
|
83
|
+
} else {
|
|
84
|
+
this.node.on(cc.Node.EventType.SIZE_CHANGED, this.onRefreshEditor, this);
|
|
85
|
+
this.node.on(cc.Node.EventType.COLOR_CHANGED, this.onRefreshEditor, this);
|
|
86
|
+
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this.onRefreshEditor, this);
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
this.cacheLabelProperties();
|
|
89
|
+
this.onPropertyChanged();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onRefreshEditor() {
|
|
95
|
+
if (CC_EDITOR && this.enableLabelCustomColors) {
|
|
96
|
+
this._originalRenderData = null;
|
|
97
|
+
this._isOriginalDataSaved = false;
|
|
98
|
+
this.node.label['_assembler'] = null;
|
|
99
|
+
this.node.active = false;
|
|
100
|
+
this.node.active = true;
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
this.onPropertyChanged();
|
|
103
|
+
}, 30);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private refreshGradient(): void {
|
|
108
|
+
this._colors = [];
|
|
109
|
+
this._percentages = [];
|
|
110
|
+
this.config.forEach((v) => {
|
|
111
|
+
this._colors.push(v.color);
|
|
112
|
+
this._percentages.push(v.percentages);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this._isPatched = false;
|
|
116
|
+
this.onUpdateAssembler();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private cacheLabelProperties(): void {
|
|
120
|
+
const label = this.node.label;
|
|
121
|
+
if (!label) {
|
|
122
|
+
this._cachedProperties = null;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this._cachedProperties = {
|
|
127
|
+
horizontalAlign: label.horizontalAlign,
|
|
128
|
+
verticalAlign: label.verticalAlign,
|
|
129
|
+
overflow: label.overflow,
|
|
130
|
+
enableWrap: label.enableWrapText,
|
|
131
|
+
fontFamily: label.fontFamily,
|
|
132
|
+
font: label.font ? label.font['_uuid'] : null,
|
|
133
|
+
underlineHeight: label.underlineHeight,
|
|
134
|
+
cacheMode: label.cacheMode,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private checkLabelPropertiesChanged(): boolean {
|
|
139
|
+
const label = this.node.label;
|
|
140
|
+
if (!label || !this._cachedProperties) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const current = {
|
|
145
|
+
horizontalAlign: label.horizontalAlign,
|
|
146
|
+
verticalAlign: label.verticalAlign,
|
|
147
|
+
overflow: label.overflow,
|
|
148
|
+
enableWrap: label.enableWrapText,
|
|
149
|
+
underlineHeight: label.underlineHeight,
|
|
150
|
+
fontFamily: label.fontFamily,
|
|
151
|
+
cacheMode: label.cacheMode,
|
|
152
|
+
font: label.font ? label.font['_uuid'] : null,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const keys = Object.keys(current);
|
|
156
|
+
for (let key of keys) {
|
|
157
|
+
if (current[key] !== this._cachedProperties[key]) {
|
|
158
|
+
this._cachedProperties = current;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected onPropertyChanged(): void {
|
|
167
|
+
if (CC_EDITOR && this.enableLabelCustomColors) {
|
|
168
|
+
this.restoreOriginalAssembler();
|
|
169
|
+
this.refreshGradient();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected onDisable(): void {
|
|
174
|
+
this.restoreOriginalAssembler();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private adjustLabel(): void {
|
|
178
|
+
if (CC_EDITOR && !this.node.label) {
|
|
179
|
+
this.node.label = this.node.getComponent(cc.Label);
|
|
180
|
+
}
|
|
181
|
+
if (this.node.label) {
|
|
182
|
+
if (this.gradientDirection === 1) {
|
|
183
|
+
this.node.label.lineHeight = this.node.label.fontSize * 0.8;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!this._horizontalAlign) {
|
|
187
|
+
this._horizontalAlign = this.node.label.horizontalAlign;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!this._verticalAlign) {
|
|
191
|
+
this._verticalAlign = this.node.label.verticalAlign;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected update(dt: number): void {
|
|
197
|
+
if (!CC_EDITOR) return;
|
|
198
|
+
if (this.checkLabelPropertiesChanged()) {
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
this.adjustLabel();
|
|
201
|
+
this.onRefreshEditor();
|
|
202
|
+
}, 100);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private onUpdateAssembler(): void {
|
|
207
|
+
if (this.enableLabelCustomColors && this.node.label) {
|
|
208
|
+
this.patchAssembler();
|
|
209
|
+
} else {
|
|
210
|
+
this.restoreOriginalAssembler();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private patchAssembler(): void {
|
|
215
|
+
const label = this.node.label;
|
|
216
|
+
if (!label || !label['_assembler'] || this._isPatched) return;
|
|
217
|
+
|
|
218
|
+
const assembler = label['_assembler'];
|
|
219
|
+
|
|
220
|
+
if (!this._isOriginalDataSaved) {
|
|
221
|
+
this._originalRenderData = FWLodash.cloneDeep(assembler);
|
|
222
|
+
this._isOriginalDataSaved = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const colorSegments = this.createColorSegments();
|
|
226
|
+
if (!colorSegments) return;
|
|
227
|
+
|
|
228
|
+
if (!this._originalUpdateRenderData) {
|
|
229
|
+
this._originalUpdateRenderData = assembler.updateRenderData;
|
|
230
|
+
}
|
|
231
|
+
if (!this._originalFillBuffers) {
|
|
232
|
+
this._originalFillBuffers = assembler.fillBuffers;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
assembler.updateRenderData = () => {
|
|
236
|
+
if (this._originalUpdateRenderData) {
|
|
237
|
+
this._originalUpdateRenderData.call(assembler, label);
|
|
238
|
+
}
|
|
239
|
+
this.applyMultiColorGradient(assembler, colorSegments);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
assembler.fillBuffers = (renderer: any, node: any) => {
|
|
243
|
+
this.fillGradientBuffers(renderer, node, assembler);
|
|
244
|
+
};
|
|
245
|
+
if (colorSegments.length > 2) {
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this._isPatched = true;
|
|
249
|
+
assembler.updateRenderData();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private restoreOriginalAssembler(): void {
|
|
253
|
+
if (!this.node.label || !this._isOriginalDataSaved) return;
|
|
254
|
+
|
|
255
|
+
const label = this.node.label;
|
|
256
|
+
if (label['_assembler']) {
|
|
257
|
+
const assembler = label['_assembler'];
|
|
258
|
+
|
|
259
|
+
if (this._originalUpdateRenderData) {
|
|
260
|
+
assembler.updateRenderData = this._originalUpdateRenderData;
|
|
261
|
+
}
|
|
262
|
+
if (this._originalFillBuffers) {
|
|
263
|
+
assembler.fillBuffers = this._originalFillBuffers;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this._originalRenderData) {
|
|
267
|
+
label['_assembler'] = FWLodash.cloneDeep(this._originalRenderData);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this._isPatched = false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private applyMultiColorGradient(
|
|
275
|
+
assembler: any,
|
|
276
|
+
colorSegments: FW.AssemblerLabelColorSegment[],
|
|
277
|
+
): void {
|
|
278
|
+
const renderData = assembler._renderData;
|
|
279
|
+
const originalUintVData = renderData.uintVDatas[0];
|
|
280
|
+
const charsCount = originalUintVData.length / 20;
|
|
281
|
+
const segmentsCount = colorSegments.length - 1;
|
|
282
|
+
const useVertical = this.gradientDirection === 1;
|
|
283
|
+
|
|
284
|
+
const verticesPerChar = segmentsCount * 4;
|
|
285
|
+
const newVertexCount = charsCount * verticesPerChar;
|
|
286
|
+
const newUintVData = new Uint32Array(newVertexCount * 5);
|
|
287
|
+
|
|
288
|
+
let writeIndex = 0;
|
|
289
|
+
|
|
290
|
+
for (let c = 0; c < charsCount; c++) {
|
|
291
|
+
const base = c * 20;
|
|
292
|
+
const vertices = this.getCharacterVertices(originalUintVData, base);
|
|
293
|
+
const bounds = this.calculateBounds(vertices);
|
|
294
|
+
|
|
295
|
+
for (let s = 0; s < segmentsCount; s++) {
|
|
296
|
+
const segment = colorSegments[s];
|
|
297
|
+
const nextSegment = colorSegments[s + 1];
|
|
298
|
+
|
|
299
|
+
if (useVertical) {
|
|
300
|
+
this.createVerticalSegment(
|
|
301
|
+
newUintVData,
|
|
302
|
+
writeIndex,
|
|
303
|
+
vertices,
|
|
304
|
+
bounds,
|
|
305
|
+
segment,
|
|
306
|
+
nextSegment,
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
this.createHorizontalSegment(
|
|
310
|
+
newUintVData,
|
|
311
|
+
writeIndex,
|
|
312
|
+
vertices,
|
|
313
|
+
bounds,
|
|
314
|
+
segment,
|
|
315
|
+
nextSegment,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
writeIndex += 4;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.updateRenderData(renderData, newUintVData, charsCount, segmentsCount);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private getCharacterVertices(uintVData: Uint32Array, baseIndex: number): any[] {
|
|
326
|
+
const vertices = [];
|
|
327
|
+
for (let i = 0; i < 4; i++) {
|
|
328
|
+
const idx = baseIndex + i * 5;
|
|
329
|
+
vertices.push({
|
|
330
|
+
x: this.uintToFloat(uintVData[idx]),
|
|
331
|
+
y: this.uintToFloat(uintVData[idx + 1]),
|
|
332
|
+
u: this.uintToFloat(uintVData[idx + 2]),
|
|
333
|
+
v: this.uintToFloat(uintVData[idx + 3]),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return vertices;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private calculateBounds(vertices: any[]): any {
|
|
340
|
+
const xs = vertices.map((v) => v.x);
|
|
341
|
+
const ys = vertices.map((v) => v.y);
|
|
342
|
+
return {
|
|
343
|
+
minX: Math.min(...xs),
|
|
344
|
+
maxX: Math.max(...xs),
|
|
345
|
+
minY: Math.min(...ys),
|
|
346
|
+
maxY: Math.max(...ys),
|
|
347
|
+
width: Math.max(...xs) - Math.min(...xs),
|
|
348
|
+
height: Math.max(...ys) - Math.min(...ys),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private createHorizontalSegment(
|
|
353
|
+
newUintVData: Uint32Array,
|
|
354
|
+
writeIndex: number,
|
|
355
|
+
vertices: any[],
|
|
356
|
+
bounds: any,
|
|
357
|
+
segment: any,
|
|
358
|
+
nextSegment: any,
|
|
359
|
+
): void {
|
|
360
|
+
const startX = bounds.minX + bounds.width * segment.position;
|
|
361
|
+
const endX = bounds.minX + bounds.width * nextSegment.position;
|
|
362
|
+
const startU = vertices[0].u + (vertices[1].u - vertices[0].u) * segment.position;
|
|
363
|
+
const endU = vertices[0].u + (vertices[1].u - vertices[0].u) * nextSegment.position;
|
|
364
|
+
|
|
365
|
+
this.setVertexData(
|
|
366
|
+
newUintVData,
|
|
367
|
+
writeIndex,
|
|
368
|
+
startX,
|
|
369
|
+
bounds.minY,
|
|
370
|
+
startU,
|
|
371
|
+
vertices[0].v,
|
|
372
|
+
segment.color._val,
|
|
373
|
+
);
|
|
374
|
+
this.setVertexData(
|
|
375
|
+
newUintVData,
|
|
376
|
+
writeIndex + 1,
|
|
377
|
+
endX,
|
|
378
|
+
bounds.minY,
|
|
379
|
+
endU,
|
|
380
|
+
vertices[1].v,
|
|
381
|
+
nextSegment.color._val,
|
|
382
|
+
);
|
|
383
|
+
this.setVertexData(
|
|
384
|
+
newUintVData,
|
|
385
|
+
writeIndex + 2,
|
|
386
|
+
startX,
|
|
387
|
+
bounds.maxY,
|
|
388
|
+
startU,
|
|
389
|
+
vertices[2].v,
|
|
390
|
+
segment.color._val,
|
|
391
|
+
);
|
|
392
|
+
this.setVertexData(
|
|
393
|
+
newUintVData,
|
|
394
|
+
writeIndex + 3,
|
|
395
|
+
endX,
|
|
396
|
+
bounds.maxY,
|
|
397
|
+
endU,
|
|
398
|
+
vertices[3].v,
|
|
399
|
+
nextSegment.color._val,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private createVerticalSegment(
|
|
404
|
+
newUintVData: Uint32Array,
|
|
405
|
+
writeIndex: number,
|
|
406
|
+
vertices: any[],
|
|
407
|
+
bounds: any,
|
|
408
|
+
segment: any,
|
|
409
|
+
nextSegment: any,
|
|
410
|
+
): void {
|
|
411
|
+
const startY = bounds.minY + bounds.height * (1 - segment.position);
|
|
412
|
+
const endY = bounds.minY + bounds.height * (1 - nextSegment.position);
|
|
413
|
+
|
|
414
|
+
const startVL = vertices[0].v + (vertices[2].v - vertices[0].v) * (1 - segment.position);
|
|
415
|
+
const endVL = vertices[0].v + (vertices[2].v - vertices[0].v) * (1 - nextSegment.position);
|
|
416
|
+
const startVR = vertices[1].v + (vertices[3].v - vertices[1].v) * (1 - segment.position);
|
|
417
|
+
const endVR = vertices[1].v + (vertices[3].v - vertices[1].v) * (1 - nextSegment.position);
|
|
418
|
+
|
|
419
|
+
this.setVertexData(
|
|
420
|
+
newUintVData,
|
|
421
|
+
writeIndex,
|
|
422
|
+
bounds.minX,
|
|
423
|
+
startY,
|
|
424
|
+
vertices[0].u,
|
|
425
|
+
startVL,
|
|
426
|
+
segment.color._val,
|
|
427
|
+
);
|
|
428
|
+
this.setVertexData(
|
|
429
|
+
newUintVData,
|
|
430
|
+
writeIndex + 1,
|
|
431
|
+
bounds.maxX,
|
|
432
|
+
startY,
|
|
433
|
+
vertices[1].u,
|
|
434
|
+
startVR,
|
|
435
|
+
segment.color._val,
|
|
436
|
+
);
|
|
437
|
+
this.setVertexData(
|
|
438
|
+
newUintVData,
|
|
439
|
+
writeIndex + 2,
|
|
440
|
+
bounds.minX,
|
|
441
|
+
endY,
|
|
442
|
+
vertices[2].u,
|
|
443
|
+
endVL,
|
|
444
|
+
nextSegment.color._val,
|
|
445
|
+
);
|
|
446
|
+
this.setVertexData(
|
|
447
|
+
newUintVData,
|
|
448
|
+
writeIndex + 3,
|
|
449
|
+
bounds.maxX,
|
|
450
|
+
endY,
|
|
451
|
+
vertices[3].u,
|
|
452
|
+
endVR,
|
|
453
|
+
nextSegment.color._val,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private setVertexData(
|
|
458
|
+
data: Uint32Array,
|
|
459
|
+
index: number,
|
|
460
|
+
x: number,
|
|
461
|
+
y: number,
|
|
462
|
+
u: number,
|
|
463
|
+
v: number,
|
|
464
|
+
color: number,
|
|
465
|
+
): void {
|
|
466
|
+
const base = index * 5;
|
|
467
|
+
data[base] = this.floatToUint(x);
|
|
468
|
+
data[base + 1] = this.floatToUint(y);
|
|
469
|
+
data[base + 2] = this.floatToUint(u);
|
|
470
|
+
data[base + 3] = this.floatToUint(v);
|
|
471
|
+
data[base + 4] = color;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private updateRenderData(
|
|
475
|
+
renderData: any,
|
|
476
|
+
newUintVData: Uint32Array,
|
|
477
|
+
charsCount: number,
|
|
478
|
+
segmentsCount: number,
|
|
479
|
+
): void {
|
|
480
|
+
renderData.uintVDatas[0] = newUintVData;
|
|
481
|
+
renderData.iDatas[0] = this.createQuadMeshIndices(charsCount, segmentsCount);
|
|
482
|
+
renderData.vertexCount = newUintVData.length / 5;
|
|
483
|
+
|
|
484
|
+
const newVData = new Float32Array(renderData.vertexCount * 5);
|
|
485
|
+
for (let i = 0; i < newVData.length; i++) {
|
|
486
|
+
newVData[i] = this.uintToFloat(newUintVData[i]);
|
|
487
|
+
}
|
|
488
|
+
renderData.vDatas[0] = newVData;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private createQuadMeshIndices(charsCount: number, segmentsCount: number): Uint16Array {
|
|
492
|
+
const indicesPerChar = segmentsCount * 6;
|
|
493
|
+
const totalIndices = charsCount * indicesPerChar;
|
|
494
|
+
const indices = new Uint16Array(totalIndices);
|
|
495
|
+
let idx = 0;
|
|
496
|
+
|
|
497
|
+
for (let charIndex = 0; charIndex < charsCount; charIndex++) {
|
|
498
|
+
const vertexOffset = charIndex * segmentsCount * 4;
|
|
499
|
+
|
|
500
|
+
for (let segIndex = 0; segIndex < segmentsCount; segIndex++) {
|
|
501
|
+
const base = vertexOffset + segIndex * 4;
|
|
502
|
+
indices[idx++] = base;
|
|
503
|
+
indices[idx++] = base + 1;
|
|
504
|
+
indices[idx++] = base + 2;
|
|
505
|
+
indices[idx++] = base + 1;
|
|
506
|
+
indices[idx++] = base + 3;
|
|
507
|
+
indices[idx++] = base + 2;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return indices;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private getLabelBounds(uintVData: Uint32Array): any {
|
|
515
|
+
let minX = Number.MAX_VALUE,
|
|
516
|
+
maxX = -Number.MAX_VALUE;
|
|
517
|
+
let minY = Number.MAX_VALUE,
|
|
518
|
+
maxY = -Number.MAX_VALUE;
|
|
519
|
+
|
|
520
|
+
for (let i = 0; i < uintVData.length; i += 5) {
|
|
521
|
+
const x = this.uintToFloat(uintVData[i]);
|
|
522
|
+
const y = this.uintToFloat(uintVData[i + 1]);
|
|
523
|
+
minX = Math.min(minX, x);
|
|
524
|
+
maxX = Math.max(maxX, x);
|
|
525
|
+
minY = Math.min(minY, y);
|
|
526
|
+
maxY = Math.max(maxY, y);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
minX,
|
|
531
|
+
maxX,
|
|
532
|
+
minY,
|
|
533
|
+
maxY,
|
|
534
|
+
width: Math.max(maxX - minX, 1),
|
|
535
|
+
height: Math.max(maxY - minY, 1),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private fillGradientBuffers(renderer: any, node: any, assembler: any): void {
|
|
540
|
+
const renderData = assembler._renderData;
|
|
541
|
+
if (!renderData?.vDatas?.[0] || !renderData?.iDatas?.[0]) return;
|
|
542
|
+
|
|
543
|
+
const buffer = node._buffer;
|
|
544
|
+
const offsetInfo = buffer.request(renderData.vertexCount, renderData.iDatas[0].length);
|
|
545
|
+
|
|
546
|
+
const vertexOffset = offsetInfo.byteOffset >> 2;
|
|
547
|
+
buffer._vData.set(renderData.vDatas[0], vertexOffset);
|
|
548
|
+
|
|
549
|
+
const ibuf = buffer._iData;
|
|
550
|
+
const vertexId = offsetInfo.vertexOffset;
|
|
551
|
+
|
|
552
|
+
renderData.iDatas[0].forEach((index, i) => {
|
|
553
|
+
ibuf[offsetInfo.indiceOffset + i] = vertexId + index;
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private createColorSegments(): FW.AssemblerLabelColorSegment[] | null {
|
|
558
|
+
if (!this._colors || this._colors.length < 2) return null;
|
|
559
|
+
|
|
560
|
+
const percentages = this.calculatePercentages();
|
|
561
|
+
const segments: FW.AssemblerLabelColorSegment[] = [];
|
|
562
|
+
|
|
563
|
+
for (let i = 0; i < this._colors.length; i++) {
|
|
564
|
+
segments.push({
|
|
565
|
+
color: this.cloneColor(this._colors[i]),
|
|
566
|
+
position: percentages[i],
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return segments;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
private calculatePercentages(): number[] {
|
|
574
|
+
if (this._percentages?.length === this._colors.length) {
|
|
575
|
+
const percentages = [...this._percentages];
|
|
576
|
+
percentages[0] = 0;
|
|
577
|
+
percentages[percentages.length - 1] = 1;
|
|
578
|
+
|
|
579
|
+
for (let i = 1; i < percentages.length - 1; i++) {
|
|
580
|
+
if (percentages[i] <= percentages[i - 1]) {
|
|
581
|
+
percentages[i] = percentages[i - 1] + 0.001;
|
|
582
|
+
}
|
|
583
|
+
if (percentages[i] >= percentages[i + 1]) {
|
|
584
|
+
percentages[i] = (percentages[i - 1] + percentages[i + 1]) / 2;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return percentages;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return Array.from({ length: this._colors.length }, (_, i) => i / (this._colors.length - 1));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private cloneColor(color: cc.Color): cc.Color {
|
|
594
|
+
return color.clone ? color.clone() : cc.color(color.r, color.g, color.b, color.a);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private interpolateColor(color1: cc.Color, color2: cc.Color, progress: number): cc.Color {
|
|
598
|
+
const r = Math.round(color1.r + (color2.r - color1.r) * progress);
|
|
599
|
+
const g = Math.round(color1.g + (color2.g - color1.g) * progress);
|
|
600
|
+
const b = Math.round(color1.b + (color2.b - color1.b) * progress);
|
|
601
|
+
const a = Math.round(color1.a + (color2.a - color1.a) * progress);
|
|
602
|
+
return cc.color(r, g, b, a);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private uintToFloat(uintValue: number): number {
|
|
606
|
+
const buffer = new ArrayBuffer(4);
|
|
607
|
+
const uintView = new Uint32Array(buffer);
|
|
608
|
+
const floatView = new Float32Array(buffer);
|
|
609
|
+
uintView[0] = uintValue;
|
|
610
|
+
return floatView[0];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private floatToUint(floatValue: number): number {
|
|
614
|
+
const buffer = new ArrayBuffer(4);
|
|
615
|
+
const floatView = new Float32Array(buffer);
|
|
616
|
+
const uintView = new Uint32Array(buffer);
|
|
617
|
+
floatView[0] = floatValue;
|
|
618
|
+
return uintView[0];
|
|
619
|
+
}
|
|
620
|
+
}
|
package/render.meta
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ver": "1.1.3",
|
|
3
|
+
"uuid": "d49ee3cc-a25e-4c14-8a04-5f22d03b8164",
|
|
4
|
+
"importer": "folder",
|
|
5
|
+
"isBundle": false,
|
|
6
|
+
"bundleName": "",
|
|
7
|
+
"priority": 1,
|
|
8
|
+
"compressionType": {},
|
|
9
|
+
"optimizeHotUpdate": {},
|
|
10
|
+
"inlineSpriteFrames": {},
|
|
11
|
+
"isRemoteBundle": {},
|
|
12
|
+
"subMetas": {}
|
|
13
|
+
}
|