mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +08:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			b0e420c167
			...
			d0dae40a5a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0dae40a5a | |||
| 72f02726ba | |||
| bca679d4b1 | |||
| df993a7242 | 
@ -1117,7 +1117,7 @@ actions.prototype._clickAction_text = function () {
 | 
				
			|||||||
    // 正在淡入淡出的话不执行
 | 
					    // 正在淡入淡出的话不执行
 | 
				
			||||||
    if (core.status.event.animateUI) return;
 | 
					    if (core.status.event.animateUI) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const Store = Mota.require('module', 'Render').TextboxStore;
 | 
					    const Store = Mota.require('module', 'MainUI').TextboxStore;
 | 
				
			||||||
    const store = Store.get('main-textbox');
 | 
					    const store = Store.get('main-textbox');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // var data = core.clone(core.status.event.data.current);
 | 
					    // var data = core.clone(core.status.event.data.current);
 | 
				
			||||||
 | 
				
			|||||||
@ -1553,7 +1553,7 @@ events.prototype.__action_doAsyncFunc = function (isAsync, func) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
events.prototype._action_text = function (data, x, y, prefix) {
 | 
					events.prototype._action_text = function (data, x, y, prefix) {
 | 
				
			||||||
    if (this.__action_checkReplaying()) return;
 | 
					    if (this.__action_checkReplaying()) return;
 | 
				
			||||||
    const Store = Mota.require('module', 'Render').TextboxStore;
 | 
					    const Store = Mota.require('module', 'MainUI').TextboxStore;
 | 
				
			||||||
    const store = Store.get('main-textbox');
 | 
					    const store = Store.get('main-textbox');
 | 
				
			||||||
    const { text } = data;
 | 
					    const { text } = data;
 | 
				
			||||||
    let title = '';
 | 
					    let title = '';
 | 
				
			||||||
@ -1594,10 +1594,10 @@ events.prototype._action_text = function (data, x, y, prefix) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const showTitle =
 | 
					    const showText = text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
 | 
				
			||||||
        text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
 | 
					 | 
				
			||||||
    store.show();
 | 
					    store.show();
 | 
				
			||||||
    store.modify({ text: showTitle, title });
 | 
					    store.modify({ title });
 | 
				
			||||||
 | 
					    store.setText(showText);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // data.text = core.replaceText(data.text, prefix);
 | 
					    // data.text = core.replaceText(data.text, prefix);
 | 
				
			||||||
    // var ctx = data.code ? '__text__' + data.code : null;
 | 
					    // var ctx = data.code ? '__text__' + data.code : null;
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ function checkSupport() {
 | 
				
			|||||||
        sleep(3000).then(() => {
 | 
					        sleep(3000).then(() => {
 | 
				
			||||||
            tip(
 | 
					            tip(
 | 
				
			||||||
                'warning',
 | 
					                'warning',
 | 
				
			||||||
                `您的浏览器不支持WebGL,大部分特效将会无法显示,建议使用新版浏览器`
 | 
					                `您的浏览器不支持WebGL,大部分效果将会无法显示,请更新你的浏览器`
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -21,7 +21,7 @@ function checkSupport() {
 | 
				
			|||||||
        sleep(3000).then(() => {
 | 
					        sleep(3000).then(() => {
 | 
				
			||||||
            tip(
 | 
					            tip(
 | 
				
			||||||
                'warning',
 | 
					                'warning',
 | 
				
			||||||
                `您的浏览器不支持WebGL2,一部分特效将会无法显示,建议使用新版浏览器`
 | 
					                `您的浏览器不支持WebGL2,大部分效果将会无法显示,请更新你的浏览器`
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -54,10 +54,10 @@ type UniformFunc<
 | 
				
			|||||||
type UniformBinderValue<N extends UniformBinderNum> = N extends 1
 | 
					type UniformBinderValue<N extends UniformBinderNum> = N extends 1
 | 
				
			||||||
    ? number
 | 
					    ? number
 | 
				
			||||||
    : N extends 2
 | 
					    : N extends 2
 | 
				
			||||||
    ? [number, number]
 | 
					      ? [number, number]
 | 
				
			||||||
    : N extends 3
 | 
					      : N extends 3
 | 
				
			||||||
    ? [number, number, number]
 | 
					        ? [number, number, number]
 | 
				
			||||||
    : [number, number, number, number];
 | 
					        : [number, number, number, number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface UniformBinder<
 | 
					interface UniformBinder<
 | 
				
			||||||
    N extends UniformBinderNum,
 | 
					    N extends UniformBinderNum,
 | 
				
			||||||
 | 
				
			|||||||
@ -80,9 +80,9 @@ export class Container<E extends EContainerEvent = EContainerEvent>
 | 
				
			|||||||
    append(parent: RenderItem): void {
 | 
					    append(parent: RenderItem): void {
 | 
				
			||||||
        super.append(parent);
 | 
					        super.append(parent);
 | 
				
			||||||
        if (this.root) {
 | 
					        if (this.root) {
 | 
				
			||||||
 | 
					            const root = this.root;
 | 
				
			||||||
            this.forEachChild(ele => {
 | 
					            this.forEachChild(ele => {
 | 
				
			||||||
                ele.checkRoot();
 | 
					                ele.setRoot(root);
 | 
				
			||||||
                this.root?.connect(ele);
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -104,6 +104,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
 | 
				
			|||||||
        this.sortedChildren = [...this.children].sort(
 | 
					        this.sortedChildren = [...this.children].sort(
 | 
				
			||||||
            (a, b) => a.zIndex - b.zIndex
 | 
					            (a, b) => a.zIndex - b.zIndex
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        this.update();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected propagateEvent<T extends ActionType>(
 | 
					    protected propagateEvent<T extends ActionType>(
 | 
				
			||||||
 | 
				
			|||||||
@ -437,6 +437,15 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
 | 
				
			|||||||
        return canvas;
 | 
					        return canvas;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 删除由 `requireCanvas` 申请的画布,当画布不再使用时,可以用该方法删除画布
 | 
				
			||||||
 | 
					     * @param canvas 要删除的画布
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected deleteCanvas(canvas: MotaOffscreenCanvas2D) {
 | 
				
			||||||
 | 
					        if (!this.canvases.delete(canvas)) return;
 | 
				
			||||||
 | 
					        canvas.delete();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //#region 修改元素属性
 | 
					    //#region 修改元素属性
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -457,8 +466,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
 | 
				
			|||||||
     * @param y 纵坐标
 | 
					     * @param y 纵坐标
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    pos(x: number, y: number) {
 | 
					    pos(x: number, y: number) {
 | 
				
			||||||
 | 
					        // 这个函数会调用 update,因此不再手动调用 update
 | 
				
			||||||
        this._transform.setTranslate(x, y);
 | 
					        this._transform.setTranslate(x, y);
 | 
				
			||||||
        this.update();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -588,10 +597,15 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    update(item: RenderItem<any> = this): void {
 | 
					    update(item: RenderItem<any> = this): void {
 | 
				
			||||||
        if (this.cacheDirty) return;
 | 
					        if (this._parent) {
 | 
				
			||||||
        this.cacheDirty = true;
 | 
					            if (this.cacheDirty && this._parent.cacheDirty) return;
 | 
				
			||||||
        if (this.hidden) return;
 | 
					            this.cacheDirty = true;
 | 
				
			||||||
        this.parent?.update(item);
 | 
					            if (this.hidden) return;
 | 
				
			||||||
 | 
					            this._parent.update(item);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if (this.cacheDirty) return;
 | 
				
			||||||
 | 
					            this.cacheDirty = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updateTransform() {
 | 
					    updateTransform() {
 | 
				
			||||||
@ -650,6 +664,12 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    //#region 父子关系
 | 
					    //#region 父子关系
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setRoot(item: RenderItem & IRenderTreeRoot) {
 | 
				
			||||||
 | 
					        this._root?.disconnect(this);
 | 
				
			||||||
 | 
					        this._root = item;
 | 
				
			||||||
 | 
					        item.connect(item);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    checkRoot(): RenderItem | null {
 | 
					    checkRoot(): RenderItem | null {
 | 
				
			||||||
        if (this._root) return this._root;
 | 
					        if (this._root) return this._root;
 | 
				
			||||||
        if (this.isRoot) return this;
 | 
					        if (this.isRoot) return this;
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,10 @@ export class Text extends RenderItem<ETextEvent> {
 | 
				
			|||||||
        super(type, false);
 | 
					        super(type, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.text = text;
 | 
					        this.text = text;
 | 
				
			||||||
        if (text.length > 0) this.calBox();
 | 
					        if (text.length > 0) {
 | 
				
			||||||
 | 
					            this.calBox();
 | 
				
			||||||
 | 
					            this.emit('setText', text);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected render(
 | 
					    protected render(
 | 
				
			||||||
@ -70,7 +73,7 @@ export class Text extends RenderItem<ETextEvent> {
 | 
				
			|||||||
    setText(text: string) {
 | 
					    setText(text: string) {
 | 
				
			||||||
        this.text = text;
 | 
					        this.text = text;
 | 
				
			||||||
        this.calBox();
 | 
					        this.calBox();
 | 
				
			||||||
        if (this.parent) this.update(this);
 | 
					        this.update(this);
 | 
				
			||||||
        this.emit('setText', text);
 | 
					        this.emit('setText', text);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,7 +84,7 @@ export class Text extends RenderItem<ETextEvent> {
 | 
				
			|||||||
    setFont(font: string) {
 | 
					    setFont(font: string) {
 | 
				
			||||||
        this.font = font;
 | 
					        this.font = font;
 | 
				
			||||||
        this.calBox();
 | 
					        this.calBox();
 | 
				
			||||||
        if (this.parent) this.update(this);
 | 
					        this.update(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -110,7 +113,11 @@ export class Text extends RenderItem<ETextEvent> {
 | 
				
			|||||||
            this.measure();
 | 
					            this.measure();
 | 
				
			||||||
        this.length = width;
 | 
					        this.length = width;
 | 
				
			||||||
        this.descent = actualBoundingBoxAscent;
 | 
					        this.descent = actualBoundingBoxAscent;
 | 
				
			||||||
        this.size(width, actualBoundingBoxAscent + actualBoundingBoxDescent);
 | 
					        this.size(
 | 
				
			||||||
 | 
					            width,
 | 
				
			||||||
 | 
					            Math.abs(actualBoundingBoxAscent) +
 | 
				
			||||||
 | 
					                Math.abs(actualBoundingBoxDescent)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected handleProps(
 | 
					    protected handleProps(
 | 
				
			||||||
@ -132,7 +139,7 @@ export class Text extends RenderItem<ETextEvent> {
 | 
				
			|||||||
            case 'font':
 | 
					            case 'font':
 | 
				
			||||||
                if (!this.assertType(nextValue, 'string', key)) return false;
 | 
					                if (!this.assertType(nextValue, 'string', key)) return false;
 | 
				
			||||||
                this.setFont(nextValue);
 | 
					                this.setFont(nextValue);
 | 
				
			||||||
                break;
 | 
					                return true;
 | 
				
			||||||
            case 'strokeWidth':
 | 
					            case 'strokeWidth':
 | 
				
			||||||
                this.setStrokeWidth(nextValue);
 | 
					                this.setStrokeWidth(nextValue);
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
@ -338,14 +345,27 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface WinskinPatterns {
 | 
				
			||||||
 | 
					    top: CanvasPattern;
 | 
				
			||||||
 | 
					    left: CanvasPattern;
 | 
				
			||||||
 | 
					    bottom: CanvasPattern;
 | 
				
			||||||
 | 
					    right: CanvasPattern;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface EWinskinEvent extends ERenderItemEvent {}
 | 
					export interface EWinskinEvent extends ERenderItemEvent {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Winskin extends RenderItem<EWinskinEvent> {
 | 
					export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			||||||
    image: SizedCanvasImageSource;
 | 
					    image: SizedCanvasImageSource;
 | 
				
			||||||
    /** 边框宽度 */
 | 
					    /** 边框宽度,32表示原始宽度 */
 | 
				
			||||||
    borderSize: number = 32;
 | 
					    borderSize: number = 32;
 | 
				
			||||||
 | 
					    /** 图片名称 */
 | 
				
			||||||
 | 
					    imageName?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private pendingImage?: ImageIds;
 | 
					    private pendingImage?: ImageIds;
 | 
				
			||||||
 | 
					    private patternCache?: WinskinPatterns;
 | 
				
			||||||
 | 
					    private patternTransform: DOMMatrix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static patternMap: Map<string, WinskinPatterns> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        image: SizedCanvasImageSource,
 | 
					        image: SizedCanvasImageSource,
 | 
				
			||||||
@ -353,6 +373,63 @@ export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        super(type, false, false);
 | 
					        super(type, false, false);
 | 
				
			||||||
        this.image = image;
 | 
					        this.image = image;
 | 
				
			||||||
 | 
					        this.setAntiAliasing(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (window.DOMMatrix) {
 | 
				
			||||||
 | 
					            this.patternTransform = new DOMMatrix();
 | 
				
			||||||
 | 
					        } else if (window.WebKitCSSMatrix) {
 | 
				
			||||||
 | 
					            this.patternTransform = new WebKitCSSMatrix();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.patternTransform = new SVGMatrix();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private generatePattern() {
 | 
				
			||||||
 | 
					        const pattern = this.requireCanvas();
 | 
				
			||||||
 | 
					        const img = this.image;
 | 
				
			||||||
 | 
					        pattern.size(32, 16);
 | 
				
			||||||
 | 
					        pattern.withGameScale(false);
 | 
				
			||||||
 | 
					        pattern.setHD(false);
 | 
				
			||||||
 | 
					        pattern.setAntiAliasing(false);
 | 
				
			||||||
 | 
					        const ctx = pattern.ctx;
 | 
				
			||||||
 | 
					        ctx.drawImage(img, 144, 0, 32, 16, 0, 0, 32, 16);
 | 
				
			||||||
 | 
					        const topPattern = ctx.createPattern(pattern.canvas, 'repeat');
 | 
				
			||||||
 | 
					        ctx.clearRect(0, 0, 32, 16);
 | 
				
			||||||
 | 
					        ctx.drawImage(img, 144, 48, 32, 16, 0, 0, 32, 16);
 | 
				
			||||||
 | 
					        const bottomPattern = ctx.createPattern(pattern.canvas, 'repeat');
 | 
				
			||||||
 | 
					        ctx.clearRect(0, 0, 32, 16);
 | 
				
			||||||
 | 
					        pattern.size(16, 32);
 | 
				
			||||||
 | 
					        ctx.drawImage(img, 128, 16, 16, 32, 0, 0, 16, 32);
 | 
				
			||||||
 | 
					        const leftPattern = ctx.createPattern(pattern.canvas, 'repeat');
 | 
				
			||||||
 | 
					        ctx.clearRect(0, 0, 16, 32);
 | 
				
			||||||
 | 
					        ctx.drawImage(img, 176, 16, 16, 32, 0, 0, 16, 32);
 | 
				
			||||||
 | 
					        const rightPattern = ctx.createPattern(pattern.canvas, 'repeat');
 | 
				
			||||||
 | 
					        if (!topPattern || !bottomPattern || !leftPattern || !rightPattern) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const winskinPattern: WinskinPatterns = {
 | 
				
			||||||
 | 
					            top: topPattern,
 | 
				
			||||||
 | 
					            bottom: bottomPattern,
 | 
				
			||||||
 | 
					            left: leftPattern,
 | 
				
			||||||
 | 
					            right: rightPattern
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if (this.imageName) {
 | 
				
			||||||
 | 
					            Winskin.patternMap.set(this.imageName, winskinPattern);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.patternCache = winskinPattern;
 | 
				
			||||||
 | 
					        this.deleteCanvas(pattern);
 | 
				
			||||||
 | 
					        return winskinPattern;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private getPattern() {
 | 
				
			||||||
 | 
					        if (!this.imageName) {
 | 
				
			||||||
 | 
					            if (this.patternCache) return this.patternCache;
 | 
				
			||||||
 | 
					            return this.generatePattern();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const pattern = Winskin.patternMap.get(this.imageName);
 | 
				
			||||||
 | 
					            if (pattern) return pattern;
 | 
				
			||||||
 | 
					            return this.generatePattern();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected render(
 | 
					    protected render(
 | 
				
			||||||
@ -361,143 +438,38 @@ export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			|||||||
    ): void {
 | 
					    ): void {
 | 
				
			||||||
        const ctx = canvas.ctx;
 | 
					        const ctx = canvas.ctx;
 | 
				
			||||||
        const img = this.image;
 | 
					        const img = this.image;
 | 
				
			||||||
        const x = 0;
 | 
					        const w = this.width;
 | 
				
			||||||
        const y = 0;
 | 
					        const h = this.height;
 | 
				
			||||||
        const w = canvas.width;
 | 
					        const pad = this.borderSize / 2;
 | 
				
			||||||
        const h = canvas.height;
 | 
					        // 背景
 | 
				
			||||||
        const sz = this.borderSize / 32;
 | 
					        ctx.drawImage(img, 0, 0, 128, 128, 2, 2, w - 4, h - 4);
 | 
				
			||||||
        ctx.drawImage(img, 0, 0, 128, 128, x + 2, y + 2, w - 4, h - 4);
 | 
					        const pattern = this.getPattern();
 | 
				
			||||||
        ctx.drawImage(img, 128, 0, 16, 16, x, y, 16 * sz, 16 * sz);
 | 
					        if (!pattern) return;
 | 
				
			||||||
        let dx;
 | 
					        const { top, left, right, bottom } = pattern;
 | 
				
			||||||
        for (dx = 0; dx < w - 64 * sz; dx += 32 * sz) {
 | 
					        top.setTransform(this.patternTransform);
 | 
				
			||||||
            ctx.drawImage(
 | 
					        left.setTransform(this.patternTransform);
 | 
				
			||||||
                img,
 | 
					        right.setTransform(this.patternTransform);
 | 
				
			||||||
                144,
 | 
					        bottom.setTransform(this.patternTransform);
 | 
				
			||||||
                0,
 | 
					        // 上下左右边框
 | 
				
			||||||
                32,
 | 
					        ctx.save();
 | 
				
			||||||
                16,
 | 
					        ctx.fillStyle = top;
 | 
				
			||||||
                x + dx + 16,
 | 
					        ctx.translate(pad, 0);
 | 
				
			||||||
                y,
 | 
					        ctx.fillRect(0, 0, w - pad * 2, pad);
 | 
				
			||||||
                32 * sz,
 | 
					        ctx.fillStyle = bottom;
 | 
				
			||||||
                16 * sz
 | 
					        ctx.translate(0, h - pad);
 | 
				
			||||||
            );
 | 
					        ctx.fillRect(0, 0, w - pad * 2, pad);
 | 
				
			||||||
            ctx.drawImage(
 | 
					        ctx.fillStyle = left;
 | 
				
			||||||
                img,
 | 
					        ctx.translate(-pad, pad * 2 - h);
 | 
				
			||||||
                144,
 | 
					        ctx.fillRect(0, 0, pad, h - pad * 2);
 | 
				
			||||||
                48,
 | 
					        ctx.fillStyle = right;
 | 
				
			||||||
                32,
 | 
					        ctx.translate(w - pad, 0);
 | 
				
			||||||
                16,
 | 
					        ctx.fillRect(0, 0, pad, h - pad * 2);
 | 
				
			||||||
                x + dx + 16,
 | 
					        ctx.restore();
 | 
				
			||||||
                y + h - 16 * sz,
 | 
					        // 四个角的边框
 | 
				
			||||||
                32 * sz,
 | 
					        ctx.drawImage(img, 128, 0, 16, 16, 0, 0, pad, pad);
 | 
				
			||||||
                16 * sz
 | 
					        ctx.drawImage(img, 176, 0, 16, 16, w - pad, 0, pad, pad);
 | 
				
			||||||
            );
 | 
					        ctx.drawImage(img, 128, 48, 16, 16, 0, h - pad, pad, pad);
 | 
				
			||||||
        }
 | 
					        ctx.drawImage(img, 176, 48, 16, 16, w - pad, h - pad, pad, pad);
 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            144,
 | 
					 | 
				
			||||||
            0,
 | 
					 | 
				
			||||||
            w - dx - 32,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            x + dx + 16 * sz,
 | 
					 | 
				
			||||||
            y,
 | 
					 | 
				
			||||||
            w - dx - 32 * sz,
 | 
					 | 
				
			||||||
            16 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            144,
 | 
					 | 
				
			||||||
            48,
 | 
					 | 
				
			||||||
            w - dx - 32,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            x + dx + 16 * sz,
 | 
					 | 
				
			||||||
            y + h - 16 * sz,
 | 
					 | 
				
			||||||
            w - dx - 32 * sz,
 | 
					 | 
				
			||||||
            16 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            176,
 | 
					 | 
				
			||||||
            0,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            x + w - 16 * sz,
 | 
					 | 
				
			||||||
            y,
 | 
					 | 
				
			||||||
            16 * sz,
 | 
					 | 
				
			||||||
            16 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        // 左右
 | 
					 | 
				
			||||||
        let dy;
 | 
					 | 
				
			||||||
        for (dy = 0; dy < h - 64 * sz; dy += 32 * sz) {
 | 
					 | 
				
			||||||
            ctx.drawImage(
 | 
					 | 
				
			||||||
                img,
 | 
					 | 
				
			||||||
                128,
 | 
					 | 
				
			||||||
                16,
 | 
					 | 
				
			||||||
                16,
 | 
					 | 
				
			||||||
                32,
 | 
					 | 
				
			||||||
                x,
 | 
					 | 
				
			||||||
                y + dy + 16 * sz,
 | 
					 | 
				
			||||||
                16 * sz,
 | 
					 | 
				
			||||||
                32 * sz
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            ctx.drawImage(
 | 
					 | 
				
			||||||
                img,
 | 
					 | 
				
			||||||
                176,
 | 
					 | 
				
			||||||
                16,
 | 
					 | 
				
			||||||
                16,
 | 
					 | 
				
			||||||
                32,
 | 
					 | 
				
			||||||
                x + w - 16 * sz,
 | 
					 | 
				
			||||||
                y + dy + 16 * sz,
 | 
					 | 
				
			||||||
                16 * sz,
 | 
					 | 
				
			||||||
                32 * sz
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            128,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            h - dy - 32,
 | 
					 | 
				
			||||||
            x,
 | 
					 | 
				
			||||||
            y + dy + 16 * sz,
 | 
					 | 
				
			||||||
            16 * sz,
 | 
					 | 
				
			||||||
            h - dy - 32 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            176,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            h - dy - 32,
 | 
					 | 
				
			||||||
            x + w - 16 * sz,
 | 
					 | 
				
			||||||
            y + dy + 16 * sz,
 | 
					 | 
				
			||||||
            16 * sz,
 | 
					 | 
				
			||||||
            h - dy - 32 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        // 下方
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            128,
 | 
					 | 
				
			||||||
            48,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            x,
 | 
					 | 
				
			||||||
            y + h - 16 * sz,
 | 
					 | 
				
			||||||
            16 * sz,
 | 
					 | 
				
			||||||
            16 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        ctx.drawImage(
 | 
					 | 
				
			||||||
            img,
 | 
					 | 
				
			||||||
            176,
 | 
					 | 
				
			||||||
            48,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            16,
 | 
					 | 
				
			||||||
            x + w - 16 * sz,
 | 
					 | 
				
			||||||
            y + h - 16 * sz,
 | 
					 | 
				
			||||||
            16 * sz,
 | 
					 | 
				
			||||||
            16 * sz
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        this.update();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -506,6 +478,7 @@ export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    setImage(image: SizedCanvasImageSource) {
 | 
					    setImage(image: SizedCanvasImageSource) {
 | 
				
			||||||
        this.image = image;
 | 
					        this.image = image;
 | 
				
			||||||
 | 
					        this.patternCache = void 0;
 | 
				
			||||||
        this.update();
 | 
					        this.update();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -530,6 +503,7 @@ export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            this.pendingImage = name;
 | 
					            this.pendingImage = name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this.imageName = name;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -538,6 +512,8 @@ export class Winskin extends RenderItem<EWinskinEvent> {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    setBorderSize(size: number) {
 | 
					    setBorderSize(size: number) {
 | 
				
			||||||
        this.borderSize = size;
 | 
					        this.borderSize = size;
 | 
				
			||||||
 | 
					        this.patternTransform.a = size / 32;
 | 
				
			||||||
 | 
					        this.patternTransform.d = size / 32;
 | 
				
			||||||
        this.update();
 | 
					        this.update();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -90,6 +90,7 @@
 | 
				
			|||||||
        "56": "Method '$1' has been deprecated. Consider using '$2' instead.",
 | 
					        "56": "Method '$1' has been deprecated. Consider using '$2' instead.",
 | 
				
			||||||
        "57": "Repeated UI controller on item '$1', new controller will not work.",
 | 
					        "57": "Repeated UI controller on item '$1', new controller will not work.",
 | 
				
			||||||
        "58": "Fail to set ellipse round rect, since length of 'ellipse' property should only be 2, 4, 6 or 8. delivered: $1",
 | 
					        "58": "Fail to set ellipse round rect, since length of 'ellipse' property should only be 2, 4, 6 or 8. delivered: $1",
 | 
				
			||||||
 | 
					        "59": "Unknown icon '$1' in parsing text content.",
 | 
				
			||||||
        "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
 | 
					        "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
 | 
				
			||||||
        "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
 | 
					        "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,8 @@ import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    computed,
 | 
					    computed,
 | 
				
			||||||
    defineComponent,
 | 
					    defineComponent,
 | 
				
			||||||
 | 
					    nextTick,
 | 
				
			||||||
    onUnmounted,
 | 
					    onUnmounted,
 | 
				
			||||||
    onUpdated,
 | 
					 | 
				
			||||||
    ref,
 | 
					    ref,
 | 
				
			||||||
    shallowReactive,
 | 
					    shallowReactive,
 | 
				
			||||||
    shallowRef,
 | 
					    shallowRef,
 | 
				
			||||||
@ -13,7 +13,7 @@ import {
 | 
				
			|||||||
} from 'vue';
 | 
					} from 'vue';
 | 
				
			||||||
import { logger } from '@/core/common/logger';
 | 
					import { logger } from '@/core/common/logger';
 | 
				
			||||||
import { Sprite } from '@/core/render/sprite';
 | 
					import { Sprite } from '@/core/render/sprite';
 | 
				
			||||||
import { ContainerProps, DefaultProps } from '@/core/render/renderer';
 | 
					import { DefaultProps } from '@/core/render/renderer';
 | 
				
			||||||
import { isNil } from 'lodash-es';
 | 
					import { isNil } from 'lodash-es';
 | 
				
			||||||
import { SetupComponentOptions } from './types';
 | 
					import { SetupComponentOptions } from './types';
 | 
				
			||||||
import EventEmitter from 'eventemitter3';
 | 
					import EventEmitter from 'eventemitter3';
 | 
				
			||||||
@ -22,7 +22,9 @@ import {
 | 
				
			|||||||
    ITextContentConfig,
 | 
					    ITextContentConfig,
 | 
				
			||||||
    TextContentTyper,
 | 
					    TextContentTyper,
 | 
				
			||||||
    TyperRenderable,
 | 
					    TyperRenderable,
 | 
				
			||||||
    TextContentType
 | 
					    TextContentType,
 | 
				
			||||||
 | 
					    WordBreak,
 | 
				
			||||||
 | 
					    TextAlign
 | 
				
			||||||
} from './textboxTyper';
 | 
					} from './textboxTyper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface TextContentProps
 | 
					export interface TextContentProps
 | 
				
			||||||
@ -34,8 +36,6 @@ export interface TextContentProps
 | 
				
			|||||||
    fill?: boolean;
 | 
					    fill?: boolean;
 | 
				
			||||||
    /** 是否描边 */
 | 
					    /** 是否描边 */
 | 
				
			||||||
    stroke?: boolean;
 | 
					    stroke?: boolean;
 | 
				
			||||||
    /** 是否忽略打字机,直接显伤全部 */
 | 
					 | 
				
			||||||
    showAll?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type TextContentEmits = {
 | 
					export type TextContentEmits = {
 | 
				
			||||||
@ -43,6 +43,18 @@ export type TextContentEmits = {
 | 
				
			|||||||
    typeStart: () => void;
 | 
					    typeStart: () => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TextContentExpose {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 重新开始打字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    retype(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 立刻显示所有文字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    showAll(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const textContentOptions = {
 | 
					const textContentOptions = {
 | 
				
			||||||
    props: [
 | 
					    props: [
 | 
				
			||||||
        'breakChars',
 | 
					        'breakChars',
 | 
				
			||||||
@ -50,7 +62,6 @@ const textContentOptions = {
 | 
				
			|||||||
        'fontSize',
 | 
					        'fontSize',
 | 
				
			||||||
        'fontWeight',
 | 
					        'fontWeight',
 | 
				
			||||||
        'fontItalic',
 | 
					        'fontItalic',
 | 
				
			||||||
        'height',
 | 
					 | 
				
			||||||
        'ignoreLineEnd',
 | 
					        'ignoreLineEnd',
 | 
				
			||||||
        'ignoreLineStart',
 | 
					        'ignoreLineStart',
 | 
				
			||||||
        'interval',
 | 
					        'interval',
 | 
				
			||||||
@ -58,17 +69,14 @@ const textContentOptions = {
 | 
				
			|||||||
        'lineHeight',
 | 
					        'lineHeight',
 | 
				
			||||||
        'text',
 | 
					        'text',
 | 
				
			||||||
        'textAlign',
 | 
					        'textAlign',
 | 
				
			||||||
        'width',
 | 
					 | 
				
			||||||
        'wordBreak',
 | 
					        'wordBreak',
 | 
				
			||||||
        'x',
 | 
					 | 
				
			||||||
        'y',
 | 
					 | 
				
			||||||
        'fill',
 | 
					        'fill',
 | 
				
			||||||
        'fillStyle',
 | 
					        'fillStyle',
 | 
				
			||||||
        'strokeStyle',
 | 
					        'strokeStyle',
 | 
				
			||||||
        'strokeWidth',
 | 
					        'strokeWidth',
 | 
				
			||||||
        'stroke',
 | 
					        'stroke',
 | 
				
			||||||
        'showAll',
 | 
					        'loc',
 | 
				
			||||||
        'loc'
 | 
					        'width'
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    emits: ['typeEnd', 'typeStart']
 | 
					    emits: ['typeEnd', 'typeStart']
 | 
				
			||||||
} satisfies SetupComponentOptions<
 | 
					} satisfies SetupComponentOptions<
 | 
				
			||||||
@ -81,69 +89,72 @@ export const TextContent = defineComponent<
 | 
				
			|||||||
    TextContentProps,
 | 
					    TextContentProps,
 | 
				
			||||||
    TextContentEmits,
 | 
					    TextContentEmits,
 | 
				
			||||||
    keyof TextContentEmits
 | 
					    keyof TextContentEmits
 | 
				
			||||||
>((props, { emit }) => {
 | 
					>((props, { emit, expose }) => {
 | 
				
			||||||
    if (props.width && props.width <= 0) {
 | 
					    const width = computed(() => props.width ?? props.loc?.[2] ?? 200);
 | 
				
			||||||
 | 
					    if (width.value < 0) {
 | 
				
			||||||
        logger.warn(41, String(props.width));
 | 
					        logger.warn(41, String(props.width));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const typer = new TextContentTyper(props);
 | 
					    const typer = new TextContentTyper(props);
 | 
				
			||||||
    let renderable: TyperRenderable[] = [];
 | 
					    let renderable: TyperRenderable[] = [];
 | 
				
			||||||
    let needUpdate = false;
 | 
					    let needUpdate = false;
 | 
				
			||||||
    let nowText = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    watch(props, value => {
 | 
					 | 
				
			||||||
        typer.setConfig(value);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const retype = () => {
 | 
					    const retype = () => {
 | 
				
			||||||
        if (props.showAll) {
 | 
					 | 
				
			||||||
            typer.typeAll();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (props.text === nowText) return;
 | 
					 | 
				
			||||||
        if (needUpdate) return;
 | 
					        if (needUpdate) return;
 | 
				
			||||||
        needUpdate = true;
 | 
					        needUpdate = true;
 | 
				
			||||||
        if (!spriteElement.value) {
 | 
					        if (!spriteElement.value) {
 | 
				
			||||||
            needUpdate = false;
 | 
					            needUpdate = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        nowText = props.text ?? '';
 | 
					 | 
				
			||||||
        renderable = [];
 | 
					        renderable = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        spriteElement.value?.requestBeforeFrame(() => {
 | 
					        spriteElement.value?.requestBeforeFrame(() => {
 | 
				
			||||||
            typer.setConfig(props);
 | 
					            typer.setConfig(props);
 | 
				
			||||||
            typer.setText(props.text ?? '');
 | 
					            typer.setText(props.text ?? '');
 | 
				
			||||||
            typer.type();
 | 
					            typer.type();
 | 
				
			||||||
            if (props.showAll) {
 | 
					 | 
				
			||||||
                typer.typeAll();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            needUpdate = false;
 | 
					            needUpdate = false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onUpdated(retype);
 | 
					    const showAll = () => {
 | 
				
			||||||
 | 
					        typer.typeAll();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    watch(props, value => {
 | 
				
			||||||
 | 
					        typer.setConfig(value);
 | 
				
			||||||
 | 
					        retype();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expose({ retype, showAll });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const spriteElement = shallowRef<Sprite>();
 | 
					    const spriteElement = shallowRef<Sprite>();
 | 
				
			||||||
    const renderContent = (canvas: MotaOffscreenCanvas2D) => {
 | 
					    const renderContent = (canvas: MotaOffscreenCanvas2D) => {
 | 
				
			||||||
        const ctx = canvas.ctx;
 | 
					        const ctx = canvas.ctx;
 | 
				
			||||||
        ctx.textBaseline = 'top';
 | 
					        ctx.textBaseline = 'top';
 | 
				
			||||||
        renderable.forEach(v => {
 | 
					        renderable.forEach(v => {
 | 
				
			||||||
            if (v.type === TextContentType.Text) {
 | 
					            switch (v.type) {
 | 
				
			||||||
                ctx.fillStyle = v.fillStyle;
 | 
					                case TextContentType.Text: {
 | 
				
			||||||
                ctx.strokeStyle = v.strokeStyle;
 | 
					                    if (v.text.length === 0) return;
 | 
				
			||||||
                ctx.font = v.font;
 | 
					                    ctx.fillStyle = v.fillStyle;
 | 
				
			||||||
                const text = v.text.slice(0, v.pointer);
 | 
					                    ctx.strokeStyle = v.strokeStyle;
 | 
				
			||||||
 | 
					                    ctx.font = v.font;
 | 
				
			||||||
 | 
					                    const text = v.text.slice(0, v.pointer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (props.fill ?? true) {
 | 
					                    if (props.fill ?? true) {
 | 
				
			||||||
                    ctx.fillText(text, v.x, v.y);
 | 
					                        ctx.fillText(text, v.x, v.y);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (props.stroke) {
 | 
				
			||||||
 | 
					                        ctx.strokeText(text, v.x, v.y);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (props.stroke) {
 | 
					                case TextContentType.Icon: {
 | 
				
			||||||
                    ctx.strokeText(text, v.x, v.y);
 | 
					                    const { renderable: r, x: dx, y: dy, width, height } = v;
 | 
				
			||||||
 | 
					                    const render = r.render;
 | 
				
			||||||
 | 
					                    const [x, y, w, h] = render[0];
 | 
				
			||||||
 | 
					                    const icon = r.autotile ? r.image[0] : r.image;
 | 
				
			||||||
 | 
					                    ctx.drawImage(icon, x, y, w, h, dx, dy, width, height);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                const r = v.renderable;
 | 
					 | 
				
			||||||
                const render = r.render;
 | 
					 | 
				
			||||||
                const [x, y, w, h] = render[0];
 | 
					 | 
				
			||||||
                const icon = r.autotile ? r.image[0] : r.image;
 | 
					 | 
				
			||||||
                ctx.drawImage(icon, x, y, w, h, v.x, v.y, v.width, v.height);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -164,19 +175,15 @@ export const TextContent = defineComponent<
 | 
				
			|||||||
    return () => {
 | 
					    return () => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <sprite
 | 
					            <sprite
 | 
				
			||||||
                {...props}
 | 
					                loc={props.loc}
 | 
				
			||||||
                ref={spriteElement}
 | 
					                ref={spriteElement}
 | 
				
			||||||
                x={props.x}
 | 
					 | 
				
			||||||
                y={props.y}
 | 
					 | 
				
			||||||
                width={props.width}
 | 
					 | 
				
			||||||
                height={props.height}
 | 
					 | 
				
			||||||
                render={renderContent}
 | 
					                render={renderContent}
 | 
				
			||||||
            ></sprite>
 | 
					            ></sprite>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}, textContentOptions);
 | 
					}, textContentOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface TextboxProps extends TextContentProps, ContainerProps {
 | 
					export interface TextboxProps extends TextContentProps, DefaultProps {
 | 
				
			||||||
    /** 背景颜色 */
 | 
					    /** 背景颜色 */
 | 
				
			||||||
    backColor?: CanvasStyle;
 | 
					    backColor?: CanvasStyle;
 | 
				
			||||||
    /** 背景 winskin */
 | 
					    /** 背景 winskin */
 | 
				
			||||||
@ -195,6 +202,28 @@ export interface TextboxProps extends TextContentProps, ContainerProps {
 | 
				
			|||||||
    titlePadding?: number;
 | 
					    titlePadding?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TextboxExpose {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 显示这个文本框
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    show(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 隐藏这个文本框
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    hide(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 重新开始打字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    retype(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 立刻显示所有文字
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    showAll(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TextboxEmits = TextContentEmits;
 | 
					type TextboxEmits = TextContentEmits;
 | 
				
			||||||
type TextboxSlots = SlotsType<{
 | 
					type TextboxSlots = SlotsType<{
 | 
				
			||||||
    default: (data: TextboxProps) => VNode[];
 | 
					    default: (data: TextboxProps) => VNode[];
 | 
				
			||||||
@ -205,23 +234,14 @@ const textboxOptions = {
 | 
				
			|||||||
    props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
 | 
					    props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
 | 
				
			||||||
        'backColor',
 | 
					        'backColor',
 | 
				
			||||||
        'winskin',
 | 
					        'winskin',
 | 
				
			||||||
        'id',
 | 
					 | 
				
			||||||
        'padding',
 | 
					        'padding',
 | 
				
			||||||
        'alpha',
 | 
					 | 
				
			||||||
        'hidden',
 | 
					 | 
				
			||||||
        'anchorX',
 | 
					 | 
				
			||||||
        'anchorY',
 | 
					 | 
				
			||||||
        'anti',
 | 
					 | 
				
			||||||
        'cache',
 | 
					 | 
				
			||||||
        'composite',
 | 
					 | 
				
			||||||
        'fall',
 | 
					 | 
				
			||||||
        'hd',
 | 
					 | 
				
			||||||
        'transform',
 | 
					 | 
				
			||||||
        'type',
 | 
					 | 
				
			||||||
        'zIndex',
 | 
					 | 
				
			||||||
        'titleFill',
 | 
					        'titleFill',
 | 
				
			||||||
        'titleStroke',
 | 
					        'titleStroke',
 | 
				
			||||||
        'titleFont'
 | 
					        'titleFont',
 | 
				
			||||||
 | 
					        'titlePadding',
 | 
				
			||||||
 | 
					        'id',
 | 
				
			||||||
 | 
					        'hidden',
 | 
				
			||||||
 | 
					        'title'
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    emits: textContentOptions.emits
 | 
					    emits: textContentOptions.emits
 | 
				
			||||||
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
 | 
					} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
 | 
				
			||||||
@ -236,153 +256,206 @@ export const Textbox = defineComponent<
 | 
				
			|||||||
    TextboxEmits,
 | 
					    TextboxEmits,
 | 
				
			||||||
    keyof TextboxEmits,
 | 
					    keyof TextboxEmits,
 | 
				
			||||||
    TextboxSlots
 | 
					    TextboxSlots
 | 
				
			||||||
>((props, { slots }) => {
 | 
					>((props, { slots, expose }) => {
 | 
				
			||||||
    const data = shallowReactive({ ...props });
 | 
					    const contentData = shallowReactive<TextContentProps>({});
 | 
				
			||||||
    data.padding ??= 8;
 | 
					    const data = shallowReactive<TextboxProps>({});
 | 
				
			||||||
    data.width ??= 200;
 | 
					
 | 
				
			||||||
    data.height ??= 200;
 | 
					    const setContentData = () => {
 | 
				
			||||||
    data.id ??= '';
 | 
					        contentData.breakChars = props.breakChars ?? '';
 | 
				
			||||||
    data.alpha ??= 1;
 | 
					        contentData.fontFamily = props.fontFamily ?? 'Verdana';
 | 
				
			||||||
    data.titleFill ??= '#000';
 | 
					        contentData.fontSize = props.fontSize ?? 16;
 | 
				
			||||||
    data.titleStroke ??= 'transparent';
 | 
					        contentData.fontWeight = props.fontWeight ?? 500;
 | 
				
			||||||
    data.titleFont ??= '16px Verdana';
 | 
					        contentData.fontItalic = props.fontItalic ?? false;
 | 
				
			||||||
    data.titlePadding ??= 4;
 | 
					        contentData.ignoreLineEnd = props.ignoreLineEnd ?? '';
 | 
				
			||||||
 | 
					        contentData.ignoreLineStart = props.ignoreLineStart ?? '';
 | 
				
			||||||
 | 
					        contentData.interval = props.interval ?? 0;
 | 
				
			||||||
 | 
					        contentData.keepLast = props.keepLast ?? false;
 | 
				
			||||||
 | 
					        contentData.lineHeight = props.lineHeight ?? 0;
 | 
				
			||||||
 | 
					        contentData.text = props.text ?? '';
 | 
				
			||||||
 | 
					        contentData.textAlign = props.textAlign ?? TextAlign.Left;
 | 
				
			||||||
 | 
					        contentData.wordBreak = props.wordBreak ?? WordBreak.Space;
 | 
				
			||||||
 | 
					        contentData.fill = props.fill ?? true;
 | 
				
			||||||
 | 
					        contentData.stroke = props.stroke ?? false;
 | 
				
			||||||
 | 
					        contentData.fillStyle = props.fillStyle ?? '#fff';
 | 
				
			||||||
 | 
					        contentData.strokeStyle = props.strokeStyle ?? '#000';
 | 
				
			||||||
 | 
					        contentData.strokeWidth = props.strokeWidth ?? 2;
 | 
				
			||||||
 | 
					        contentData.loc = props.loc;
 | 
				
			||||||
 | 
					        contentData.width = props.width;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const setTextboxData = () => {
 | 
				
			||||||
 | 
					        data.backColor = props.backColor ?? '#222';
 | 
				
			||||||
 | 
					        data.winskin = props.winskin;
 | 
				
			||||||
 | 
					        data.padding = props.padding ?? 8;
 | 
				
			||||||
 | 
					        data.titleFill = props.titleFill ?? 'gold';
 | 
				
			||||||
 | 
					        data.titleStroke = props.titleStroke ?? 'transparent';
 | 
				
			||||||
 | 
					        data.titleFont = props.titleFont ?? '18px Verdana';
 | 
				
			||||||
 | 
					        data.titlePadding = props.titlePadding ?? 8;
 | 
				
			||||||
 | 
					        data.width = props.width ?? props.loc?.[2] ?? 200;
 | 
				
			||||||
 | 
					        data.height = props.height ?? props.loc?.[3] ?? 200;
 | 
				
			||||||
 | 
					        data.title = props.title ?? '';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setContentData();
 | 
				
			||||||
 | 
					    setTextboxData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    watch(props, () => {
 | 
				
			||||||
 | 
					        const needUpdateTitle = data.title !== props.title;
 | 
				
			||||||
 | 
					        setTextboxData();
 | 
				
			||||||
 | 
					        if (needUpdateTitle) {
 | 
				
			||||||
 | 
					            onSetText();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const titleElement = ref<Text>();
 | 
					    const titleElement = ref<Text>();
 | 
				
			||||||
    const titleWidth = ref(data.titlePadding * 2);
 | 
					    const content = ref<TextContentExpose>();
 | 
				
			||||||
    const titleHeight = ref(data.titlePadding * 2);
 | 
					    const hidden = ref(props.hidden);
 | 
				
			||||||
 | 
					    /** 标题宽度 */
 | 
				
			||||||
 | 
					    const tw = ref(data.titlePadding! * 2);
 | 
				
			||||||
 | 
					    /** 标题高度 */
 | 
				
			||||||
 | 
					    const th = ref(data.titlePadding! * 2);
 | 
				
			||||||
    const contentY = computed(() => {
 | 
					    const contentY = computed(() => {
 | 
				
			||||||
        const height = titleHeight.value;
 | 
					        const height = th.value;
 | 
				
			||||||
        return data.title ? height : 0;
 | 
					        return data.title ? height : 0;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    const backHeight = computed(() => data.height! - contentY.value);
 | 
				
			||||||
    const contentWidth = computed(() => data.width! - data.padding! * 2);
 | 
					    const contentWidth = computed(() => data.width! - data.padding! * 2);
 | 
				
			||||||
    const contentHeight = computed(
 | 
					    const contentHeight = computed(
 | 
				
			||||||
        () => data.height! - data.padding! * 2 - contentY.value
 | 
					        () => data.height! - data.padding! * 2 - contentY.value
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const calTitleSize = (text: string) => {
 | 
					    const onSetText = () => {
 | 
				
			||||||
        if (!titleElement.value) return;
 | 
					        nextTick(() => {
 | 
				
			||||||
        const { width, height } = titleElement.value;
 | 
					            titleElement.value?.requestBeforeFrame(() => {
 | 
				
			||||||
        titleWidth.value = width + data.titlePadding! * 2;
 | 
					                if (titleElement.value) {
 | 
				
			||||||
        titleHeight.value = height + data.titlePadding! * 2;
 | 
					                    const { width, height } = titleElement.value;
 | 
				
			||||||
        data.title = text;
 | 
					                    tw.value = width + data.padding! * 2;
 | 
				
			||||||
 | 
					                    th.value = height + data.padding! * 2;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watch(titleElement, (value, old) => {
 | 
					 | 
				
			||||||
        old?.off('setText', calTitleSize);
 | 
					 | 
				
			||||||
        value?.on('setText', calTitleSize);
 | 
					 | 
				
			||||||
        if (value) calTitleSize(value?.text);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onUnmounted(() => {
 | 
					 | 
				
			||||||
        titleElement.value?.off('setText', calTitleSize);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ----- store
 | 
					    // ----- store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** 结束打字机 */
 | 
					    /** 结束打字机 */
 | 
				
			||||||
    const storeEmits: TextboxStoreEmits = {
 | 
					    const storeEmits: TextboxStoreEmits = {
 | 
				
			||||||
        endType() {
 | 
					        endType() {
 | 
				
			||||||
            data.showAll = true;
 | 
					            content.value?.showAll();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hide() {
 | 
				
			||||||
 | 
					            hidden.value = true;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        show() {
 | 
				
			||||||
 | 
					            hidden.value = false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        update(value) {
 | 
				
			||||||
 | 
					            if (data.title !== value.title) {
 | 
				
			||||||
 | 
					                data.title = value.title;
 | 
				
			||||||
 | 
					                onSetText();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setText(text) {
 | 
				
			||||||
 | 
					            if (contentData.text === text) {
 | 
				
			||||||
 | 
					                content.value?.retype();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                contentData.text = text;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const store = TextboxStore.use(
 | 
					    const store = TextboxStore.use(
 | 
				
			||||||
        props.id ?? getNextTextboxId(),
 | 
					        props.id ?? getNextTextboxId(),
 | 
				
			||||||
        data,
 | 
					        contentData,
 | 
				
			||||||
        storeEmits
 | 
					        storeEmits
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const hidden = ref(data.hidden);
 | 
					 | 
				
			||||||
    store.on('hide', () => (hidden.value = true));
 | 
					 | 
				
			||||||
    store.on('show', () => (hidden.value = false));
 | 
					 | 
				
			||||||
    store.on('update', value => {
 | 
					 | 
				
			||||||
        if (value.title) {
 | 
					 | 
				
			||||||
            titleElement.value?.requestBeforeFrame(() => {
 | 
					 | 
				
			||||||
                const { width, height } = titleElement.value!;
 | 
					 | 
				
			||||||
                titleWidth.value = width + data.padding! * 2;
 | 
					 | 
				
			||||||
                titleHeight.value = height + data.padding! * 2;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onTypeStart = () => {
 | 
					    const onTypeStart = () => {
 | 
				
			||||||
        store.emitTypeStart();
 | 
					        store.emitTypeStart();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onTypeEnd = () => {
 | 
					    const onTypeEnd = () => {
 | 
				
			||||||
        data.showAll = false;
 | 
					 | 
				
			||||||
        store.emitTypeEnd();
 | 
					        store.emitTypeEnd();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return () => {
 | 
					    expose<TextboxExpose>({
 | 
				
			||||||
        return (
 | 
					        show() {
 | 
				
			||||||
            <container {...data} hidden={hidden.value} alpha={data.alpha}>
 | 
					            hidden.value = false;
 | 
				
			||||||
                {data.title ? (
 | 
					        },
 | 
				
			||||||
                    <container
 | 
					        hide() {
 | 
				
			||||||
                        zIndex={10}
 | 
					            hidden.value = true;
 | 
				
			||||||
                        width={titleWidth.value}
 | 
					        },
 | 
				
			||||||
                        height={titleHeight.value}
 | 
					        retype() {
 | 
				
			||||||
                    >
 | 
					            content.value?.retype();
 | 
				
			||||||
                        {slots.title ? (
 | 
					        },
 | 
				
			||||||
                            slots.title(data)
 | 
					        showAll() {
 | 
				
			||||||
                        ) : props.winskin ? (
 | 
					            content.value?.showAll();
 | 
				
			||||||
                            <winskin image={props.winskin}></winskin>
 | 
					        }
 | 
				
			||||||
                        ) : (
 | 
					    });
 | 
				
			||||||
                            <g-rect
 | 
					
 | 
				
			||||||
                                x={0}
 | 
					    return () => (
 | 
				
			||||||
                                y={0}
 | 
					        <container
 | 
				
			||||||
                                width={titleWidth.value}
 | 
					            id={props.id}
 | 
				
			||||||
                                height={titleHeight.value}
 | 
					            hidden={hidden.value}
 | 
				
			||||||
                                fillStyle={data.backColor}
 | 
					            alpha={data.alpha}
 | 
				
			||||||
                            ></g-rect>
 | 
					            loc={props.loc}
 | 
				
			||||||
                        )}
 | 
					        >
 | 
				
			||||||
                        <text
 | 
					            {data.title && (
 | 
				
			||||||
                            ref={titleElement}
 | 
					                <container zIndex={10} loc={[0, 0, tw.value, th.value]}>
 | 
				
			||||||
                            text={data.title}
 | 
					                    {slots.title ? (
 | 
				
			||||||
                            x={data.titlePadding}
 | 
					                        slots.title(data)
 | 
				
			||||||
                            y={data.titlePadding}
 | 
					                    ) : props.winskin ? (
 | 
				
			||||||
                            fillStyle={data.titleFill}
 | 
					                        <winskin
 | 
				
			||||||
                            strokeStyle={data.titleStroke}
 | 
					                            image={props.winskin}
 | 
				
			||||||
                            font={data.titleFont}
 | 
					                            loc={[0, 0, tw.value, th.value]}
 | 
				
			||||||
                        ></text>
 | 
					                        ></winskin>
 | 
				
			||||||
                    </container>
 | 
					                    ) : (
 | 
				
			||||||
                ) : (
 | 
					                        <g-rect loc={[0, 0, tw.value, th.value]}></g-rect>
 | 
				
			||||||
                    ''
 | 
					                    )}
 | 
				
			||||||
                )}
 | 
					                    <text
 | 
				
			||||||
                {slots.default ? (
 | 
					                        ref={titleElement}
 | 
				
			||||||
                    slots.default(data)
 | 
					                        text={data.title}
 | 
				
			||||||
                ) : props.winskin ? (
 | 
					                        loc={[data.titlePadding, data.titlePadding]}
 | 
				
			||||||
                    <winskin image={props.winskin}></winskin>
 | 
					                        fillStyle={data.titleFill}
 | 
				
			||||||
                ) : (
 | 
					                        strokeStyle={data.titleStroke}
 | 
				
			||||||
                    <g-rect
 | 
					                        font={data.titleFont}
 | 
				
			||||||
                        x={0}
 | 
					                    ></text>
 | 
				
			||||||
                        y={contentY.value}
 | 
					                </container>
 | 
				
			||||||
                        width={data.width ?? 200}
 | 
					            )}
 | 
				
			||||||
                        height={(data.height ?? 200) - contentY.value}
 | 
					            {slots.default ? (
 | 
				
			||||||
                        fill
 | 
					                slots.default(data)
 | 
				
			||||||
                        fillStyle={data.backColor}
 | 
					            ) : props.winskin ? (
 | 
				
			||||||
                    ></g-rect>
 | 
					                <winskin
 | 
				
			||||||
                )}
 | 
					                    image={props.winskin}
 | 
				
			||||||
                <TextContent
 | 
					                    loc={[0, contentY.value, data.width!, backHeight.value]}
 | 
				
			||||||
                    {...data}
 | 
					                ></winskin>
 | 
				
			||||||
                    id=""
 | 
					            ) : (
 | 
				
			||||||
                    hidden={false}
 | 
					                <g-rect
 | 
				
			||||||
                    x={data.padding!}
 | 
					                    loc={[0, contentY.value, data.width!, backHeight.value]}
 | 
				
			||||||
                    y={contentY.value + data.padding!}
 | 
					                    fill
 | 
				
			||||||
                    width={contentWidth.value}
 | 
					                    fillStyle={data.backColor}
 | 
				
			||||||
                    height={contentHeight.value}
 | 
					                ></g-rect>
 | 
				
			||||||
                    onTypeEnd={onTypeEnd}
 | 
					            )}
 | 
				
			||||||
                    onTypeStart={onTypeStart}
 | 
					            <TextContent
 | 
				
			||||||
                    zIndex={0}
 | 
					                {...contentData}
 | 
				
			||||||
                    showAll={data.showAll}
 | 
					                ref={content}
 | 
				
			||||||
                ></TextContent>
 | 
					                x={data.padding!}
 | 
				
			||||||
            </container>
 | 
					                y={contentY.value + data.padding!}
 | 
				
			||||||
        );
 | 
					                width={contentWidth.value}
 | 
				
			||||||
    };
 | 
					                height={contentHeight.value}
 | 
				
			||||||
 | 
					                onTypeEnd={onTypeEnd}
 | 
				
			||||||
 | 
					                onTypeStart={onTypeStart}
 | 
				
			||||||
 | 
					            ></TextContent>
 | 
				
			||||||
 | 
					        </container>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
}, textboxOptions);
 | 
					}, textboxOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TextboxStoreEmits {
 | 
					interface TextboxStoreEmits {
 | 
				
			||||||
    endType: () => void;
 | 
					    endType: () => void;
 | 
				
			||||||
 | 
					    hide: () => void;
 | 
				
			||||||
 | 
					    show: () => void;
 | 
				
			||||||
 | 
					    update: (value: TextboxProps) => void;
 | 
				
			||||||
 | 
					    setText: (text: string) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TextboxStoreEvent {
 | 
					interface TextboxStoreEvent {
 | 
				
			||||||
@ -436,21 +509,30 @@ export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
 | 
				
			|||||||
            // @ts-expect-error 无法推导
 | 
					            // @ts-expect-error 无法推导
 | 
				
			||||||
            if (!isNil(value)) this.data[key] = value;
 | 
					            if (!isNil(value)) this.data[key] = value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this.emits.update(this.data);
 | 
				
			||||||
        this.emit('update', this.data);
 | 
					        this.emit('update', this.data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置显示的文本
 | 
				
			||||||
 | 
					     * @param text 要显示的文本
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setText(text: string) {
 | 
				
			||||||
 | 
					        this.emits.setText(text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 显示文本框
 | 
					     * 显示文本框
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    show() {
 | 
					    show() {
 | 
				
			||||||
        this.emit('show');
 | 
					        this.emits.show();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 隐藏文本框
 | 
					     * 隐藏文本框
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    hide() {
 | 
					    hide() {
 | 
				
			||||||
        this.emit('hide');
 | 
					        this.emits.hide();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -135,7 +135,16 @@ export interface TyperIconRenderable {
 | 
				
			|||||||
    renderable: RenderableData | AutotileRenderable;
 | 
					    renderable: RenderableData | AutotileRenderable;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type TyperRenderable = TyperTextRenderable | TyperIconRenderable;
 | 
					export interface TyperWaitRenderable {
 | 
				
			||||||
 | 
					    type: TextContentType.Wait;
 | 
				
			||||||
 | 
					    wait: number;
 | 
				
			||||||
 | 
					    waited: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TyperRenderable =
 | 
				
			||||||
 | 
					    | TyperTextRenderable
 | 
				
			||||||
 | 
					    | TyperIconRenderable
 | 
				
			||||||
 | 
					    | TyperWaitRenderable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TextContentTyperEvent {
 | 
					interface TextContentTyperEvent {
 | 
				
			||||||
    typeStart: [];
 | 
					    typeStart: [];
 | 
				
			||||||
@ -271,49 +280,64 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
 | 
				
			|||||||
    private createTyperData(index: number, line: number) {
 | 
					    private createTyperData(index: number, line: number) {
 | 
				
			||||||
        const renderable = this.renderObject.data[index];
 | 
					        const renderable = this.renderObject.data[index];
 | 
				
			||||||
        if (!renderable) return false;
 | 
					        if (!renderable) return false;
 | 
				
			||||||
        if (renderable.type === TextContentType.Text) {
 | 
					        switch (renderable.type) {
 | 
				
			||||||
            if (line < 0 || line > renderable.splitLines.length) {
 | 
					            case TextContentType.Text: {
 | 
				
			||||||
                return false;
 | 
					                if (line < 0 || line > renderable.splitLines.length) {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const start = renderable.splitLines[line - 1] ?? -1;
 | 
				
			||||||
 | 
					                const end =
 | 
				
			||||||
 | 
					                    renderable.splitLines[line] ?? renderable.text.length - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const data: TyperTextRenderable = {
 | 
				
			||||||
 | 
					                    type: TextContentType.Text,
 | 
				
			||||||
 | 
					                    x: this.x,
 | 
				
			||||||
 | 
					                    y: this.y,
 | 
				
			||||||
 | 
					                    text: renderable.text.slice(start + 1, end + 1),
 | 
				
			||||||
 | 
					                    font: renderable.font,
 | 
				
			||||||
 | 
					                    fillStyle: renderable.fillStyle,
 | 
				
			||||||
 | 
					                    strokeStyle: this.config.strokeStyle,
 | 
				
			||||||
 | 
					                    pointer: 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                this.processingData = data;
 | 
				
			||||||
 | 
					                this.renderData.push(data);
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const start = renderable.splitLines[line - 1] ?? 0;
 | 
					            case TextContentType.Icon: {
 | 
				
			||||||
            const end = renderable.splitLines[line] ?? renderable.text.length;
 | 
					                const tex = texture.getRenderable(renderable.icon!);
 | 
				
			||||||
            const data: TyperTextRenderable = {
 | 
					                if (!tex) return false;
 | 
				
			||||||
                type: TextContentType.Text,
 | 
					                const { render } = tex;
 | 
				
			||||||
                x: this.x,
 | 
					                const [, , width, height] = render[0];
 | 
				
			||||||
                y: this.y,
 | 
					                const aspect = width / height;
 | 
				
			||||||
                text: renderable.text.slice(start, end),
 | 
					                let iconWidth = 0;
 | 
				
			||||||
                font: renderable.font,
 | 
					                if (aspect < 1) {
 | 
				
			||||||
                fillStyle: renderable.fillStyle,
 | 
					                    // 这时候应该把高度限定在当前字体大小
 | 
				
			||||||
                strokeStyle: this.config.strokeStyle,
 | 
					                    iconWidth = width * (renderable.fontSize / height);
 | 
				
			||||||
                pointer: 0
 | 
					                } else {
 | 
				
			||||||
            };
 | 
					                    iconWidth = renderable.fontSize;
 | 
				
			||||||
            this.processingData = data;
 | 
					                }
 | 
				
			||||||
            this.renderData.push(data);
 | 
					                const data: TyperIconRenderable = {
 | 
				
			||||||
            return true;
 | 
					                    type: TextContentType.Icon,
 | 
				
			||||||
        } else {
 | 
					                    x: this.x,
 | 
				
			||||||
            const tex = texture.getRenderable(renderable.icon!);
 | 
					                    y: this.y,
 | 
				
			||||||
            if (!tex) return false;
 | 
					                    width: iconWidth,
 | 
				
			||||||
            const { render } = tex;
 | 
					                    height: iconWidth / aspect,
 | 
				
			||||||
            const [, , width, height] = render[0];
 | 
					                    renderable: tex
 | 
				
			||||||
            const aspect = width / height;
 | 
					                };
 | 
				
			||||||
            let iconWidth = 0;
 | 
					                this.processingData = data;
 | 
				
			||||||
            if (aspect < 1) {
 | 
					                this.renderData.push(data);
 | 
				
			||||||
                // 这时候应该把高度限定在当前字体大小
 | 
					                return true;
 | 
				
			||||||
                iconWidth = width * (renderable.fontSize / height);
 | 
					            }
 | 
				
			||||||
            } else {
 | 
					            case TextContentType.Wait: {
 | 
				
			||||||
                iconWidth = renderable.fontSize;
 | 
					                const data: TyperWaitRenderable = {
 | 
				
			||||||
 | 
					                    type: TextContentType.Wait,
 | 
				
			||||||
 | 
					                    wait: renderable.wait!,
 | 
				
			||||||
 | 
					                    waited: 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                this.processingData = data;
 | 
				
			||||||
 | 
					                this.renderData.push(data);
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const data: TyperIconRenderable = {
 | 
					 | 
				
			||||||
                type: TextContentType.Icon,
 | 
					 | 
				
			||||||
                x: this.x,
 | 
					 | 
				
			||||||
                y: this.y,
 | 
					 | 
				
			||||||
                width: iconWidth,
 | 
					 | 
				
			||||||
                height: iconWidth / aspect,
 | 
					 | 
				
			||||||
                renderable: tex
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            this.processingData = data;
 | 
					 | 
				
			||||||
            this.renderData.push(data);
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -339,62 +363,64 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
 | 
				
			|||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const lineHeight = this.renderObject.lineHeights[this.nowLine];
 | 
					            const lineHeight = this.renderObject.lineHeights[this.nowLine];
 | 
				
			||||||
            if (now.type === TextContentType.Text) {
 | 
					            switch (now.type) {
 | 
				
			||||||
                const restChars = now.text.length - now.pointer;
 | 
					                case TextContentType.Text: {
 | 
				
			||||||
                if (restChars <= rest) {
 | 
					                    const restChars = now.text.length - now.pointer;
 | 
				
			||||||
                    // 当前这段 renderable 打字完成后,刚好结束或还有内容
 | 
					                    if (restChars <= rest) {
 | 
				
			||||||
                    rest -= restChars;
 | 
					                        // 当前这段 renderable 打字完成后,刚好结束或还有内容
 | 
				
			||||||
                    now.pointer = now.text.length;
 | 
					                        rest -= restChars;
 | 
				
			||||||
                    if (this.dataLine === renderable.splitLines.length) {
 | 
					                        now.pointer = now.text.length;
 | 
				
			||||||
                        // 如果是最后一行
 | 
					                        if (this.dataLine === renderable.splitLines.length) {
 | 
				
			||||||
                        if (isNil(renderable.lastLineWidth)) {
 | 
					                            // 如果是最后一行
 | 
				
			||||||
                            const ctx = this.parser.testCanvas.ctx;
 | 
					                            if (isNil(renderable.lastLineWidth)) {
 | 
				
			||||||
                            ctx.font = now.font;
 | 
					                                const ctx = this.parser.testCanvas.ctx;
 | 
				
			||||||
                            const metrics = ctx.measureText(now.text);
 | 
					                                ctx.font = now.font;
 | 
				
			||||||
                            renderable.lastLineWidth = metrics.width;
 | 
					                                const metrics = ctx.measureText(now.text);
 | 
				
			||||||
 | 
					                                renderable.lastLineWidth = metrics.width;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            this.x += renderable.lastLineWidth;
 | 
				
			||||||
 | 
					                            this.dataLine = 0;
 | 
				
			||||||
 | 
					                            this.pointer++;
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            // 不是最后一行,那么换行
 | 
				
			||||||
 | 
					                            this.x = 0;
 | 
				
			||||||
 | 
					                            this.y += lineHeight + this.config.lineHeight;
 | 
				
			||||||
 | 
					                            this.dataLine++;
 | 
				
			||||||
 | 
					                            this.nowLine++;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        this.x += renderable.lastLineWidth;
 | 
					 | 
				
			||||||
                        this.dataLine = 0;
 | 
					 | 
				
			||||||
                        this.pointer++;
 | 
					 | 
				
			||||||
                        const success = this.createTyperData(
 | 
					 | 
				
			||||||
                            this.pointer,
 | 
					 | 
				
			||||||
                            this.dataLine
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        if (!success) return true;
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        // 不是最后一行,那么换行
 | 
					                        now.pointer += rest;
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                case TextContentType.Icon: {
 | 
				
			||||||
 | 
					                    rest--;
 | 
				
			||||||
 | 
					                    this.pointer++;
 | 
				
			||||||
 | 
					                    if (renderable.splitLines[0] === 0) {
 | 
				
			||||||
 | 
					                        // 如果图标换行
 | 
				
			||||||
                        this.x = 0;
 | 
					                        this.x = 0;
 | 
				
			||||||
                        this.y += lineHeight + this.config.lineHeight;
 | 
					                        this.y += lineHeight + this.config.lineHeight;
 | 
				
			||||||
                        this.dataLine++;
 | 
					 | 
				
			||||||
                        this.nowLine++;
 | 
					                        this.nowLine++;
 | 
				
			||||||
                        const success = this.createTyperData(
 | 
					                        this.dataLine = 0;
 | 
				
			||||||
                            this.pointer,
 | 
					                        now.x = 0;
 | 
				
			||||||
                            this.dataLine
 | 
					                        now.y = this.y;
 | 
				
			||||||
                        );
 | 
					                    } else {
 | 
				
			||||||
                        if (!success) return true;
 | 
					                        this.x += now.width;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                    break;
 | 
				
			||||||
                    now.pointer += rest;
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					                case TextContentType.Wait: {
 | 
				
			||||||
                rest--;
 | 
					                    now.waited += num;
 | 
				
			||||||
                this.pointer++;
 | 
					                    if (now.waited > now.wait) {
 | 
				
			||||||
                if (renderable.splitLines[0] === 0) {
 | 
					                        // 等待结束
 | 
				
			||||||
                    // 如果图标换行
 | 
					                        this.pointer++;
 | 
				
			||||||
                    this.x = 0;
 | 
					                    }
 | 
				
			||||||
                    this.y += lineHeight + this.config.lineHeight;
 | 
					                    break;
 | 
				
			||||||
                    this.nowLine++;
 | 
					 | 
				
			||||||
                    this.dataLine = 0;
 | 
					 | 
				
			||||||
                    now.x = 0;
 | 
					 | 
				
			||||||
                    now.y = this.y;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const success = this.createTyperData(
 | 
					 | 
				
			||||||
                    this.pointer,
 | 
					 | 
				
			||||||
                    this.dataLine
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                if (!success) return true;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            const success = this.createTyperData(this.pointer, this.dataLine);
 | 
				
			||||||
 | 
					            if (!success) return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -422,6 +448,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
 | 
				
			|||||||
     * 开始打字
 | 
					     * 开始打字
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    type() {
 | 
					    type() {
 | 
				
			||||||
 | 
					        if (this.typing) return;
 | 
				
			||||||
        if (this.config.interval === 0) {
 | 
					        if (this.config.interval === 0) {
 | 
				
			||||||
            this.emit('typeStart');
 | 
					            this.emit('typeStart');
 | 
				
			||||||
            this.typeChars(Infinity);
 | 
					            this.typeChars(Infinity);
 | 
				
			||||||
@ -438,6 +465,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
 | 
				
			|||||||
     * 立即显示所有文字
 | 
					     * 立即显示所有文字
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    typeAll() {
 | 
					    typeAll() {
 | 
				
			||||||
 | 
					        if (!this.typing) return;
 | 
				
			||||||
        this.typeChars(Infinity);
 | 
					        this.typeChars(Infinity);
 | 
				
			||||||
        this.render?.(this.renderData, false);
 | 
					        this.render?.(this.renderData, false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -561,7 +589,7 @@ export class TextContentParser {
 | 
				
			|||||||
        const end = this.indexParam(start);
 | 
					        const end = this.indexParam(start);
 | 
				
			||||||
        if (end === -1) {
 | 
					        if (end === -1) {
 | 
				
			||||||
            // 标签结束
 | 
					            // 标签结束
 | 
				
			||||||
            return ['', start];
 | 
					            return ['', start - 1];
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // 标签开始
 | 
					            // 标签开始
 | 
				
			||||||
            return [this.text.slice(start + 1, end), end];
 | 
					            return [this.text.slice(start + 1, end), end];
 | 
				
			||||||
@ -632,43 +660,43 @@ export class TextContentParser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFillStyle(pointer: number) {
 | 
					    private parseFillStyle(pointer: number) {
 | 
				
			||||||
        const [param, end] = this.getChildableTagParam(pointer + 1);
 | 
					        const [param, end] = this.getChildableTagParam(pointer + 2);
 | 
				
			||||||
        if (!param) {
 | 
					        if (!param) {
 | 
				
			||||||
            // 参数为空或没有参数,视为标签结束
 | 
					            // 参数为空或没有参数,视为标签结束
 | 
				
			||||||
            const color = this.fillStyleStack.pop();
 | 
					            const color = this.fillStyleStack.pop();
 | 
				
			||||||
            if (!color) {
 | 
					            if (!color) {
 | 
				
			||||||
                logger.warn(54, '\\r', pointer.toString());
 | 
					                logger.warn(54, '\\r', pointer.toString());
 | 
				
			||||||
                return pointer;
 | 
					                return end;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fillStyle = color;
 | 
					            this.status.fillStyle = color;
 | 
				
			||||||
            return pointer;
 | 
					            return end;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // 标签开始
 | 
					            // 标签开始
 | 
				
			||||||
            this.fillStyleStack.push(this.status.fillStyle);
 | 
					            this.fillStyleStack.push(this.status.fillStyle);
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fillStyle = param;
 | 
					            this.status.fillStyle = param;
 | 
				
			||||||
            return end;
 | 
					            return end;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFontSize(pointer: number) {
 | 
					    private parseFontSize(pointer: number) {
 | 
				
			||||||
        const [param, end] = this.getChildableTagParam(pointer + 1);
 | 
					        const [param, end] = this.getChildableTagParam(pointer + 2);
 | 
				
			||||||
        if (!param) {
 | 
					        if (!param) {
 | 
				
			||||||
            // 参数为空或没有参数,视为标签结束
 | 
					            // 参数为空或没有参数,视为标签结束
 | 
				
			||||||
            const size = this.fontSizeStack.pop();
 | 
					            const size = this.fontSizeStack.pop();
 | 
				
			||||||
            if (!size) {
 | 
					            if (!size) {
 | 
				
			||||||
                logger.warn(54, '\\c', pointer.toString());
 | 
					                logger.warn(54, '\\c', pointer.toString());
 | 
				
			||||||
                return pointer;
 | 
					                return end;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fontSize = size;
 | 
					            this.status.fontSize = size;
 | 
				
			||||||
            this.font = this.buildFont();
 | 
					            this.font = this.buildFont();
 | 
				
			||||||
            return pointer;
 | 
					            return end;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // 标签开始
 | 
					            // 标签开始
 | 
				
			||||||
            this.fontSizeStack.push(this.status.fontSize);
 | 
					            this.fontSizeStack.push(this.status.fontSize);
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fontSize = parseFloat(param);
 | 
					            this.status.fontSize = parseFloat(param);
 | 
				
			||||||
            this.font = this.buildFont();
 | 
					            this.font = this.buildFont();
 | 
				
			||||||
            return end;
 | 
					            return end;
 | 
				
			||||||
@ -676,22 +704,22 @@ export class TextContentParser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFontFamily(pointer: number) {
 | 
					    private parseFontFamily(pointer: number) {
 | 
				
			||||||
        const [param, end] = this.getChildableTagParam(pointer + 1);
 | 
					        const [param, end] = this.getChildableTagParam(pointer + 2);
 | 
				
			||||||
        if (!param) {
 | 
					        if (!param) {
 | 
				
			||||||
            // 参数为空或没有参数,视为标签结束
 | 
					            // 参数为空或没有参数,视为标签结束
 | 
				
			||||||
            const font = this.fontFamilyStack.pop();
 | 
					            const font = this.fontFamilyStack.pop();
 | 
				
			||||||
            if (!font) {
 | 
					            if (!font) {
 | 
				
			||||||
                logger.warn(54, '\\g', pointer.toString());
 | 
					                logger.warn(54, '\\g', pointer.toString());
 | 
				
			||||||
                return pointer;
 | 
					                return end;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fontFamily = font;
 | 
					            this.status.fontFamily = font;
 | 
				
			||||||
            this.font = this.buildFont();
 | 
					            this.font = this.buildFont();
 | 
				
			||||||
            return pointer;
 | 
					            return end;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // 标签开始
 | 
					            // 标签开始
 | 
				
			||||||
            this.fontFamilyStack.push(this.status.fontFamily);
 | 
					            this.fontFamilyStack.push(this.status.fontFamily);
 | 
				
			||||||
            this.addTextRenderable();
 | 
					            if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
            this.status.fontFamily = param;
 | 
					            this.status.fontFamily = param;
 | 
				
			||||||
            this.font = this.buildFont();
 | 
					            this.font = this.buildFont();
 | 
				
			||||||
            return end;
 | 
					            return end;
 | 
				
			||||||
@ -699,20 +727,20 @@ export class TextContentParser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFontWeight() {
 | 
					    private parseFontWeight() {
 | 
				
			||||||
        this.addTextRenderable();
 | 
					        if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
        this.status.fontWeight = this.status.fontWeight > 500 ? 500 : 700;
 | 
					        this.status.fontWeight = this.status.fontWeight > 500 ? 500 : 700;
 | 
				
			||||||
        this.font = this.buildFont();
 | 
					        this.font = this.buildFont();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFontItalic() {
 | 
					    private parseFontItalic() {
 | 
				
			||||||
        this.addTextRenderable();
 | 
					        if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
        this.status.fontItalic = !this.status.fontItalic;
 | 
					        this.status.fontItalic = !this.status.fontItalic;
 | 
				
			||||||
        this.font = this.buildFont();
 | 
					        this.font = this.buildFont();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseWait(pointer: number) {
 | 
					    private parseWait(pointer: number) {
 | 
				
			||||||
        this.addTextRenderable();
 | 
					        if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
        const [param, end] = this.getTagParam(pointer);
 | 
					        const [param, end] = this.getTagParam(pointer + 2);
 | 
				
			||||||
        if (!param) {
 | 
					        if (!param) {
 | 
				
			||||||
            logger.warn(55, '\\z');
 | 
					            logger.warn(55, '\\z');
 | 
				
			||||||
            return pointer;
 | 
					            return pointer;
 | 
				
			||||||
@ -723,10 +751,10 @@ export class TextContentParser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseIcon(pointer: number) {
 | 
					    private parseIcon(pointer: number) {
 | 
				
			||||||
        this.addTextRenderable();
 | 
					        if (this.resolved.length > 0) this.addTextRenderable();
 | 
				
			||||||
        const [param, end] = this.getTagParam(pointer);
 | 
					        const [param, end] = this.getTagParam(pointer + 2);
 | 
				
			||||||
        if (!param) {
 | 
					        if (!param) {
 | 
				
			||||||
            logger.warn(55, '\\z');
 | 
					            logger.warn(55, '\\i');
 | 
				
			||||||
            return pointer;
 | 
					            return pointer;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (/^\d+$/.test(param)) {
 | 
					        if (/^\d+$/.test(param)) {
 | 
				
			||||||
@ -739,6 +767,10 @@ export class TextContentParser {
 | 
				
			|||||||
                this.addIconRenderable(num as AllNumbers);
 | 
					                this.addIconRenderable(num as AllNumbers);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const num = texture.idNumberMap[param as AllIds];
 | 
					                const num = texture.idNumberMap[param as AllIds];
 | 
				
			||||||
 | 
					                if (num === void 0) {
 | 
				
			||||||
 | 
					                    logger.warn(59, param);
 | 
				
			||||||
 | 
					                    return end;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                this.addIconRenderable(num);
 | 
					                this.addIconRenderable(num);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -842,9 +874,11 @@ export class TextContentParser {
 | 
				
			|||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    case 'd':
 | 
					                    case 'd':
 | 
				
			||||||
                        this.parseFontWeight();
 | 
					                        this.parseFontWeight();
 | 
				
			||||||
 | 
					                        pointer++;
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    case 'e':
 | 
					                    case 'e':
 | 
				
			||||||
                        this.parseFontItalic();
 | 
					                        this.parseFontItalic();
 | 
				
			||||||
 | 
					                        pointer++;
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    case 'z':
 | 
					                    case 'z':
 | 
				
			||||||
                        pointer = this.parseWait(pointer);
 | 
					                        pointer = this.parseWait(pointer);
 | 
				
			||||||
@ -861,7 +895,7 @@ export class TextContentParser {
 | 
				
			|||||||
                // 表达式
 | 
					                // 表达式
 | 
				
			||||||
                pointer++;
 | 
					                pointer++;
 | 
				
			||||||
                inExpression = true;
 | 
					                inExpression = true;
 | 
				
			||||||
                expStart = pointer;
 | 
					                expStart = pointer + 1;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -875,7 +909,8 @@ export class TextContentParser {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private getHeight(metrics: TextMetrics) {
 | 
					    private getHeight(metrics: TextMetrics) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
 | 
					            Math.abs(metrics.actualBoundingBoxAscent) +
 | 
				
			||||||
 | 
					            Math.abs(metrics.actualBoundingBoxDescent)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -899,10 +934,14 @@ export class TextContentParser {
 | 
				
			|||||||
        // 如果大于猜测,那么算长度
 | 
					        // 如果大于猜测,那么算长度
 | 
				
			||||||
        const data = this.renderable[this.nowRenderable];
 | 
					        const data = this.renderable[this.nowRenderable];
 | 
				
			||||||
        const ctx = this.testCanvas.ctx;
 | 
					        const ctx = this.testCanvas.ctx;
 | 
				
			||||||
        ctx.font = this.font;
 | 
					        ctx.font = data.font;
 | 
				
			||||||
        const metrics = ctx.measureText(
 | 
					        const metrics = ctx.measureText(
 | 
				
			||||||
            data.text.slice(this.lineStart, pointer + 1)
 | 
					            data.text.slice(this.lineStart, pointer + 1)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        const height = this.getHeight(metrics);
 | 
				
			||||||
 | 
					        if (height > this.lineHeight) {
 | 
				
			||||||
 | 
					            this.lineHeight = height;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (metrics.width < rest) {
 | 
					        if (metrics.width < rest) {
 | 
				
			||||||
            // 实际宽度小于剩余宽度时,将猜测增益乘以剩余总宽度与当前宽度的比值的若干倍
 | 
					            // 实际宽度小于剩余宽度时,将猜测增益乘以剩余总宽度与当前宽度的比值的若干倍
 | 
				
			||||||
            this.guessGain *= (rest / metrics.width) * (1.1 + 1 / length);
 | 
					            this.guessGain *= (rest / metrics.width) * (1.1 + 1 / length);
 | 
				
			||||||
@ -947,7 +986,7 @@ export class TextContentParser {
 | 
				
			|||||||
        const data = this.renderable[index];
 | 
					        const data = this.renderable[index];
 | 
				
			||||||
        const { wordBreak } = data;
 | 
					        const { wordBreak } = data;
 | 
				
			||||||
        const ctx = this.testCanvas.ctx;
 | 
					        const ctx = this.testCanvas.ctx;
 | 
				
			||||||
        ctx.font = this.font;
 | 
					        ctx.font = data.font;
 | 
				
			||||||
        while (true) {
 | 
					        while (true) {
 | 
				
			||||||
            const mid = Math.floor((start + end) / 2);
 | 
					            const mid = Math.floor((start + end) / 2);
 | 
				
			||||||
            if (mid === start) {
 | 
					            if (mid === start) {
 | 
				
			||||||
@ -958,31 +997,33 @@ export class TextContentParser {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            const text = data.text.slice(
 | 
					            const text = data.text.slice(
 | 
				
			||||||
                wordBreak[this.bsStart],
 | 
					                wordBreak[this.bsStart],
 | 
				
			||||||
                wordBreak[mid]
 | 
					                wordBreak[mid] + 1
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            const metrics = ctx.measureText(text);
 | 
					            const metrics = ctx.measureText(text);
 | 
				
			||||||
 | 
					            height = this.getHeight(metrics);
 | 
				
			||||||
            if (metrics.width > width) {
 | 
					            if (metrics.width > width) {
 | 
				
			||||||
                end = mid;
 | 
					                end = mid;
 | 
				
			||||||
            } else if (metrics.width === width) {
 | 
					            } else if (metrics.width === width) {
 | 
				
			||||||
 | 
					                if (height > this.lineHeight) {
 | 
				
			||||||
 | 
					                    this.lineHeight = height;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                return mid;
 | 
					                return mid;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                start = mid;
 | 
					                start = mid;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            height = this.getHeight(metrics);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 检查剩余字符能否分行
 | 
					     * 检查剩余字符能否分行
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private checkRestLine(width: number, guess: number) {
 | 
					    private checkRestLine(width: number, guess: number, pointer: number) {
 | 
				
			||||||
        if (this.wordBreak.length === 0) return true;
 | 
					        if (this.wordBreak.length === 0) return true;
 | 
				
			||||||
        const last = this.nowRenderable - 1;
 | 
					        if (pointer === -1) {
 | 
				
			||||||
        if (last === -1) {
 | 
					            return this.checkLineWidth(width, guess, 0);
 | 
				
			||||||
            const now = this.renderable[this.nowRenderable];
 | 
					 | 
				
			||||||
            return this.checkLineWidth(width, guess, now.text.length);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const data = this.renderable[last];
 | 
					        const isLast = this.renderable.length - 1 === pointer;
 | 
				
			||||||
 | 
					        const data = this.renderable[pointer];
 | 
				
			||||||
        const rest = width - this.lineWidth;
 | 
					        const rest = width - this.lineWidth;
 | 
				
			||||||
        if (data.type === TextContentType.Text) {
 | 
					        if (data.type === TextContentType.Text) {
 | 
				
			||||||
            const wordBreak = data.wordBreak;
 | 
					            const wordBreak = data.wordBreak;
 | 
				
			||||||
@ -990,7 +1031,7 @@ export class TextContentParser {
 | 
				
			|||||||
            const lastIndex = isNil(lastLine) ? 0 : lastLine;
 | 
					            const lastIndex = isNil(lastLine) ? 0 : lastLine;
 | 
				
			||||||
            const restText = data.text.slice(lastIndex);
 | 
					            const restText = data.text.slice(lastIndex);
 | 
				
			||||||
            const ctx = this.testCanvas.ctx;
 | 
					            const ctx = this.testCanvas.ctx;
 | 
				
			||||||
            ctx.font = this.font;
 | 
					            ctx.font = data.font;
 | 
				
			||||||
            const metrics = ctx.measureText(restText);
 | 
					            const metrics = ctx.measureText(restText);
 | 
				
			||||||
            // 如果剩余内容不能构成完整的行
 | 
					            // 如果剩余内容不能构成完整的行
 | 
				
			||||||
            if (metrics.width < rest) {
 | 
					            if (metrics.width < rest) {
 | 
				
			||||||
@ -1008,12 +1049,12 @@ export class TextContentParser {
 | 
				
			|||||||
                this.bsEnd = lastBreak;
 | 
					                this.bsEnd = lastBreak;
 | 
				
			||||||
                let maxWidth = rest;
 | 
					                let maxWidth = rest;
 | 
				
			||||||
                while (true) {
 | 
					                while (true) {
 | 
				
			||||||
                    const index = this.bsLineWidth(maxWidth, last);
 | 
					                    const index = this.bsLineWidth(maxWidth, pointer);
 | 
				
			||||||
                    data.splitLines.push(this.wordBreak[index]);
 | 
					                    data.splitLines.push(this.wordBreak[index]);
 | 
				
			||||||
                    this.lineHeights.push(this.lineHeight);
 | 
					                    this.lineHeights.push(this.lineHeight);
 | 
				
			||||||
                    this.bsStart = index;
 | 
					                    this.bsStart = index;
 | 
				
			||||||
                    const text = data.text.slice(this.wordBreak[index]);
 | 
					                    const text = data.text.slice(this.wordBreak[index]);
 | 
				
			||||||
                    if (text.length < guess / 4) {
 | 
					                    if (!isLast && text.length < guess / 4) {
 | 
				
			||||||
                        // 如果剩余文字很少,几乎不可能会单独成一行时,直接结束循环
 | 
					                        // 如果剩余文字很少,几乎不可能会单独成一行时,直接结束循环
 | 
				
			||||||
                        this.lastBreakIndex = index;
 | 
					                        this.lastBreakIndex = index;
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
@ -1040,9 +1081,15 @@ export class TextContentParser {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                iconWidth = this.status.fontSize;
 | 
					                iconWidth = this.status.fontSize;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            this.lineWidth += iconWidth;
 | 
				
			||||||
 | 
					            const iconHeight = iconWidth / aspect;
 | 
				
			||||||
 | 
					            if (iconHeight > this.lineHeight) {
 | 
				
			||||||
 | 
					                this.lineHeight = iconHeight;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (iconWidth > rest) {
 | 
					            if (iconWidth > rest) {
 | 
				
			||||||
                data.splitLines.push(0);
 | 
					                data.splitLines.push(0);
 | 
				
			||||||
                this.lineHeights.push(this.lineHeight);
 | 
					                this.lineHeights.push(this.lineHeight);
 | 
				
			||||||
 | 
					                this.lineWidth = 0;
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
@ -1070,8 +1117,6 @@ export class TextContentParser {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const allBreak = this.wordBreakRule === WordBreak.All;
 | 
					        const allBreak = this.wordBreakRule === WordBreak.All;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // debugger;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (let i = 0; i < this.renderable.length; i++) {
 | 
					        for (let i = 0; i < this.renderable.length; i++) {
 | 
				
			||||||
            const data = this.renderable[i];
 | 
					            const data = this.renderable[i];
 | 
				
			||||||
            const { wordBreak, fontSize } = data;
 | 
					            const { wordBreak, fontSize } = data;
 | 
				
			||||||
@ -1080,7 +1125,7 @@ export class TextContentParser {
 | 
				
			|||||||
            this.wordBreak = wordBreak;
 | 
					            this.wordBreak = wordBreak;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (data.type === TextContentType.Icon) {
 | 
					            if (data.type === TextContentType.Icon) {
 | 
				
			||||||
                this.checkRestLine(width, guess);
 | 
					                this.checkRestLine(width, guess, i - 1);
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            } else if (data.type === TextContentType.Wait) {
 | 
					            } else if (data.type === TextContentType.Wait) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
@ -1122,7 +1167,7 @@ export class TextContentParser {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.checkRestLine(width, guess);
 | 
					            this.checkRestLine(width, guess, i);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import { defineComponent } from 'vue';
 | 
				
			|||||||
import { UIController } from '@/core/system';
 | 
					import { UIController } from '@/core/system';
 | 
				
			||||||
import { mainSceneUI } from './ui/main';
 | 
					import { mainSceneUI } from './ui/main';
 | 
				
			||||||
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
 | 
					import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
 | 
				
			||||||
 | 
					import { TextboxStore } from './components';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function create() {
 | 
					export function create() {
 | 
				
			||||||
    const main = new MotaRenderer();
 | 
					    const main = new MotaRenderer();
 | 
				
			||||||
@ -34,6 +35,10 @@ export function create() {
 | 
				
			|||||||
    console.log(main);
 | 
					    console.log(main);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mota.register('module', 'MainUI', {
 | 
				
			||||||
 | 
					    TextboxStore
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from './components';
 | 
					export * from './components';
 | 
				
			||||||
export * from './ui';
 | 
					export * from './ui';
 | 
				
			||||||
export * from './use';
 | 
					export * from './use';
 | 
				
			||||||
 | 
				
			|||||||
@ -57,9 +57,7 @@ const MainScene = defineComponent(() => {
 | 
				
			|||||||
    const mainTextboxProps: Props<typeof Textbox> = {
 | 
					    const mainTextboxProps: Props<typeof Textbox> = {
 | 
				
			||||||
        text: '',
 | 
					        text: '',
 | 
				
			||||||
        hidden: true,
 | 
					        hidden: true,
 | 
				
			||||||
        width: 480,
 | 
					        loc: [0, 330, 480, 150],
 | 
				
			||||||
        height: 150,
 | 
					 | 
				
			||||||
        y: 330,
 | 
					 | 
				
			||||||
        zIndex: 30,
 | 
					        zIndex: 30,
 | 
				
			||||||
        fillStyle: '#fff',
 | 
					        fillStyle: '#fff',
 | 
				
			||||||
        titleFill: 'gold',
 | 
					        titleFill: 'gold',
 | 
				
			||||||
@ -67,7 +65,7 @@ const MainScene = defineComponent(() => {
 | 
				
			|||||||
        titleFont: '700 20px normal',
 | 
					        titleFont: '700 20px normal',
 | 
				
			||||||
        winskin: 'winskin2.png',
 | 
					        winskin: 'winskin2.png',
 | 
				
			||||||
        interval: 100,
 | 
					        interval: 100,
 | 
				
			||||||
        lineHeight: 6
 | 
					        lineHeight: 4
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const map = ref<LayerGroup>();
 | 
					    const map = ref<LayerGroup>();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user