401 lines
11 KiB
TypeScript
401 lines
11 KiB
TypeScript
import { _decorator, Director, director, Label, Node, Overflow, RichText, tween, UITransform, Vec3 } from 'cc';
|
|
import { StaticTxt } from './StaticTxt';
|
|
const { ccclass, property } = _decorator;
|
|
|
|
export class Tools {
|
|
private static _instance: Tools;
|
|
|
|
ctx: any = null
|
|
|
|
public static get instance(): Tools {
|
|
if (!this._instance) {
|
|
this._instance = new Tools();
|
|
|
|
let canvas = document.createElement('canvas');
|
|
this._instance.ctx = canvas.getContext('2d')!;
|
|
}
|
|
|
|
return this._instance;
|
|
}
|
|
|
|
}
|
|
|
|
/**RichText元素*/
|
|
interface Segment {
|
|
type: "label" | "image";
|
|
value: string;
|
|
}
|
|
|
|
interface WrapRes {
|
|
result: string;
|
|
lastLineWidth: number;
|
|
}
|
|
|
|
interface LangTxt {
|
|
labels: Label[]; // Label 组件数组
|
|
richTexts: RichText[]; // RichText 组件数组
|
|
}
|
|
|
|
export function fixNum(num: number): string {
|
|
return (num / 10000).toFixed(2)
|
|
}
|
|
|
|
// 需要代码计算空格处理换行的婆罗米系文字
|
|
export function isBrahmic(lang: string) {
|
|
let arr = ["th", "my"]
|
|
return arr.indexOf(lang) != -1
|
|
}
|
|
|
|
export function getLabelsAndRichTexts(node: Node, langText: LangTxt): LangTxt {
|
|
|
|
|
|
const label = node.getComponent(Label);
|
|
const richText = node.getComponent(RichText);
|
|
|
|
const staticTxt = node.getComponent(StaticTxt)
|
|
if (label && staticTxt) {
|
|
langText.labels.push(label);
|
|
}
|
|
if (richText && staticTxt) {
|
|
langText.richTexts.push(richText);
|
|
}
|
|
// 遍历所有子节点
|
|
node.children.forEach(child => {
|
|
getLabelsAndRichTexts(child, langText);
|
|
});
|
|
|
|
return langText
|
|
}
|
|
|
|
/**拿到节点下所有需要翻译的, 挂载了StaticTxt脚本的 Label*/
|
|
export function getAllLabels(node: Node, result: Label[] = []): Label[] {
|
|
const label = node.getComponent(Label);
|
|
const staticTxt = node.getComponent(StaticTxt)
|
|
if (label && staticTxt) {
|
|
result.push(label);
|
|
}
|
|
|
|
// 遍历所有子节点
|
|
node.children.forEach(child => {
|
|
getAllLabels(child, result);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**拿到节点下所有需要翻译的, 挂载了StaticTxt脚本的 Label*/
|
|
export function getAllRichTexts(node: Node, result: RichText[] = []): RichText[] {
|
|
const richText = node.getComponent(RichText);
|
|
const staticTxt = node.getComponent(StaticTxt)
|
|
if (richText && staticTxt) {
|
|
result.push(richText);
|
|
}
|
|
|
|
// 遍历所有子节点
|
|
node.children.forEach(child => {
|
|
getAllRichTexts(child, result);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
export function getTranslate(dict, key) {
|
|
let str = dict[key] ?? key
|
|
return str;
|
|
}
|
|
|
|
export function getTimezoneOffsetString(): string {
|
|
|
|
const pad = (n: number) => (n < 10 ? '0' + n : String(n));
|
|
|
|
const offset = -new Date().getTimezoneOffset(); // 单位是分钟,注意要取反
|
|
const sign = offset >= 0 ? "+" : "-";
|
|
const hours = pad(Math.floor(Math.abs(offset) / 60))
|
|
const minutes = pad(Math.abs(offset) % 60);
|
|
|
|
return `(GMT${sign}${hours}:${minutes})`;
|
|
}
|
|
|
|
export function updateLang(node: Node, dict, lang: string) {
|
|
|
|
let langText: LangTxt = { labels: [], richTexts: [] }
|
|
getLabelsAndRichTexts(node, langText)
|
|
|
|
langText.labels.forEach((label, i) => {
|
|
// console.log(`label.node.name: ${label.node.name}`)
|
|
|
|
const staticTxt = label.node.getComponent(StaticTxt)
|
|
const bResize = label.overflow == Overflow.RESIZE_HEIGHT
|
|
|
|
let key = staticTxt?.key
|
|
if (dict[key]) {
|
|
|
|
if (isBrahmic(lang) && bResize) {
|
|
label.string = wrapTextBySpace(label, dict[key])
|
|
}
|
|
else {
|
|
label.string = dict[key]
|
|
}
|
|
|
|
if (label.node.activeInHierarchy) {
|
|
label.updateRenderData()
|
|
}
|
|
}
|
|
})
|
|
|
|
langText.richTexts.forEach((richText, i) => {
|
|
|
|
const staticTxt = richText.node.getComponent(StaticTxt)
|
|
richText.maxWidth = staticTxt.maxWidth
|
|
richText.string = wrapRichTextBySpace(richText, staticTxt.key, dict, lang)
|
|
})
|
|
|
|
return langText
|
|
}
|
|
|
|
export function wrapRichTextBySpace(richText: RichText, key: string, dict, lang): string {
|
|
|
|
// key like this: Label_Rules_6_14_2 <img src='dates' /> Label_Rules_6_14_3
|
|
const segmentArr: Segment[] = [];
|
|
const regex = /Label_[^<\s]+|<img[^>]+>/g;
|
|
let match: RegExpExecArray | null;
|
|
while ((match = regex.exec(key)) !== null) {
|
|
if (match[0].startsWith("Label_")) {
|
|
|
|
let key = match[0].split("Label_")[1]
|
|
if (dict[key]) {
|
|
segmentArr.push({ type: "label", value: dict[key] });
|
|
}
|
|
|
|
} else {
|
|
segmentArr.push({ type: "image", value: match[0] }); // "<img src='dates' />"
|
|
}
|
|
}
|
|
|
|
// console.log("segmentArr:", segmentArr);
|
|
|
|
/*
|
|
segmentArr:
|
|
[
|
|
{
|
|
"type": "label",
|
|
"value": "Tap"
|
|
},
|
|
{
|
|
"type": "image",
|
|
"value": "<img src='dates' />"
|
|
},
|
|
{
|
|
"type": "label",
|
|
"value": "to select the dates of games to be shown in History."
|
|
}
|
|
]
|
|
*/
|
|
|
|
let wrapResult: WrapRes = { result: "", lastLineWidth: 0 }
|
|
segmentArr.forEach((part, i) => {
|
|
|
|
if (isBrahmic(lang)) {
|
|
if (part.type == "label") {
|
|
wrapResult = wrapTextByRichTextWidth(richText, part.value, wrapResult)
|
|
} else if (part.type == "image") {
|
|
|
|
let img = part.value.match(/src=['"]([^'"]+)['"]/)
|
|
const spriteFrame = richText.imageAtlas.getSpriteFrame(img[1]);
|
|
|
|
if (spriteFrame) {
|
|
const rect = spriteFrame.rect; // 原始图片矩形
|
|
// console.log("原始图片宽度:", rect.width);
|
|
|
|
// 图片宽度 + 最后一行宽度 比 最大宽度大,图片换行放到下一行去
|
|
if (wrapResult.lastLineWidth + rect.width > richText.maxWidth) {
|
|
wrapResult.result += "\n" + part.value
|
|
wrapResult.lastLineWidth = rect.width
|
|
} else {
|
|
// 放得下就放在最后一行
|
|
wrapResult.result += part.value
|
|
wrapResult.lastLineWidth += rect.width
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// 非婆罗米系文字,引擎自动换行就能处理好
|
|
// 不需要手动计算
|
|
wrapResult.result += part.value
|
|
}
|
|
|
|
})
|
|
|
|
return wrapResult.result
|
|
}
|
|
|
|
|
|
// 富文本换行
|
|
export function wrapTextByRichTextWidth(richText: RichText, str: string, lastWrapResult: WrapRes = { result: "", lastLineWidth: 0 }): WrapRes {
|
|
const fontSize = richText.fontSize;
|
|
const fontFamily = richText.fontFamily ?? 'Arial'
|
|
const maxWidth = richText.maxWidth // richText 最大宽度
|
|
|
|
let wrapResult = lastWrapResult;
|
|
|
|
Tools.instance.ctx.font = `${fontSize}px ${fontFamily}`;
|
|
|
|
const words = str.split(' ')
|
|
let line = '';
|
|
let result = '';
|
|
|
|
let endIdx = words.length - 1
|
|
for (let i = 0; i < words.length; i++) {
|
|
let word = words[i]
|
|
|
|
// console.log("word:", word)
|
|
|
|
let testLine = line + " " + word
|
|
|
|
const width = wrapResult.lastLineWidth + Tools.instance.ctx.measureText(testLine).width;
|
|
|
|
// console.log("width = ", width, "maxWidth = ", maxWidth)
|
|
|
|
|
|
// 长度超了放到下一行去
|
|
if (width + 5 > maxWidth && line.length > 0) {
|
|
if (i == endIdx) {
|
|
|
|
// console.log("最后一个单词长度超了")
|
|
|
|
|
|
result += line + '\n' + word;
|
|
wrapResult.lastLineWidth = Tools.instance.ctx.measureText(word).width;
|
|
} else {
|
|
|
|
// console.log("单词长度超了,换行")
|
|
|
|
|
|
result += line + '\n';
|
|
line = word
|
|
wrapResult.lastLineWidth = 0
|
|
}
|
|
} else {
|
|
if (i == endIdx) {
|
|
|
|
// console.log("最后一个单词,拼在屁股后面")
|
|
|
|
|
|
result += testLine;
|
|
wrapResult.lastLineWidth = Tools.instance.ctx.measureText(testLine).width;
|
|
|
|
} else {
|
|
|
|
// console.log("拼在屁股后面")
|
|
|
|
line = testLine;
|
|
}
|
|
}
|
|
}
|
|
|
|
// console.log("循环结束, ", result)
|
|
|
|
wrapResult.result += result
|
|
|
|
return wrapResult;
|
|
}
|
|
|
|
|
|
|
|
// 按单词换行
|
|
export function wrapTextBySpace(label: Label, str: string): string {
|
|
|
|
|
|
const fontSize = label.fontSize;
|
|
const fontFamily = label.fontFamily ?? 'Arial'
|
|
const maxWidth = label.node.getComponent(UITransform).width; // Label 最大宽度
|
|
|
|
const words = str.split(' ')
|
|
|
|
let line = '';
|
|
let result = '';
|
|
// const canvas = document.createElement('canvas');
|
|
// const ctx = canvas.getContext('2d')!;
|
|
Tools.instance.ctx.font = `${fontSize}px ${fontFamily}`;
|
|
|
|
let endIdx = words.length - 1
|
|
for (let i = 0; i < words.length; i++) {
|
|
let word = words[i]
|
|
|
|
// console.log("word = ", word)
|
|
|
|
let testLine = line + " " + word
|
|
|
|
const width = Tools.instance.ctx.measureText(testLine).width;
|
|
|
|
// 长度超了放到下一行去
|
|
if (width > maxWidth && line.length > 0) {
|
|
if (i == endIdx) {
|
|
result += line + '\n' + word;
|
|
} else {
|
|
result += line + '\n';
|
|
line = word
|
|
}
|
|
} else {
|
|
if (i == endIdx) {
|
|
result += testLine;
|
|
} else {
|
|
line = testLine;
|
|
}
|
|
}
|
|
}
|
|
|
|
// console.log("循环结束, ", result)
|
|
|
|
return result;
|
|
}
|
|
|
|
export function waitNextFrame(): Promise<void> {
|
|
return new Promise(resolve => {
|
|
director.once(Director.EVENT_BEFORE_UPDATE, () => resolve());
|
|
});
|
|
}
|
|
|
|
export function hideAllChildren(node: Node) {
|
|
node.children.forEach(child => {
|
|
child.active = false
|
|
})
|
|
}
|
|
|
|
export function showAllChildren(node: Node) {
|
|
node.children.forEach(child => {
|
|
child.active = true
|
|
})
|
|
}
|
|
|
|
export function showFromBottom(node: Node) {
|
|
if (!node) {
|
|
return
|
|
}
|
|
let height = node.getComponent(UITransform)?.height
|
|
if (height) {
|
|
height = 1920;
|
|
}
|
|
|
|
node.position = new Vec3(0, -height / 2)
|
|
tween(node).to(0.15, { position: new Vec3() }).start()
|
|
}
|
|
|
|
export function hideToBottom(node: Node, callFuc = null) {
|
|
if (!node) {
|
|
return
|
|
}
|
|
let height = node.getComponent(UITransform)?.height
|
|
if (height) {
|
|
height = 1920;
|
|
}
|
|
|
|
let endPos = new Vec3(0, -height)
|
|
tween(node).to(0.15, { position: endPos }).call(() => {
|
|
if (callFuc) {
|
|
callFuc()
|
|
}
|
|
}).start()
|
|
} |