feat: Textbox & fix: hide & show

This commit is contained in:
unanmed 2024-12-22 23:06:35 +08:00
parent b018a5fd9a
commit 297b67da5b
5 changed files with 459 additions and 329 deletions

View File

@ -1,9 +1,12 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { import {
computed,
defineComponent, defineComponent,
onMounted, onMounted,
onUnmounted,
onUpdated, onUpdated,
ref, ref,
shallowReactive,
shallowRef, shallowRef,
SlotsType, SlotsType,
VNode, VNode,
@ -16,6 +19,8 @@ import { Sprite } from '../sprite';
import { onTick } from '../renderer'; import { onTick } from '../renderer';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { SetupComponentOptions } from './types'; import { SetupComponentOptions } from './types';
import EventEmitter from 'eventemitter3';
import { Container } from '../container';
export const enum WordBreak { export const enum WordBreak {
/** 不换行 */ /** 不换行 */
@ -128,364 +133,412 @@ const textContentOptions = {
'width', 'width',
'wordBreak', 'wordBreak',
'x', 'x',
'y' 'y',
'fill',
'fillStyle',
'strokeStyle',
'strokeWidth',
'stroke'
], ],
emits: ['typeEnd', 'typeStart'] emits: ['typeEnd', 'typeStart']
} satisfies SetupComponentOptions<TextContentProps, TextContentEmits>; } satisfies SetupComponentOptions<
TextContentProps,
TextContentEmits,
keyof TextContentEmits
>;
export const TextContent = defineComponent<TextContentProps, TextContentEmits>( export const TextContent = defineComponent<
(props, { emit }) => { TextContentProps,
if (props.width && props.width <= 0) { TextContentEmits,
logger.warn(41, String(props.width)); keyof TextContentEmits
} >((props, { emit }) => {
const renderData: Required<TextContentProps> = { if (props.width && props.width <= 0) {
text: props.text, logger.warn(41, String(props.width));
textAlign: props.textAlign ?? TextAlign.Left, }
x: props.x ?? 0, const renderData: Required<TextContentProps> = {
y: props.y ?? 0, text: props.text,
width: !props.width || props.width <= 0 ? 200 : props.width, textAlign: props.textAlign ?? TextAlign.Left,
height: props.height ?? 200, x: props.x ?? 0,
font: y: props.y ?? 0,
props.font ?? width: !props.width || props.width <= 0 ? 200 : props.width,
core.status.globalAttribute?.font ?? height: props.height ?? 200,
'16px Verdana', font: props.font ?? core.status.globalAttribute?.font ?? '16px Verdana',
ignoreLineEnd: props.ignoreLineEnd ?? new Set(), ignoreLineEnd: props.ignoreLineEnd ?? new Set(),
ignoreLineStart: props.ignoreLineStart ?? new Set(), ignoreLineStart: props.ignoreLineStart ?? new Set(),
keepLast: props.keepLast ?? false, keepLast: props.keepLast ?? false,
interval: props.interval ?? 0, interval: props.interval ?? 0,
lineHeight: props.lineHeight ?? 0, lineHeight: props.lineHeight ?? 0,
wordBreak: props.wordBreak ?? WordBreak.Space, wordBreak: props.wordBreak ?? WordBreak.Space,
breakChars: props.breakChars ?? new Set(), breakChars: props.breakChars ?? new Set(),
fillStyle: props.fillStyle ?? '#fff', fillStyle: props.fillStyle ?? '#fff',
strokeStyle: props.strokeStyle ?? 'transparent', strokeStyle: props.strokeStyle ?? 'transparent',
fill: props.fill ?? true, fill: props.fill ?? true,
stroke: props.stroke ?? false, stroke: props.stroke ?? false,
strokeWidth: props.strokeWidth ?? 2 strokeWidth: props.strokeWidth ?? 2
}; };
const ensureProps = () => { const ensureProps = () => {
for (const [key, value] of Object.entries(props)) { for (const [key, value] of Object.entries(props)) {
if (key in renderData && !isNil(value)) { if (key in renderData && !isNil(value)) {
if (key === 'width') { if (key === 'width') {
if (value && value <= 0) { if (value && value <= 0) {
logger.warn(41, String(props.width)); logger.warn(41, String(props.width));
renderData.width = 200; renderData.width = 200;
} else {
renderData.width = value;
}
} else { } else {
// @ts-ignore renderData.width = value;
renderData[key] = value;
} }
}
}
};
const makeSplitData = (): TextContentData => {
ensureProps();
return {
text: renderData.text,
width: renderData.width!,
font: renderData.font!,
wordBreak: renderData.wordBreak!,
ignoreLineStart: new Set(renderData.ignoreLineStart),
ignoreLineEnd: new Set(renderData.ignoreLineEnd),
breakChars: new Set(renderData.breakChars)
};
};
/**
*
*/
const needResplit = (value: TextContentData, old: TextContentData) => {
return (
value.text !== old.text ||
value.font !== old.font ||
value.width !== old.width ||
value.wordBreak !== old.wordBreak ||
!isSetEqual(value.breakChars, old.breakChars) ||
!isSetEqual(value.ignoreLineEnd, old.ignoreLineEnd) ||
!isSetEqual(value.ignoreLineStart, old.ignoreLineStart)
);
};
/** 每行的渲染信息 */
const renderable: TextContentRenderable[] = [];
/** 需要更新的行数 */
const dirtyIndex: number[] = [];
const spriteElement = ref<Sprite>();
/** dirtyIndex 更新指针 */
let linePointer = 0;
let startTime = 0;
/** 从哪个字符开始渲染 */
let fromChar = 0;
/** 是否需要更新渲染 */
let needUpdate = false;
const tick = () => {
if (!needUpdate) return;
spriteElement.value?.update();
const time = Date.now();
const char =
Math.floor((time - startTime) / renderData.interval!) +
fromChar;
if (!isFinite(char)) {
renderable.forEach(v => (v.pointer = v.text.length));
needUpdate = false;
return;
}
while (linePointer < dirtyIndex.length) {
const line = dirtyIndex[linePointer];
const data = renderable[line];
const pointer = char - data.from;
if (char >= data.to) {
data.pointer = data.text.length;
linePointer++;
} else { } else {
data.pointer = pointer; // @ts-ignore
break; renderData[key] = value;
} }
} }
if (linePointer >= dirtyIndex.length) { }
needUpdate = false; };
renderable.forEach(v => (v.pointer = v.text.length));
emit('typeEnd'); const makeSplitData = (): TextContentData => {
} ensureProps();
return {
text: renderData.text,
width: renderData.width!,
font: renderData.font!,
wordBreak: renderData.wordBreak!,
ignoreLineStart: new Set(renderData.ignoreLineStart),
ignoreLineEnd: new Set(renderData.ignoreLineEnd),
breakChars: new Set(renderData.breakChars)
}; };
};
onTick(tick); /**
onMounted(() => { *
data.value = makeSplitData(); */
lineData.value = splitLines(data.value); const needResplit = (value: TextContentData, old: TextContentData) => {
}); return (
value.text !== old.text ||
value.font !== old.font ||
value.width !== old.width ||
value.wordBreak !== old.wordBreak ||
!isSetEqual(value.breakChars, old.breakChars) ||
!isSetEqual(value.ignoreLineEnd, old.ignoreLineEnd) ||
!isSetEqual(value.ignoreLineStart, old.ignoreLineStart)
);
};
const renderContent = ( /** 每行的渲染信息 */
canvas: MotaOffscreenCanvas2D, const renderable: TextContentRenderable[] = [];
transform: Transform /** 需要更新的行数 */
) => { const dirtyIndex: number[] = [];
const ctx = canvas.ctx; const spriteElement = ref<Sprite>();
ctx.font = renderData.font;
ctx.fillStyle = renderData.fillStyle;
ctx.strokeStyle = renderData.strokeStyle;
ctx.lineWidth = renderData.strokeWidth;
renderable.forEach(v => { /** dirtyIndex 更新指针 */
if (v.pointer === 0) return; let linePointer = 0;
const text = v.text.slice(0, v.pointer); let startTime = 0;
/** 从哪个字符开始渲染 */
let fromChar = 0;
/** 是否需要更新渲染 */
let needUpdate = false;
const tick = () => {
if (!needUpdate) return;
spriteElement.value?.update();
const time = Date.now();
const char =
Math.floor((time - startTime) / renderData.interval!) + fromChar;
if (!isFinite(char)) {
renderable.forEach(v => (v.pointer = v.text.length));
needUpdate = false;
return;
}
while (linePointer < dirtyIndex.length) {
const line = dirtyIndex[linePointer];
const data = renderable[line];
const pointer = char - data.from;
if (char >= data.to) {
data.pointer = data.text.length;
linePointer++;
} else {
data.pointer = pointer;
break;
}
}
if (linePointer >= dirtyIndex.length) {
needUpdate = false;
renderable.forEach(v => (v.pointer = v.text.length));
emit('typeEnd');
}
};
onTick(tick);
onMounted(() => {
data.value = makeSplitData();
lineData.value = splitLines(data.value);
});
const renderContent = (
canvas: MotaOffscreenCanvas2D,
transform: Transform
) => {
const ctx = canvas.ctx;
ctx.font = renderData.font;
ctx.fillStyle = renderData.fillStyle;
ctx.strokeStyle = renderData.strokeStyle;
ctx.lineWidth = renderData.strokeWidth;
renderable.forEach(v => {
if (v.pointer === 0) return;
const text = v.text.slice(0, v.pointer);
if (renderData.textAlign === TextAlign.Left) {
if (renderData.stroke) ctx.strokeText(text, v.x, v.y); if (renderData.stroke) ctx.strokeText(text, v.x, v.y);
if (renderData.fill) ctx.fillText(text, v.x, v.y); if (renderData.fill) ctx.fillText(text, v.x, v.y);
}); } else if (renderData.textAlign === TextAlign.Center) {
}; const x = (renderData.width - v.x) / 2 + v.x;
if (renderData.stroke) ctx.strokeText(text, x, v.y);
if (renderData.fill) ctx.fillText(text, x, v.y);
} else {
const x = renderData.width;
if (renderData.stroke) ctx.strokeText(text, x, v.y);
if (renderData.fill) ctx.fillText(text, x, v.y);
}
});
};
/** /**
* renderable * renderable
* @param text * @param text
* @param lines * @param lines
* @param index * @param index
* @param from * @param from
*/ */
const makeRenderable = ( const makeRenderable = (
text: string, text: string,
lines: number[], lines: number[],
index: number, index: number,
from: number from: number
) => { ) => {
renderable.splice(index); renderable.splice(index);
dirtyIndex.splice(0); dirtyIndex.splice(0);
dirtyIndex.push(index); dirtyIndex.push(index);
// 初始化渲染 // 初始化渲染
linePointer = 0; linePointer = 0;
startTime = Date.now(); startTime = Date.now();
fromChar = from; fromChar = from;
needUpdate = true; needUpdate = true;
let startY = renderable.reduce( let startY = renderable.reduce(
(prev, curr) => prev + curr.textHeight + curr.height, (prev, curr) => prev + curr.textHeight + curr.height,
0 0
); );
// 第一个比较特殊,需要特判 // 第一个比较特殊,需要特判
const start = lines[index - 1] ?? 0; const start = lines[index - 1] ?? 0;
const end = lines[index]; const end = lines[index];
const startPointer = from > start && from < end ? from - start : 0; const startPointer = from > start && from < end ? from - start : 0;
const height = testHeight(text, renderData.font!);
startY += height;
renderable.push({
text: text.slice(start, end),
x: 0,
y: startY,
height: renderData.lineHeight!,
textHeight: height,
pointer: startPointer,
from: start,
to: end
});
for (let i = index + 1; i < lines.length; i++) {
dirtyIndex.push(i);
const start = lines[i - 1] ?? 0;
const end = lines[i];
const height = testHeight(text, renderData.font!); const height = testHeight(text, renderData.font!);
startY += height; startY += height;
renderable.push({ renderable.push({
text: text.slice(start, end), text: text.slice(start, end),
x: 0, x: 0,
y: startY, y: startY,
height: renderData.lineHeight!, height: renderData.lineHeight!,
textHeight: height, textHeight: height,
pointer: startPointer, pointer: 0,
from: start, from: start,
to: end to: end
}); });
}
emit('typeStart');
};
for (let i = index + 1; i < lines.length; i++) { /**
dirtyIndex.push(i); *
const start = lines[i - 1] ?? 0; */
const end = lines[i]; const rawRender = (text: string, lines: number[]) => {
const height = testHeight(text, renderData.font!); makeRenderable(text, lines, 0, 0);
startY += height; spriteElement.value?.update();
};
renderable.push({ /**
text: text.slice(start, end), *
x: 0, * @param from
y: startY, * @param lines
height: renderData.lineHeight!, * @param index
textHeight: height, */
pointer: 0, const continueRender = (
from: start, text: string,
to: end from: number,
}); lines: number[],
} index: number
emit('typeStart'); ) => {
}; makeRenderable(text, lines, index, from);
spriteElement.value?.update();
};
/** const data = shallowRef<TextContentData>(makeSplitData());
*
*/
const rawRender = (text: string, lines: number[]) => {
makeRenderable(text, lines, 0, 0);
spriteElement.value?.update();
};
/** onUpdated(() => {
* data.value = makeSplitData();
* @param from });
* @param lines
* @param index
*/
const continueRender = (
text: string,
from: number,
lines: number[],
index: number
) => {
makeRenderable(text, lines, index, from);
spriteElement.value?.update();
};
const data = shallowRef<TextContentData>(makeSplitData()); let shouldKeep = false;
const lineData = shallowRef([0]);
watch(data, (value, old) => {
if (needResplit(value, old)) {
lineData.value = splitLines(value);
}
onUpdated(() => { if (renderData.keepLast && value.text.startsWith(old.text)) {
data.value = makeSplitData(); shouldKeep = true;
}); }
});
let shouldKeep = false; // 判断是否需要接续渲染
const lineData = shallowRef([0]); watch(lineData, (value, old) => {
watch(data, (value, old) => { if (shouldKeep) {
if (needResplit(value, old)) { shouldKeep = false;
lineData.value = splitLines(value); const isSub = old.slice(0, -1).every((v, i) => v === value[i]);
}
if (renderData.keepLast && value.text.startsWith(old.text)) { // 有点地狱的条件分歧,大体就是分为两种情况,一种是两个末尾一致,如果一致那直接从下一行接着画就完事了
shouldKeep = true; // 但是如果不一致,那么从旧的最后一个开始往后画
} if (isSub) {
}); const last = value[old.length - 1];
const oldLast = value.at(-1);
// 判断是否需要接续渲染 if (!last) {
watch(lineData, (value, old) => {
if (shouldKeep) {
shouldKeep = false;
const isSub = old.slice(0, -1).every((v, i) => v === value[i]);
// 有点地狱的条件分歧,大体就是分为两种情况,一种是两个末尾一致,如果一致那直接从下一行接着画就完事了
// 但是如果不一致,那么从旧的最后一个开始往后画
if (isSub) {
const last = value[old.length - 1];
const oldLast = value.at(-1);
if (!last) {
rawRender(data.value.text, value);
return;
}
if (last === oldLast) {
const index = old.length - 1;
continueRender(data.value.text, last, value, index);
} else {
if (!oldLast) {
rawRender(data.value.text, value);
} else {
const index = old.length - 1;
continueRender(
data.value.text,
oldLast,
value,
index
);
}
}
} else {
rawRender(data.value.text, value); rawRender(data.value.text, value);
return;
}
if (last === oldLast) {
const index = old.length - 1;
continueRender(data.value.text, last, value, index);
} else {
if (!oldLast) {
rawRender(data.value.text, value);
} else {
const index = old.length - 1;
continueRender(data.value.text, oldLast, value, index);
}
} }
} else { } else {
rawRender(data.value.text, value); rawRender(data.value.text, value);
} }
}); } else {
rawRender(data.value.text, value);
}
});
return () => { return () => {
return ( return (
<sprite <sprite
ref={spriteElement} ref={spriteElement}
hd hd
antiAliasing={false} antiAliasing={true}
x={renderData.x} x={renderData.x}
y={renderData.y} y={renderData.y}
width={renderData.width} width={renderData.width}
height={renderData.height} height={renderData.height}
render={renderContent} render={renderContent}
></sprite> ></sprite>
); );
}; };
}, }, textContentOptions);
textContentOptions
);
export interface TextboxProps extends TextContentProps { export interface TextboxProps extends TextContentProps {
id?: string;
/** 背景颜色 */ /** 背景颜色 */
backColor?: CanvasStyle; backColor?: CanvasStyle;
/** 背景 winskin */ /** 背景 winskin */
winskin?: string; winskin?: string;
/** 边框与文字间的距离默认为8 */
padding?: number;
} }
interface TextboxSlots extends SlotsType { type TextboxEmits = TextContentEmits;
default: () => VNode; type TextboxSlots = SlotsType<{ default: (data: TextboxProps) => VNode[] }>;
}
const textboxOptions = { const textboxOptions = {
props: (textContentOptions.props as (keyof TextboxProps)[]).concat([ props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
'backColor', 'backColor',
'winskin' 'winskin',
]) 'id'
]),
emits: textContentOptions.emits
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>; } satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
export const Textbox = defineComponent<TextboxProps, {}, string, TextboxSlots>( let id = 0;
(props, { slots }) => { function getNextTextboxId() {
return () => { return `@default-textbox-${id++}`;
return ( }
<container>
{slots.default ? ( export const Textbox = defineComponent<
slots.default() TextboxProps,
) : props.winskin ? ( TextboxEmits,
// todo keyof TextboxEmits,
<winskin></winskin> TextboxSlots
) : ( >((props, { slots }) => {
// todo const data = shallowReactive({ ...props });
<g-rect data.padding ??= 8;
x={0} data.width ??= 200;
y={0} data.height ??= 200;
width={props.width ?? 200} data.id ??= '';
height={props.height ?? 200}
fill const store = TextboxStore.use(props.id ?? getNextTextboxId(), data);
fillStyle={props.backColor} const hidden = ref(false);
></g-rect> store.on('hide', () => (hidden.value = true));
)} store.on('show', () => (hidden.value = false));
<TextContent {...props}></TextContent> onUpdated(() => {
</container> for (const [key, value] of Object.entries(props)) {
); // @ts-ignore
}; if (!isNil(value)) data[key] = value;
}, }
textboxOptions });
);
const contentWidth = computed(() => data.width! - data.padding! * 2);
const contentHeight = computed(() => data.height! - data.padding! * 2);
return () => {
return (
<container hidden={hidden.value} id="11111">
{slots.default ? (
slots.default(data)
) : props.winskin ? (
// todo
<winskin></winskin>
) : (
// todo
<g-rect
x={0}
y={0}
width={data.width ?? 200}
height={data.height ?? 200}
fill
fillStyle={data.backColor}
></g-rect>
)}
<TextContent
{...data}
x={data.padding}
y={data.padding}
width={contentWidth.value}
height={contentHeight.value}
></TextContent>
</container>
);
};
}, textboxOptions);
const fontSizeGuessScale = new Map<string, number>([ const fontSizeGuessScale = new Map<string, number>([
['px', 1], ['px', 1],
@ -531,6 +584,7 @@ function splitLines(data: TextContentData) {
let mid = 0; let mid = 0;
let guessCount = 1; let guessCount = 1;
let splitProgress = false; let splitProgress = false;
let last = 0;
while (1) { while (1) {
if (!splitProgress) { if (!splitProgress) {
@ -548,8 +602,15 @@ function splitLines(data: TextContentData) {
const diff = end - start; const diff = end - start;
if (diff === 1) { if (diff === 1) {
res.push(words[start]); if (start === last) {
res.push(words[last + 1]);
start = words[last + 1];
end = start + 1;
} else {
res.push(words[start]);
}
if (end >= words.length) break; if (end >= words.length) break;
last = resolved;
resolved = start; resolved = start;
end = Math.ceil(start + guess); end = Math.ceil(start + guess);
if (end > words.length) end = words.length; if (end > words.length) end = words.length;
@ -659,3 +720,67 @@ function testHeight(text: string, font: string) {
ctx.font = font; ctx.font = font;
return ctx.measureText(text).fontBoundingBoxAscent; return ctx.measureText(text).fontBoundingBoxAscent;
} }
interface TextboxStoreEvent {
update: [value: TextboxProps];
show: [];
hide: [];
}
export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
static list: Map<string, TextboxStore> = new Map();
private constructor(private readonly data: TextboxProps) {
super();
}
/**
*
*/
modify(data: Partial<TextboxProps>) {
for (const [key, value] of Object.entries(data)) {
// @ts-ignore
if (!isNil(value)) this.data[key] = value;
}
this.emit('update', this.data);
}
/**
*
*/
show() {
this.emit('show');
}
/**
*
*/
hide() {
this.emit('hide');
}
/**
*
* @param id id
*/
static get(id: string): TextboxStore | undefined {
return this.list.get(id);
}
/**
*
* @param id id
* @param props
*/
static use(id: string, props: TextboxProps) {
const store = new TextboxStore(props);
if (this.list.has(id)) {
logger.warn(42, id);
}
this.list.set(id, store);
onUnmounted(() => {
this.list.delete(id);
});
return store;
}
}

View File

@ -159,6 +159,7 @@ const beforeFrame: (() => void)[] = [];
const afterFrame: (() => void)[] = []; const afterFrame: (() => void)[] = [];
const renderFrame: (() => void)[] = []; const renderFrame: (() => void)[] = [];
let count = 0;
export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
extends EventEmitter<ERenderItemEvent | E> extends EventEmitter<ERenderItemEvent | E>
implements implements
@ -182,6 +183,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
/** id到渲染元素的映射 */ /** id到渲染元素的映射 */
static itemMap: Map<string, RenderItem> = new Map(); static itemMap: Map<string, RenderItem> = new Map();
readonly uid: number = count++;
private _id: string = ''; private _id: string = '';
get id(): string { get id(): string {
@ -399,11 +402,11 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
this.anchorY = y; this.anchorY = y;
} }
update(item: RenderItem<any> = this): void { update(item: RenderItem<any> = this, force: boolean = false): void {
if (this.needUpdate || this.hidden) return; if ((this.needUpdate || this.hidden) && !force) return;
this.needUpdate = true; this.needUpdate = true;
this.cacheDirty = true; this.cacheDirty = true;
this.parent?.update(item); this.parent?.update(item, force);
} }
setHD(hd: boolean): void { setHD(hd: boolean): void {
@ -473,7 +476,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
hide() { hide() {
if (this.hidden) return; if (this.hidden) return;
this.hidden = true; this.hidden = true;
this.update(this); this.update(this, true);
} }
/** /**
@ -482,7 +485,23 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
show() { show() {
if (!this.hidden) return; if (!this.hidden) return;
this.hidden = false; this.hidden = false;
this.update(this); this.refreshAllChildren(true);
}
/**
*
*/
refreshAllChildren(force: boolean = false) {
if (this.children.size > 0) {
const stack: RenderItem[] = [this];
while (stack.length > 0) {
const item = stack.pop();
if (!item) continue;
item.cacheDirty = true;
item.children.forEach(v => stack.push(v));
}
}
this.update(this, force);
} }
/** /**
@ -555,7 +574,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
if (typeof expected === 'string') { if (typeof expected === 'string') {
const type = typeof value; const type = typeof value;
if (type !== expected) { if (type !== expected) {
logger.warn(21, key, expected, type); logger.error(21, key, expected, type);
return false; return false;
} else { } else {
return true; return true;
@ -564,7 +583,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
if (value instanceof expected) { if (value instanceof expected) {
return true; return true;
} else { } else {
logger.warn( logger.error(
21, 21,
key, key,
expected.name, expected.name,

View File

@ -9,7 +9,6 @@ export class MotaRenderer extends Container {
target!: MotaOffscreenCanvas2D; target!: MotaOffscreenCanvas2D;
protected needUpdate: boolean = false;
readonly isRoot: boolean = true; readonly isRoot: boolean = true;
constructor(id: string = 'render-main') { constructor(id: string = 'render-main') {
@ -84,27 +83,13 @@ export class MotaRenderer extends Container {
this.target.delete(); this.target.delete();
} }
/**
*
*/
refreshAll() {
const stack: RenderItem[] = [this];
while (stack.length > 0) {
const item = stack.pop();
if (!item) break;
if (item.children.size === 0) {
item.update();
} else {
item.children.forEach(v => stack.push(v));
}
}
}
static get(id: string) { static get(id: string) {
return this.list.get(id); return this.list.get(id);
} }
} }
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
MotaRenderer.list.forEach(v => v.requestAfterFrame(() => v.refreshAll())); MotaRenderer.list.forEach(v =>
v.requestAfterFrame(() => v.refreshAllChildren())
);
}); });

View File

@ -49,7 +49,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
}, },
createText: function (text: string): RenderItem<ERenderItemEvent> { createText: function (text: string): RenderItem<ERenderItemEvent> {
logger.warn(38); if (!/^\s*$/.test(text)) logger.warn(38);
return new Text(text); return new Text(text);
}, },

View File

@ -69,6 +69,7 @@
"39": "Plain text is not supported outside Text element.", "39": "Plain text is not supported outside Text element.",
"40": "Cannot return canvas that is not provided by this pool.", "40": "Cannot return canvas that is not provided by this pool.",
"41": "Width of text content components must be positive. receive: $1", "41": "Width of text content components must be positive. receive: $1",
"42": "Repeat Textbox id: '$1'.",
"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."
} }